You've already forked lazarus-ccr
2361 lines
78 KiB
ObjectPascal
2361 lines
78 KiB
ObjectPascal
![]() |
{*********************************************************}
|
||
|
{* FlashFiler: FF 2 file & block interface classes *}
|
||
|
{*********************************************************}
|
||
|
|
||
|
(* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Version: MPL 1.1
|
||
|
*
|
||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||
|
* the License. You may obtain a copy of the License at
|
||
|
* http://www.mozilla.org/MPL/
|
||
|
*
|
||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
|
* for the specific language governing rights and limitations under the
|
||
|
* License.
|
||
|
*
|
||
|
* The Original Code is TurboPower FlashFiler
|
||
|
*
|
||
|
* The Initial Developer of the Original Code is
|
||
|
* TurboPower Software
|
||
|
*
|
||
|
* Portions created by the Initial Developer are Copyright (C) 1996-2002
|
||
|
* the Initial Developer. All Rights Reserved.
|
||
|
*
|
||
|
* Contributor(s):
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** *)
|
||
|
|
||
|
{$I ffdefine.inc}
|
||
|
|
||
|
unit ffv2file;
|
||
|
|
||
|
interface
|
||
|
|
||
|
uses
|
||
|
Classes,
|
||
|
FFFileInt,
|
||
|
FFLLBase,
|
||
|
FFSrBase,
|
||
|
FFTbDict;
|
||
|
|
||
|
type
|
||
|
Tffv2FileInterface = class(TffFileInterface)
|
||
|
{ Implements the interface for FF 2.xx tables. }
|
||
|
protected
|
||
|
FBufMgr : TffBufferManager;
|
||
|
{ Buffer manager used to manage file blocks. }
|
||
|
FDict : TffServerDataDict;
|
||
|
{ Server data dictionary. }
|
||
|
FDictBlocks : TInterfaceList;
|
||
|
{ List of data dictionary blocks. }
|
||
|
FFileInfo : PffFileInfo;
|
||
|
{ Structure used to store information about file being verified. }
|
||
|
FFileHeaderBlock : IFileHeaderBlock;
|
||
|
FIndexHeaderBlock : IIndexHeaderBlock;
|
||
|
FOnReportError : TffReportErrorEvent;
|
||
|
FOnReportFix : TffReportFixEvent;
|
||
|
|
||
|
FTI : PffTransInfo;
|
||
|
{ Fake transaction information. }
|
||
|
|
||
|
procedure CloseCurrentFile;
|
||
|
{ If a file is open, this method closes the file. }
|
||
|
|
||
|
function GetDictBlockCount : Integer; override;
|
||
|
function GetDictBlocks(const Inx : Longint) : IStreamBlock; override;
|
||
|
function GetOnReportError : TffReportErrorEvent; override;
|
||
|
function GetOnReportFix : TffReportFixEvent; override;
|
||
|
|
||
|
procedure SetOnReportError(Value : TffReportErrorEvent); override;
|
||
|
procedure SetOnReportFix(Value : TffReportFixEvent); override;
|
||
|
procedure SetOutputVersion(const Value : Longint); override;
|
||
|
|
||
|
public
|
||
|
destructor Destroy; override;
|
||
|
|
||
|
procedure Initialize; override;
|
||
|
|
||
|
procedure Close; override;
|
||
|
{ Close the currently opened file. }
|
||
|
|
||
|
function GetBlock(const BlockNumber : Longint) : ICommonBlock; override;
|
||
|
{ Returns a specific block from the file. }
|
||
|
|
||
|
function GetFileHeaderBlock : IFileHeaderBlock; override;
|
||
|
{ Returns the file header block. }
|
||
|
|
||
|
function GetFileInfo : TffGeneralFileInfo; override;
|
||
|
{ Returns general file information that is made available to blocks. }
|
||
|
|
||
|
function GetIndexHeaderBlock : IIndexHeaderBlock; override;
|
||
|
{ Returns the index header block. }
|
||
|
|
||
|
procedure Open(const Filename : string); override;
|
||
|
{ Open a file for analysis. }
|
||
|
|
||
|
procedure Pack; override;
|
||
|
|
||
|
end;
|
||
|
|
||
|
Tffv2FileBlock = class(TffFileBlock)
|
||
|
protected
|
||
|
FIsModified : Boolean;
|
||
|
{ Set to True when BeginUpdate is called.
|
||
|
Set to False when EndUpdate is called. }
|
||
|
public
|
||
|
constructor Create(BufMgr : TffBufferManager;
|
||
|
FileInfo : PffFileInfo;
|
||
|
TI : PffTransInfo;
|
||
|
const BlockNum : TffWord32); override;
|
||
|
|
||
|
procedure BeginUpdate; override;
|
||
|
procedure EndUpdate; override;
|
||
|
end;
|
||
|
|
||
|
TffFileHeaderBlock = class(Tffv2FileBlock, IFileHeaderBlock)
|
||
|
protected
|
||
|
function GetAvailBlocks : Longint; virtual;
|
||
|
function GetBLOBCount : TffWord32; virtual;
|
||
|
function GetBlockSize : Longint; virtual;
|
||
|
function GetDataDictBlockNum : TffWord32; virtual;
|
||
|
function GetDeletedBLOBHead : TffInt64; virtual;
|
||
|
function GetDeletedBLOBTail : TffInt64; virtual;
|
||
|
function GetDeletedRecordCount : Longint; virtual;
|
||
|
function GetEncrypted : Longint; virtual;
|
||
|
function GetEstimatedUsedBlocks : TffWord32; virtual;
|
||
|
function GetFFVersion : Longint; virtual;
|
||
|
function GetFieldCount : Longint; virtual;
|
||
|
function GetFirstDataBlock : TffWord32; virtual;
|
||
|
function GetFirstDeletedRecord : TffInt64; virtual;
|
||
|
function GetFirstFreeBlock : TffWord32; virtual;
|
||
|
function GetHasSequentialIndex : Longint; virtual;
|
||
|
function GetIndexCount : Longint; virtual;
|
||
|
function GetIndexHeaderBlockNum : TffWord32; virtual;
|
||
|
function GetLastAutoIncValue : TffWord32; virtual;
|
||
|
function GetLastDataBlock : TffWord32; virtual;
|
||
|
function GetLog2BlockSize : TffWord32; virtual;
|
||
|
function GetRecLenPlusTrailer : Longint; virtual;
|
||
|
function GetRecordCount : Longint; virtual;
|
||
|
function GetRecordLength : Longint; virtual;
|
||
|
function GetRecordsPerBlock : Longint; virtual;
|
||
|
function GetUsedBlocks : TffWord32; virtual;
|
||
|
function GetPropertyCell(const Row, Column : Integer) : string; override;
|
||
|
function GetPropertyRowCount : Integer; override;
|
||
|
|
||
|
procedure SetFirstDataBlock(const Value : TffWord32); virtual;
|
||
|
procedure SetFirstFreeBlock(const Value : TffWord32); virtual;
|
||
|
procedure SetHasSequentialIndex(const Value : Longint); virtual;
|
||
|
procedure SetLastDataBlock(const Value : TffWord32); virtual;
|
||
|
procedure SetLog2BlockSize(const Value : TffWord32); virtual;
|
||
|
procedure SetUsedBlocks(const Value : TffWord32); virtual;
|
||
|
|
||
|
procedure VerifyRepair(const Repair : Boolean); override;
|
||
|
{ This method is used by both Verify & Repair. It carries out the actual
|
||
|
verification &, if specified, repairing of problems. }
|
||
|
public
|
||
|
|
||
|
{ Properties }
|
||
|
property AvailBlocks : Longint
|
||
|
read GetAvailBlocks;
|
||
|
{ The number of free blocks in the file. }
|
||
|
|
||
|
property BLOBCount : TffWord32
|
||
|
read GetBLOBCount;
|
||
|
{ The number of BLOBs in the table. }
|
||
|
|
||
|
property BlockSize : Longint
|
||
|
read GetBlockSize;
|
||
|
{ Size of blocks in bytes (e.g., 4k, 8k, 16k, 32k, 64k) }
|
||
|
|
||
|
property DataDictBlockNum : TffWord32
|
||
|
read GetDataDictBlockNum;
|
||
|
{ The block number of the data dictionary. If there is no data
|
||
|
dictionary then this property returns the value zero. }
|
||
|
|
||
|
property DeletedBLOBHead : TffInt64
|
||
|
read GetDeletedBLOBHead;
|
||
|
{ The file-relative offset of the first segment in the deleted BLOB
|
||
|
chain. }
|
||
|
|
||
|
property DeletedBLOBTail : TffInt64
|
||
|
read GetDeletedBLOBTail;
|
||
|
{ The file-relative offset of the last segment in the deleted BLOB
|
||
|
chain. }
|
||
|
|
||
|
property DeletedRecordCount : Longint
|
||
|
read GetDeletedRecordCount;
|
||
|
{ The number of deleted records in the table. }
|
||
|
|
||
|
property Encrypted : Longint
|
||
|
read GetEncrypted;
|
||
|
{ 0 = not encrypted, 1 = encrypted }
|
||
|
|
||
|
property EstimatedUsedBlocks : TffWord32
|
||
|
read GetEstimatedUsedBlocks;
|
||
|
{ For cases where the UsedBlocks counter is invalid, use this property
|
||
|
to estimate the number of used blocks in the file. This only works
|
||
|
in cases where the BlockSize is valid. }
|
||
|
|
||
|
property FFVersion : Longint
|
||
|
read GetFFVersion;
|
||
|
{ The version of FlashFiler with which this table was created. }
|
||
|
|
||
|
property FieldCount : Longint
|
||
|
read GetFieldCount;
|
||
|
{ The number of fields in a record. }
|
||
|
|
||
|
property FirstDataBlock : TffWord32
|
||
|
read GetFirstDataBlock write SetFirstDataBlock;
|
||
|
{ The first data block in the chain of data blocks. }
|
||
|
|
||
|
property FirstDeletedRecord : TffInt64
|
||
|
read GetFirstDeletedRecord;
|
||
|
{ The offset of the first record in the deleted record chain. }
|
||
|
|
||
|
property FirstFreeBlock : TffWord32
|
||
|
read GetFirstFreeBlock;
|
||
|
{ The block number of the first free block in the deleted block chain. }
|
||
|
|
||
|
property HasSequentialIndex : Longint
|
||
|
read GetHasSequentialIndex write SetHasSequentialIndex;
|
||
|
{ Identifies whether the table has a sequential index. A value of zero
|
||
|
means the table does not have a sequential index. A value of 1
|
||
|
means the table does have a sequential index. }
|
||
|
|
||
|
property IndexCount : Longint
|
||
|
read GetIndexCount;
|
||
|
{ The number of indexes in the table. }
|
||
|
|
||
|
property IndexHeaderBlockNum : TffWord32
|
||
|
read GetIndexHeaderBlockNum;
|
||
|
{ The block number of the index header. }
|
||
|
|
||
|
property LastAutoIncValue : TffWord32
|
||
|
read GetLastAutoIncValue;
|
||
|
{ The last autoincrement value assigned to a record in the table. }
|
||
|
|
||
|
property LastDataBlock : TffWord32
|
||
|
read GetLastDataBlock write SetLastDataBlock;
|
||
|
{ The last data block in the chain of data blocks. }
|
||
|
|
||
|
property Log2BlockSize : TffWord32
|
||
|
read GetLog2BlockSize write SetLog2BlockSize;
|
||
|
{ log base 2 of BlockSize (e.g., 12, 13, 14, 15, or 16) }
|
||
|
|
||
|
property RecordCount : Longint
|
||
|
read GetRecordCount;
|
||
|
{ The number of records in the table. }
|
||
|
|
||
|
property RecordLength : Longint
|
||
|
read GetRecordLength;
|
||
|
{ The length of the record in bytes. }
|
||
|
|
||
|
property RecordLengthPlusTrailer : Longint
|
||
|
read GetRecLenPlusTrailer;
|
||
|
{ The length of the record plus the deletion link. }
|
||
|
|
||
|
property RecordsPerBlock : Longint
|
||
|
read GetRecordsPerBlock;
|
||
|
{ The number of records per data block. }
|
||
|
|
||
|
property UsedBlocks : TffWord32
|
||
|
read GetUsedBlocks write SetUsedBlocks;
|
||
|
{ The number of blocks in the file. }
|
||
|
|
||
|
end;
|
||
|
|
||
|
TffIndexBlock = class(Tffv2FileBlock, IIndexBlock)
|
||
|
protected
|
||
|
function GetIndexBlockType : Byte; virtual;
|
||
|
function GetIsLeafPage : Boolean; virtual;
|
||
|
function GetNodeLevel : Byte; virtual;
|
||
|
function GetKeysAreRefs : Boolean; virtual;
|
||
|
function GetIndexNum : Word; virtual;
|
||
|
function GetKeyLength : Word; virtual;
|
||
|
function GetKeyCount : Longint; virtual;
|
||
|
function GetMaxKeyCount : Longint; virtual;
|
||
|
function GetPrevPageRef : TffWord32; virtual;
|
||
|
function GetPropertyCell(const Row, Column : Integer) : string; override;
|
||
|
function GetPropertyRowCount : Integer; override;
|
||
|
|
||
|
procedure VerifyRepair(const Repair : Boolean); override;
|
||
|
{ This method is used by both Verify & Repair. It carries out the actual
|
||
|
verification &, if specified, repairing of problems. }
|
||
|
public
|
||
|
property IndexBlockType : Byte
|
||
|
read GetIndexBlockType;
|
||
|
{ The type of index block. Header blocks have value 0, B-Tree pages
|
||
|
have value 1. }
|
||
|
property IsLeafPage : Boolean
|
||
|
read GetIsLeafPage;
|
||
|
{ Returns False if this is an internal B-Tree page or True if this is
|
||
|
a leaf B-Tree page. }
|
||
|
property NodeLevel : Byte
|
||
|
read GetNodeLevel;
|
||
|
{ Returns the node level. Leaves have value 1, increments. }
|
||
|
property KeysAreRefs : Boolean
|
||
|
read GetKeysAreRefs;
|
||
|
{ Returns the value True if the keys in the index are record reference
|
||
|
numbers. }
|
||
|
property IndexNum : Word
|
||
|
read GetIndexNum;
|
||
|
{ The index number with which the index page is associated. }
|
||
|
property KeyLength : Word
|
||
|
read GetKeyLength;
|
||
|
{ The length of each key. }
|
||
|
property KeyCount : Longint
|
||
|
read GetKeyCount;
|
||
|
{ The number of keys currently in the page. }
|
||
|
property MaxKeyCount : Longint
|
||
|
read GetMaxKeyCount;
|
||
|
{ The maximum number of keys that may be placed within the page. }
|
||
|
property PrevPageRef : TffWord32
|
||
|
read GetPrevPageRef;
|
||
|
{ Block number of the previous page. }
|
||
|
end;
|
||
|
|
||
|
TffIndexHeaderBlock = class(TffIndexBlock, IIndexBlock, IIndexHeaderBlock)
|
||
|
protected
|
||
|
FDataColumns : Integer;
|
||
|
FIndexHead : PffIndexHeader;
|
||
|
procedure VerifyRepair(const Repair : Boolean); override;
|
||
|
{ This method is used by both Verify & Repair. It carries out the actual
|
||
|
verification &, if specified, repairing of problems. }
|
||
|
public
|
||
|
constructor Create(BufMgr : TffBufferManager;
|
||
|
FileInfo : PffFileInfo;
|
||
|
TI : PffTransInfo;
|
||
|
const BlockNum : TffWord32); override;
|
||
|
|
||
|
{ Data access }
|
||
|
function GetDataCell(const Row, Column : Integer) : string; override;
|
||
|
function GetDataColCaption(const Index : Integer) : string; override;
|
||
|
function GetDataColCount : Integer; override;
|
||
|
function GetDataColWidth(const Index : Integer) : Integer; override;
|
||
|
function GetDataRowCount : Integer; override;
|
||
|
end;
|
||
|
|
||
|
TffDataBlock = class(Tffv2FileBlock, IDataBlock)
|
||
|
protected
|
||
|
|
||
|
FNumDataColumns : Integer;
|
||
|
{ The number of columns calculated for the data view. }
|
||
|
|
||
|
function GetNextDataBlock : TffWord32; virtual;
|
||
|
function GetPrevDataBlock : TffWord32; virtual;
|
||
|
function GetRecCount : Longint; virtual;
|
||
|
function GetRecLen : Longint; virtual;
|
||
|
|
||
|
function IsEmptyLookupEntry(Entry : PffBLOBLookupEntry) : Boolean; virtual;
|
||
|
|
||
|
{ Property access }
|
||
|
function GetPropertyCell(const Row, Column : Integer) : string; override;
|
||
|
function GetPropertyRowCount : Integer; override;
|
||
|
|
||
|
{ Data access }
|
||
|
function GetDataCell(const Row, Column : Integer) : string; override;
|
||
|
function GetDataColCaption(const Index : Integer) : string; override;
|
||
|
function GetDataColCount : Integer; override;
|
||
|
function GetDataColWidth(const Index : Integer) : Integer; override;
|
||
|
function GetDataRowCount : Integer; override;
|
||
|
|
||
|
procedure SetNextDataBlock(const Value : TffWord32); virtual;
|
||
|
procedure SetPrevDataBlock(const Value : TffWord32); virtual;
|
||
|
procedure SetRecCount(const Value : Longint); virtual;
|
||
|
procedure SetRecLen(const Value : Longint); virtual;
|
||
|
|
||
|
procedure VerifyBLOB(const BLOBNr : TffInt64;
|
||
|
var ErrCode : Integer); virtual;
|
||
|
public
|
||
|
procedure VerifyRepair(const Repair : Boolean); override;
|
||
|
|
||
|
property RecordCount : Longint
|
||
|
read GetRecCount write SetRecCount;
|
||
|
{ The maximum number of records in the block. }
|
||
|
property RecordLen : Longint
|
||
|
read GetRecLen write SetRecLen;
|
||
|
{ The length of each record. }
|
||
|
property NextDataBlock : TffWord32
|
||
|
read GetNextDataBlock write SetNextDataBlock;
|
||
|
{ The block # of the next data block. }
|
||
|
property PrevDataBlock : TffWord32
|
||
|
read GetPrevDataBlock write SetPrevDataBlock;
|
||
|
{ The block # of the previous data block. }
|
||
|
end;
|
||
|
|
||
|
TffBLOBBlock = class(Tffv2FileBlock, IBLOBBlock)
|
||
|
protected
|
||
|
public
|
||
|
procedure VerifyRepair(const Repair : Boolean); override;
|
||
|
end;
|
||
|
|
||
|
TffStreamBlock = class(Tffv2FileBlock, IStreamBlock)
|
||
|
protected
|
||
|
function GetNextStrmBlock : TffWord32; virtual;
|
||
|
function GetStreamType : Longint; virtual;
|
||
|
function GetStreamLength : Longint; virtual;
|
||
|
function GetOwningStream : Longint; virtual;
|
||
|
function GetPropertyCell(const Row, Column : Integer) : string; override;
|
||
|
function GetPropertyRowCount : Integer; override;
|
||
|
public
|
||
|
procedure VerifyRepair(const Repair : Boolean); override;
|
||
|
end;
|
||
|
|
||
|
implementation
|
||
|
|
||
|
uses
|
||
|
FFDbBase,
|
||
|
FFFile,
|
||
|
FFRepCnst,
|
||
|
FFSrBDE,
|
||
|
FFSrBLOB,
|
||
|
FFSrEng,
|
||
|
FFTbData,
|
||
|
FFTbIndx,
|
||
|
FFUtil,
|
||
|
SysUtils,
|
||
|
Windows;
|
||
|
|
||
|
const
|
||
|
FFStartVersion : Longint = 20000; {2.00.00}
|
||
|
FFEndVersion : Longint = 29999; {2.99.99, all FF 2 versions }
|
||
|
|
||
|
ciDataBlockRows = 4;
|
||
|
ciFileHeaderRows = 24;
|
||
|
ciIndexBlockRows = 9;
|
||
|
ciIndexHeaderDataColumns = 6;
|
||
|
ciStreamRows = 4;
|
||
|
|
||
|
csAlias = 'FFVerify';
|
||
|
|
||
|
{ The following constants were copied from the implementation section of unit
|
||
|
FFTBBLOB. }
|
||
|
const
|
||
|
ffc_FileBLOB = -1;
|
||
|
ffc_BLOBLink = -2;
|
||
|
|
||
|
{ The following types were copied from the implementation section of unit
|
||
|
FFTBINDX. }
|
||
|
|
||
|
type
|
||
|
PRef = ^TRef;
|
||
|
TRef = TffInt64;
|
||
|
PPageNum = ^TpageNum;
|
||
|
TPageNum = TffWord32;
|
||
|
|
||
|
const
|
||
|
SizeOfRef = sizeof(TRef);
|
||
|
SizeOfPageNum = sizeof(TPageNum);
|
||
|
|
||
|
type
|
||
|
PRefBlock = ^TRefBlock;
|
||
|
TRefBlock = array [0..($FFFFFFF div SizeOfRef)-1] of TRef;
|
||
|
|
||
|
PPageNumBlock = ^TPageNumBlock;
|
||
|
TPageNumBlock = array [0..($FFFFFFF div SizeOfPageNum)-1] of TPageNum;
|
||
|
|
||
|
|
||
|
{===TffFileInterface=================================================}
|
||
|
destructor Tffv2FileInterface.Destroy;
|
||
|
begin
|
||
|
{ If a file is open then close it. }
|
||
|
CloseCurrentFile;
|
||
|
|
||
|
FTI^.tirTrans.Free;
|
||
|
FFFreeMem(FTI, SizeOf(TffTransInfo));
|
||
|
|
||
|
if Assigned(FBufMgr) then
|
||
|
FBufMgr.free;
|
||
|
|
||
|
FDictBlocks.Free;
|
||
|
|
||
|
inherited;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileInterface.Initialize;
|
||
|
begin
|
||
|
inherited;
|
||
|
FBufMgr := TffBufferManager.Create(GetCurrentDir, 1);
|
||
|
FBufMgr.MaxRAM := 20;
|
||
|
fileProcsInitialize;
|
||
|
FFGetMem(FTI, SizeOf(TffTransInfo));
|
||
|
FOutputVersion := FFVersionNumber;
|
||
|
FTI^.tirLockMgr := nil;
|
||
|
FTI^.tirTrans := TffSrTransaction.Create(1000, False, False);
|
||
|
|
||
|
FEndFFVersion := FFEndVersion;
|
||
|
FStartFFVersion := FFStartVersion;
|
||
|
|
||
|
FDictBlocks := TInterfaceList.Create;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileInterface.Close;
|
||
|
begin
|
||
|
CloseCurrentFile;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileInterface.CloseCurrentFile;
|
||
|
var
|
||
|
Inx : Integer;
|
||
|
begin
|
||
|
if Assigned(FDict) then begin
|
||
|
FDict.Free;
|
||
|
FDict := nil;
|
||
|
end;
|
||
|
|
||
|
if FFileHeaderBlock <> nil then
|
||
|
FFileHeaderBlock := nil;
|
||
|
{ No need to free since it will be autofreed. }
|
||
|
|
||
|
if FIndexHeaderBlock <> nil then
|
||
|
FIndexHeaderBlock := nil;
|
||
|
|
||
|
{ Free the list of dictionary blocks. }
|
||
|
for Inx := Pred(FDictBlocks.Count) downto 0 do
|
||
|
FDictBlocks.Delete(Inx);
|
||
|
|
||
|
if FFileInfo <> nil then begin
|
||
|
{ Close the file. }
|
||
|
FBufMgr.RemoveFile(FFileInfo);
|
||
|
FFCloseFilePrim(FFileInfo);
|
||
|
FFFreeFileInfo(FFileInfo);
|
||
|
FFileInfo := nil;
|
||
|
end;
|
||
|
|
||
|
end;
|
||
|
{--------}
|
||
|
function Tffv2FileInterface.GetBlock(const BlockNumber : Longint) : ICommonBlock;
|
||
|
var
|
||
|
Block : ICommonBlock;
|
||
|
begin
|
||
|
Block := TffFileBlock.Create(FBufMgr, FFileInfo, FTI, BlockNumber);
|
||
|
case Block.BlockType of
|
||
|
btUnknown : Result := Tffv2FileBlock.Create(FBufMgr, FFileInfo, FTI, BlockNumber);
|
||
|
btFileHeader : Result := TffFileHeaderBlock.Create(FBufMgr, FFileInfo, FTI, BlockNumber);
|
||
|
btIndexHeader : Result := TffIndexHeaderBlock.Create(FBufMgr, FFileInfo, FTI, BlockNumber);
|
||
|
btData : Result := TffDataBlock.Create(FBufMgr, FFileInfo, FTI, BlockNumber);
|
||
|
btIndex : Result := TffIndexBlock.Create(FBufMgr, FFileInfo, FTI, BlockNumber);
|
||
|
btBLOB : Result := TffBLOBBlock.Create(FBufMgr, FFileInfo, FTI, BlockNumber);
|
||
|
btStream : Result := TffStreamBlock.Create(FBufMgr, FFileInfo, FTI, BlockNumber);
|
||
|
btFree : Result := Block;
|
||
|
end; { case }
|
||
|
Result.OnReportError := FOnReportError;
|
||
|
Result.OnReportFix := FOnReportFix;
|
||
|
end;
|
||
|
{--------}
|
||
|
function Tffv2FileInterface.GetDictBlockCount : Longint;
|
||
|
begin
|
||
|
Result := FDictBlocks.Count;
|
||
|
end;
|
||
|
{--------}
|
||
|
function Tffv2FileInterface.GetDictBlocks(const Inx : Longint) : IStreamBlock;
|
||
|
begin
|
||
|
Result := IStreamBlock(FDictBlocks[Inx]);
|
||
|
Result.OnReportError := FOnReportError;
|
||
|
Result.OnReportFix := FOnReportFix;
|
||
|
end;
|
||
|
{--------}
|
||
|
function Tffv2FileInterface.GetFileHeaderBlock : IFileHeaderBlock;
|
||
|
begin
|
||
|
Result := FFileHeaderBlock;
|
||
|
if Result <> nil then begin
|
||
|
Result.OnReportError := FOnReportError;
|
||
|
Result.OnReportFix := FOnReportFix;
|
||
|
end;
|
||
|
end;
|
||
|
{--------}
|
||
|
function Tffv2FileInterface.GetFileInfo : TffGeneralFileInfo;
|
||
|
begin
|
||
|
{ TODO:: File must be opened. }
|
||
|
Result := TffGeneralFileInfo.Create(FDict, FFileHeaderBlock);
|
||
|
end;
|
||
|
{--------}
|
||
|
function Tffv2FileInterface.GetIndexHeaderBlock : IIndexHeaderBlock;
|
||
|
begin
|
||
|
Result := FIndexHeaderBlock;
|
||
|
if Result <> nil then begin
|
||
|
Result.OnReportError := FOnReportError;
|
||
|
Result.OnReportFix := FOnReportFix;
|
||
|
end;
|
||
|
end;
|
||
|
{--------}
|
||
|
function Tffv2FileInterface.GetOnReportError : TffReportErrorEvent;
|
||
|
begin
|
||
|
Result := FOnReportError;
|
||
|
end;
|
||
|
{--------}
|
||
|
function Tffv2FileInterface.GetOnReportFix : TffReportFixEvent;
|
||
|
begin
|
||
|
Result := FOnReportFix;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileInterface.Open(const Filename : string);
|
||
|
var
|
||
|
FileBlock : PffBlock;
|
||
|
Block : ICommonBlock;
|
||
|
DictBlock : IStreamBlock;
|
||
|
FileVersion : Longint;
|
||
|
RelMethod : TffReleaseMethod;
|
||
|
begin
|
||
|
CloseCurrentFile;
|
||
|
|
||
|
{ Set up the info for the file. }
|
||
|
FFileInfo := FFAllocFileInfo(FileName, ffc_extForData, FBufMgr);
|
||
|
|
||
|
{ Open the file. }
|
||
|
FFOpenFile(FFileInfo, omReadWrite, smExclusive, False, False);
|
||
|
|
||
|
{ Read the header record to see if this is a FF data file supported by this
|
||
|
interface. First, add the file to the buffer manager. }
|
||
|
FileBlock := FBufMgr.AddFile(FFileInfo, FTI, False, RelMethod);
|
||
|
try
|
||
|
{ Get the header block. }
|
||
|
FFileHeaderBlock := TffFileHeaderBlock.Create(FBufMgr, FFileInfo, FTI, 0);
|
||
|
try
|
||
|
if ICommonBlock(FFileHeaderBlock).BlockType <> btFileHeader then
|
||
|
raise Exception.CreateFmt('"%s" is not a FlashFiler table.', [FileName])
|
||
|
else begin
|
||
|
{ Get the version. }
|
||
|
FileVersion := FFileHeaderBlock.FFVersion;
|
||
|
{ Does this interface handle the version? }
|
||
|
if (FileVersion < FFStartVersion) or (FileVersion > FFEndVersion) then
|
||
|
raise Exception.CreateFmt
|
||
|
('Table "%s" was created with version %s ' +
|
||
|
'of FlashFiler but this interface only supports versions ' +
|
||
|
'%s through %s',
|
||
|
[FileName, VersionToStr(FileVersion),
|
||
|
VersionToStr(FFStartVersion),
|
||
|
VersionToStr(FFEndVersion)]);
|
||
|
|
||
|
{ Get the data dictionary blocks. }
|
||
|
Block := GetBlock(FFileHeaderBlock.DataDictBlockNum);
|
||
|
if Supports(Block, IStreamBlock, DictBlock) then
|
||
|
FDictBlocks.Add(DictBlock)
|
||
|
else
|
||
|
raise Exception.CreateFmt('Block %d is not a dictionary block as expected.',
|
||
|
[FFileHeaderBlock.DataDictBlockNum]);
|
||
|
while DictBlock.NextBlock <> ffc_W32NoValue do begin
|
||
|
Block := GetBlock(DictBlock.NextBlock);
|
||
|
if Supports(Block, IStreamBlock, DictBlock) then
|
||
|
FDictBlocks.Add(DictBlock)
|
||
|
else
|
||
|
raise Exception.CreateFmt('Block %d is not a dictionary block as expected.',
|
||
|
[DictBlock.NextBlock]);
|
||
|
end; { while }
|
||
|
|
||
|
{ Get the index header block. }
|
||
|
FIndexHeaderBlock := TffIndexHeaderBlock.Create
|
||
|
(FBufMgr, FFileInfo, FTI,
|
||
|
FFileHeaderBlock.IndexHeaderBlockNum);
|
||
|
|
||
|
{ Read the dictionary. }
|
||
|
FDict := TffServerDataDict.Create(4096);
|
||
|
FDict.ReadFromFile(FFileInfo, FTI);
|
||
|
end; { if }
|
||
|
except
|
||
|
CloseCurrentFile;
|
||
|
raise;
|
||
|
end;
|
||
|
finally
|
||
|
RelMethod(FileBlock);
|
||
|
end;
|
||
|
end;
|
||
|
{--------}
|
||
|
type
|
||
|
SrDBCracker = class(TffSrDatabase);
|
||
|
{--------}
|
||
|
procedure Tffv2FileInterface.Pack;
|
||
|
const
|
||
|
ciTimeout = 10000;
|
||
|
var
|
||
|
FileName,
|
||
|
FileDir : string;
|
||
|
Engine : TffServerEngine;
|
||
|
RebuildID,
|
||
|
MaxPos : Integer;
|
||
|
ClientID : TffClientID;
|
||
|
SessionID : TffSessionID;
|
||
|
DatabaseID : TffDatabaseID;
|
||
|
Result : TffResult;
|
||
|
PwdHash : TffWord32;
|
||
|
TableName : string;
|
||
|
IsPresent : Boolean;
|
||
|
Status : TffRebuildStatus;
|
||
|
E : EffDatabaseError;
|
||
|
begin
|
||
|
{ Get location & name of table. }
|
||
|
FileName := FFileInfo^.fiName^;
|
||
|
FileDir := ExtractFilePath(FileName);
|
||
|
TableName := ChangeFileExt(ExtractFileName(FileName), '');
|
||
|
|
||
|
{ Close the file. }
|
||
|
CloseCurrentFile;
|
||
|
|
||
|
{ Future:: Backup location specified? }
|
||
|
|
||
|
{ Init client, session, database IDs. }
|
||
|
ClientID := ffc_W32NoValue;
|
||
|
SessionID := ffc_W32NoValue;
|
||
|
DatabaseID := ffc_W32NoValue;
|
||
|
|
||
|
{ Pack the table via a temporary embedded server engine. }
|
||
|
Engine := TffServerEngine.Create(nil);
|
||
|
try
|
||
|
try
|
||
|
{ Initialize. }
|
||
|
Engine.Startup;
|
||
|
Engine.IsReadOnly := True;
|
||
|
Engine.MaxRAM := 50;
|
||
|
|
||
|
{ Obtain a client connection. }
|
||
|
Result := Engine.ClientAdd(ClientID, '', '', ciTimeout, PwdHash);
|
||
|
if Result <> DBIERR_NONE then
|
||
|
raise Exception.CreateFmt('Pack error: Could not add client, error code %d',
|
||
|
[Result]);
|
||
|
|
||
|
{ Open a session. }
|
||
|
Result := Engine.SessionAdd(ClientID, ciTimeout, SessionID);
|
||
|
if Result <> DBIERR_NONE then
|
||
|
raise Exception.CreateFmt('Pack error: Could not add session, error code %d',
|
||
|
[Result]);
|
||
|
|
||
|
{ Add an alias for the current directory. }
|
||
|
Result := Engine.DatabaseAddAlias(csAlias, FileDir, False, ClientID);
|
||
|
if Result <> DBIERR_NONE then
|
||
|
raise Exception.CreateFmt('Pack error: Could not add alias, error code %d',
|
||
|
[Result]);
|
||
|
|
||
|
{ Open a database. }
|
||
|
Result := Engine.DatabaseOpen(ClientID, csAlias, omReadWrite, smShared,
|
||
|
ciTimeout, DatabaseID);
|
||
|
if Result <> DBIERR_NONE then
|
||
|
raise Exception.CreateFmt('Pack error: Could not add session, error code %d',
|
||
|
[Result]);
|
||
|
|
||
|
{ Set the output version for the new table. }
|
||
|
SrDBCracker(DatabaseID).dbSetNewTableVersion(FOutputVersion);
|
||
|
|
||
|
{ Calculate Max position for progress. }
|
||
|
MaxPos := 100;
|
||
|
|
||
|
{ Start the pack. This is asynchronous so wait for the pack to finish. }
|
||
|
Engine.IsReadOnly := False;
|
||
|
Result := Engine.TablePack(DatabaseID, TableName, RebuildID);
|
||
|
if Result <> DBIERR_NONE then
|
||
|
raise Exception.CreateFmt('Pack error: Could not initiate pack, ' +
|
||
|
'error code %d', [Result]);
|
||
|
|
||
|
repeat
|
||
|
Sleep(100);
|
||
|
Result := Engine.RebuildGetStatus(RebuildID, ClientID, IsPresent, Status);
|
||
|
if Assigned(FRebuildProgress) then
|
||
|
FRebuildProgress(Self, Status.rsPercentDone, MaxPos);
|
||
|
until (Result = DBIERR_OBJNOTFOUND) or Status.rsFinished;
|
||
|
|
||
|
if (Status.rsErrorCode <> DBIERR_NONE) and
|
||
|
Assigned(FOnReportError) then begin
|
||
|
E := EffDatabaseError.CreateViaCode(Status.rsErrorCode, False);
|
||
|
try
|
||
|
FOnReportError(nil, rciPackFailure,
|
||
|
Format(rcErrStr[rciPackFailure],
|
||
|
[E.Message]));
|
||
|
finally
|
||
|
E.Free;
|
||
|
end;
|
||
|
end; { if }
|
||
|
except
|
||
|
on E:Exception do begin
|
||
|
FOnReportError(nil, rciPackFailure,
|
||
|
Format(rcErrStr[rciPackFailure],
|
||
|
[E.Message]));
|
||
|
end;
|
||
|
end;
|
||
|
finally
|
||
|
if DatabaseID <> ffc_W32NoValue then
|
||
|
Engine.DatabaseClose(DatabaseID);
|
||
|
|
||
|
if SessionID <> ffc_W32NoValue then
|
||
|
Engine.SessionRemove(ClientID, SessionID);
|
||
|
|
||
|
if ClientID <> ffc_W32NoValue then
|
||
|
Engine.ClientRemove(ClientID);
|
||
|
|
||
|
Engine.Free;
|
||
|
|
||
|
{ Re-open the file. }
|
||
|
Open(FileName);
|
||
|
end;
|
||
|
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileInterface.SetOnReportError(Value : TffReportErrorEvent);
|
||
|
begin
|
||
|
FOnReportError := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileInterface.SetOnReportFix(Value : TffReportFixEvent);
|
||
|
begin
|
||
|
FOnReportFix := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileInterface.SetOutputVersion(const Value : Longint);
|
||
|
begin
|
||
|
{ Validate the version. }
|
||
|
if (Value >= ffVersion2_10) and (Value <= ffVersionNumber) then
|
||
|
FOutputVersion := Value
|
||
|
else
|
||
|
raise Exception.Create(Format('The output version must be >= %d and <= %d',
|
||
|
[ffVersion2_10, ffVersionNumber]));
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
|
||
|
{===Tffv2FileBlock===================================================}
|
||
|
constructor Tffv2FileBlock.Create(BufMgr : TffBufferManager;
|
||
|
FileInfo : PffFileInfo;
|
||
|
TI : PffTransInfo;
|
||
|
const BlockNum : TffWord32);
|
||
|
begin
|
||
|
inherited;
|
||
|
FIsModified := False;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileBlock.BeginUpdate;
|
||
|
begin
|
||
|
if not FIsModified then begin
|
||
|
{ We need to change the block. Release the read-only copy & grab a modifiable
|
||
|
copy. }
|
||
|
FRelMethod(FBlock);
|
||
|
FBufMgr.StartTransaction(FTI.tirTrans, False, '');
|
||
|
FBlock := FBufMgr.GetBlock(FFileInfo, FBlockNum, FTI, ffc_MarkDirty,
|
||
|
FRelMethod);
|
||
|
FIsModified := True;
|
||
|
end;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure Tffv2FileBlock.EndUpdate;
|
||
|
begin
|
||
|
{ Release the dirty copy, commit the change, & get a read-only copy. }
|
||
|
FRelMethod(FBlock);
|
||
|
FBufMgr.CommitTransaction(FTI.tirTrans);
|
||
|
FBlock := FBufMgr.GetBlock(FFileInfo, FBlockNum, FTI, ffc_ReadOnly,
|
||
|
FRelMethod);
|
||
|
FIsModified := False;
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
|
||
|
{===TffFileHeaderBlock===============================================}
|
||
|
function TffFileHeaderBlock.GetAvailBlocks : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfAvailBlocks;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetBLOBCount : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfBLOBCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetBlockSize : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfBlockSize;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetDataDictBlockNum : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfDataDict;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetDeletedBLOBHead : TffInt64;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfDelBLOBHead;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetDeletedBLOBTail : TffInt64;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfDelBLOBTail;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetDeletedRecordCount : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfDelRecCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetEncrypted : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfEncrypted;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetEstimatedUsedBlocks : TffWord32;
|
||
|
var
|
||
|
CalcInt64Value : TffInt64;
|
||
|
begin
|
||
|
ffI64DivInt(FFGetFileSize(FFileInfo), BlockSize, CalcInt64Value);
|
||
|
Result := CalcInt64Value.iLow;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetFFVersion : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfFFVersion;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetFieldCount : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfFieldCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetFirstDataBlock : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhf1stDataBlock;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetFirstDeletedRecord : TffInt64;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhf1stDelRec;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetFirstFreeBlock : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhf1stFreeBlock;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetHasSequentialIndex : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfHasSeqIndex;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetIndexCount : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfIndexCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetIndexHeaderBlockNum : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfIndexHeader;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetLastAutoIncValue : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfAutoIncValue;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetLastDataBlock : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfLastDataBlock;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetLog2BlockSize : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfLog2BlockSize;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetRecLenPlusTrailer : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfRecLenPlusTrailer;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetRecordCount : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfRecordCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetRecordLength : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfRecordLength;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetRecordsPerBlock : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfRecsPerBlock;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetUsedBlocks : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderFile(FBlock)^.bhfUsedBlocks;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetPropertyCell(const Row, Column : Integer) : string;
|
||
|
begin
|
||
|
if Column > Pred(ciFileBlockColumns) then
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in column %d when there are only %d columns in the view',
|
||
|
[Column, ciFileBlockColumns]);
|
||
|
|
||
|
{ Does this cell come from the common block view? }
|
||
|
if Row < ciFileBlockRows then
|
||
|
Result := inherited GetPropertyCell(Row, Column)
|
||
|
else
|
||
|
case Row of
|
||
|
5 : if Column = 0 then
|
||
|
Result := 'Block size'
|
||
|
else
|
||
|
Result := IntToStr(GetBlockSize);
|
||
|
6 : if Column = 0 then
|
||
|
Result := 'Encrypted?'
|
||
|
else
|
||
|
Result := YesNoValue(GetEncrypted);
|
||
|
7 : if Column = 0 then
|
||
|
Result := 'Log 2 block size'
|
||
|
else
|
||
|
Result := IntToStr(GetLog2BlockSize);
|
||
|
8 : if Column = 0 then
|
||
|
Result := 'Used blocks'
|
||
|
else
|
||
|
Result := IntToStr(GetUsedBlocks);
|
||
|
9 : if Column = 0 then
|
||
|
Result := 'Available blocks'
|
||
|
else
|
||
|
Result := IntToStr(GetAvailBlocks);
|
||
|
10: if Column = 0 then
|
||
|
Result := '1st free block'
|
||
|
else
|
||
|
Result := IntToStr(GetFirstFreeBlock);
|
||
|
11: if Column = 0 then
|
||
|
Result := 'Record count'
|
||
|
else
|
||
|
Result := IntToStr(GetRecordCount);
|
||
|
12: if Column = 0 then
|
||
|
Result := 'Deleted record count'
|
||
|
else
|
||
|
Result := IntToStr(GetDeletedRecordCount);
|
||
|
13: if Column = 0 then
|
||
|
Result := '1st deleted record'
|
||
|
else
|
||
|
Result := Int64ToStr(GetFirstDeletedRecord);
|
||
|
14: if Column = 0 then
|
||
|
Result := 'Record length'
|
||
|
else
|
||
|
Result := IntToStr(GetRecordLength);
|
||
|
15: if Column = 0 then
|
||
|
Result := 'Record length plus trailer'
|
||
|
else
|
||
|
Result := IntToStr(GetRecLenPlusTrailer);
|
||
|
16: if Column = 0 then
|
||
|
Result := 'Records per block'
|
||
|
else
|
||
|
Result := IntToStr(GetRecordsPerBlock);
|
||
|
17: if Column = 0 then
|
||
|
Result := '1st data block'
|
||
|
else
|
||
|
Result := IntToStr(GetFirstDataBlock);
|
||
|
18: if Column = 0 then
|
||
|
Result := 'Last data block'
|
||
|
else
|
||
|
Result := IntToStr(GetLastDataBlock);
|
||
|
19: if Column = 0 then
|
||
|
Result := 'BLOB count'
|
||
|
else
|
||
|
Result := IntToStr(GetBLOBCount);
|
||
|
20: if Column = 0 then
|
||
|
Result := 'Deleted BLOB head'
|
||
|
else
|
||
|
Result := Int64ToStr(GetDeletedBLOBHead);
|
||
|
21: if Column = 0 then
|
||
|
Result := 'Deleted BLOB tail'
|
||
|
else
|
||
|
Result := Int64ToStr(GetDeletedBLOBTail);
|
||
|
22: if Column = 0 then
|
||
|
Result := 'Last autoinc value'
|
||
|
else
|
||
|
Result := IntToStr(GetLastAutoIncValue);
|
||
|
23: if Column = 0 then
|
||
|
Result := 'Index count'
|
||
|
else
|
||
|
Result := IntToStr(GetIndexCount);
|
||
|
24: if Column = 0 then
|
||
|
Result := 'Sequential index?'
|
||
|
else
|
||
|
Result := YesNoValue(GetHasSequentialIndex);
|
||
|
25: if Column = 0 then
|
||
|
Result := 'Index header block number'
|
||
|
else
|
||
|
Result := IntToStr(GetIndexHeaderBlockNum);
|
||
|
26: if Column = 0 then
|
||
|
Result := 'Field count'
|
||
|
else
|
||
|
Result := IntToStr(GetFieldCount);
|
||
|
27: if Column = 0 then
|
||
|
Result := 'Data dictionary block number'
|
||
|
else
|
||
|
Result := IntToStr(GetDataDictBlockNum);
|
||
|
28: if Column = 0 then
|
||
|
Result := 'FF version'
|
||
|
else
|
||
|
Result := VersionToStr(GetFFVersion);
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in row %d when there are only %d rows in the view',
|
||
|
[Row, ciFileBlockRows + ciFileHeaderRows]);
|
||
|
end; { case }
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffFileHeaderBlock.GetPropertyRowCount : Integer;
|
||
|
begin
|
||
|
Result := ciFileBlockRows + ciFileHeaderRows;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffFileHeaderBlock.SetFirstDataBlock(const Value : TffWord32);
|
||
|
begin
|
||
|
PffBlockHeaderFile(FBlock)^.bhf1stDataBlock := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffFileHeaderBlock.SetFirstFreeBlock(const Value : TffWord32);
|
||
|
begin
|
||
|
PffBlockHeaderFile(FBlock)^.bhf1stFreeBlock := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffFileHeaderBlock.SetHasSequentialIndex(const Value : Longint);
|
||
|
begin
|
||
|
PffBlockHeaderFile(FBlock)^.bhfHasSeqIndex := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffFileHeaderBlock.SetLastDataBlock(const Value : TffWord32);
|
||
|
begin
|
||
|
PFfBlockHeaderFile(FBlock)^.bhfLastDataBlock := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffFileHeaderBlock.SetLog2BlockSize(const Value : TffWord32);
|
||
|
begin
|
||
|
PFfBlockHeaderFile(FBlock)^.bhfLog2BlockSize := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffFileHeaderBlock.SetUsedBlocks(const Value : TffWord32);
|
||
|
begin
|
||
|
PFfBlockHeaderFile(FBlock)^.bhfUsedBlocks := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffFileHeaderBlock.VerifyRepair(const Repair : Boolean);
|
||
|
var
|
||
|
Block : PffBlock;
|
||
|
RelMethod : TffReleaseMethod;
|
||
|
BlockSizeValid : Boolean;
|
||
|
Log2BlockSizeValid : Boolean;
|
||
|
CalcValue : TffWord32;
|
||
|
Modified : Boolean;
|
||
|
begin
|
||
|
inherited;
|
||
|
Modified := False;
|
||
|
Log2BlockSizeValid := False;
|
||
|
try
|
||
|
|
||
|
{ TODO: AvailBlocks will be checked by repair logic once the number of deleted
|
||
|
blocks has been determined. }
|
||
|
|
||
|
{ BLOBCount is not verified at this time. }
|
||
|
|
||
|
{ Verify block size is one of the accepted values. }
|
||
|
BlockSizeValid := ((BlockSize = 4096) or
|
||
|
(BlockSize = 8192) or
|
||
|
(BlockSize = 16384) or
|
||
|
(BlockSize = 32768) or
|
||
|
(BlockSize = 65536));
|
||
|
if not BlockSizeValid then
|
||
|
DoReportError(rciInvalidBlockSize, [BlockSize]);
|
||
|
{ Future: Implement logic that tests for block size, perhaps by looking for
|
||
|
valid signatures at specific block boundaries, & self repairs
|
||
|
block size. }
|
||
|
|
||
|
{ Verify log2 block size. }
|
||
|
if BlockSizeValid then begin
|
||
|
CalcValue := FFCalcLog2BlockSize(BlockSize);
|
||
|
Log2BlockSizeValid := (Log2BlockSize = CalcValue);
|
||
|
if not Log2BlockSizeValid then begin
|
||
|
DoReportError(rciInvalidLog2BlockSize,
|
||
|
[BlockSize, CalcValue, Log2BlockSize]);
|
||
|
if Repair then begin
|
||
|
BeginUpdate;
|
||
|
Modified := True;
|
||
|
Log2BlockSize := CalcValue;
|
||
|
DoReportFix(rciInvalidLog2BlockSize, [CalcValue]);
|
||
|
end; { if }
|
||
|
end; { if }
|
||
|
end; { if }
|
||
|
|
||
|
{ Verify the reference to the data dictionary block. }
|
||
|
if DataDictBlockNum <> ffc_W32NoValue then
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, DataDictBlockNum, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
{ Is it a stream block? }
|
||
|
if (PffBlockHeaderStream(Block)^.bhsSignature <> ffc_SigStreamBlock) or
|
||
|
(PffBlockHeaderStream(Block)^.bhsStreamType <> ffc_SigDictStream) then
|
||
|
DoReportError(rciInvalidBlockRefDict, [DataDictBlockNum]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInvalidBlockRefDict, [DataDictBlockNum]);
|
||
|
end
|
||
|
else
|
||
|
DoReportError(rciNoDictBlock, [DataDictBlockNum]);
|
||
|
|
||
|
{ Is the deleted BLOB head valid?
|
||
|
Future: Determine if it is a BLOB segment. }
|
||
|
if (DeletedBLOBHead.iLow <> ffc_W32NoValue) and
|
||
|
Log2BlockSizeValid and
|
||
|
(not FFVerifyBLOBNr(DeletedBLOBHead, Log2BlockSize)) then
|
||
|
DoReportError(rciInvalidInt64, ['Deleted BLOB head', DeletedBLOBHead.iHigh,
|
||
|
DeletedBLOBHead.iLow]);
|
||
|
|
||
|
{ Is the deleted BLOB tail valid?
|
||
|
Future: Determine if it is a BLOB segment. }
|
||
|
if (DeletedBLOBTail.iLow <> ffc_W32NoValue) and
|
||
|
Log2BlockSizeValid and
|
||
|
(not FFVerifyBLOBNr(DeletedBLOBTail, Log2BlockSize)) then
|
||
|
DoReportError(rciInvalidInt64, ['Deleted BLOB tail', DeletedBLOBTail.iHigh,
|
||
|
DeletedBLOBTail.iLow]);
|
||
|
|
||
|
|
||
|
{ Future: Verify deleted record count. }
|
||
|
|
||
|
{ Future: Verify encrypted flag. }
|
||
|
|
||
|
{ Future: Verify FF version. }
|
||
|
|
||
|
{ Future: Verify field count. }
|
||
|
|
||
|
{ Is FirstDataBlock valid? }
|
||
|
if FirstDataBlock <> ffc_W32NoValue then
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, FirstDataBlock, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
{ Is it a data block? }
|
||
|
if (PffBlockHeaderData(Block)^.bhdsignature <> ffc_SigDataBlock) then
|
||
|
DoReportError(rciInvalidBlockRefFirstData, [FirstDataBlock]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInvalidBlockRefFirstData, [FirstDataBlock]);
|
||
|
end
|
||
|
else if RecordCount > 0 then
|
||
|
DoReportError(rciNoDataBlockForRecs, [RecordCount]);
|
||
|
|
||
|
{ Verify ref to 1st deleted record.
|
||
|
Future: Determine if it really is a deleted record. }
|
||
|
if (DeletedRecordCount = 0) then begin
|
||
|
if FirstDeletedRecord.iLow <> ffc_W32NoValue then
|
||
|
DoReportError(rciInvalidInt64, ['First Deleted Record',
|
||
|
FirstDeletedRecord.iHigh,
|
||
|
FirstDeletedRecord.iLow]);
|
||
|
end
|
||
|
else if Log2BlockSizeValid and
|
||
|
(not FFVerifyRefNr(FirstDeletedRecord, Log2BlockSize,
|
||
|
RecordLengthPlusTrailer)) then
|
||
|
DoReportError(rciInvalidInt64, ['First Deleted Record',
|
||
|
FirstDeletedRecord.iHigh,
|
||
|
FirstDeletedRecord.iLow]);
|
||
|
|
||
|
{ Verify ref to first free block. }
|
||
|
if FirstFreeBlock <> ffc_W32NoValue then
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, FirstFreeBlock, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
{ Is it a free block? }
|
||
|
if (PffBlockCommonHeader(Block)^.bchsignature <> ffc_SigFreeBlock) then
|
||
|
DoReportError(rciInvalidBlockRefFirstFree, [FirstFreeBlock]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInvalidBlockRefFirstFree, [FirstFreeBlock]);
|
||
|
end;
|
||
|
|
||
|
{ For FF 2.x, each table should have a sequential index. }
|
||
|
if HasSequentialIndex <> 1 then begin
|
||
|
DoReportError(rciInvalidSeqIndexFlag, [HasSequentialIndex]);
|
||
|
if Repair then begin
|
||
|
BeginUpdate;
|
||
|
Modified := True;
|
||
|
HasSequentialIndex := 1;
|
||
|
DoReportFix(rciInvalidSeqIndexFlag, [1]);
|
||
|
end; { if }
|
||
|
end; { if }
|
||
|
|
||
|
{ Future: Does the index count match the dictionary. }
|
||
|
|
||
|
{ Verify ref to index header. }
|
||
|
if IndexHeaderBlockNum <> ffc_W32NoValue then
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, IndexHeaderBlockNum, FTI,
|
||
|
ffc_ReadOnly, RelMethod);
|
||
|
try
|
||
|
{ Is it an index block & is its block type set to zero indicating
|
||
|
a header block? }
|
||
|
if (PffBlockHeaderIndex(Block)^.bhisignature <> ffc_SigIndexBlock) or
|
||
|
(PffBlockHeaderIndex(Block)^.bhiBlockType <> 0) then
|
||
|
DoReportError(rciInvalidBlockRefIndexHead, [IndexHeaderBlockNum]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInvalidBlockRefIndexHead, [IndexHeaderBlockNum]);
|
||
|
end;
|
||
|
|
||
|
{ Future: Verify last autoinc value. }
|
||
|
|
||
|
{ Verify ref to last data block. }
|
||
|
if LastDataBlock <> ffc_W32NoValue then
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, LastDataBlock, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
{ Is it a data block? }
|
||
|
if (PffBlockHeaderData(Block)^.bhdsignature <> ffc_SigDataBlock) then
|
||
|
DoReportError(rciInvalidBlockRefLastData, [LastDataBlock]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInvalidBlockRefLastData, [LastDataBlock]);
|
||
|
end
|
||
|
else if RecordCount > 0 then
|
||
|
DoReportError(rciNoLastDataBlockForRecs, [RecordCount]);
|
||
|
|
||
|
{ Future: Verify record length plus trailer. }
|
||
|
|
||
|
{ Future: Verify record count. }
|
||
|
|
||
|
{ Future: Verify record length. }
|
||
|
{ TODO:: Can now get this information from the data dictionary. ]
|
||
|
|
||
|
{ Future: Verify records per block. }
|
||
|
|
||
|
{ Verify that used blocks matches the size of the file. }
|
||
|
if BlockSizeValid then begin
|
||
|
CalcValue := EstimatedUsedBlocks;
|
||
|
if CalcValue <> UsedBlocks then begin
|
||
|
DoReportError(rciInvalidUsedBlocks, [CalcValue, UsedBlocks]);
|
||
|
if Repair then begin
|
||
|
BeginUpdate;
|
||
|
Modified := True;
|
||
|
UsedBlocks := CalcValue;
|
||
|
DoReportFix(rciInvalidUsedBlocks, [CalcValue]);
|
||
|
end; { if }
|
||
|
end; { if }
|
||
|
end; { if }
|
||
|
finally
|
||
|
if Modified then
|
||
|
EndUpdate;
|
||
|
end;
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
|
||
|
{====================================================================}
|
||
|
function TffStreamBlock.GetNextStrmBlock : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderStream(FBlock)^.bhsNextStrmBlock;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffStreamBlock.GetStreamType : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderStream(FBlock)^.bhsStreamType;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffStreamBlock.GetStreamLength : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderStream(FBlock)^.bhsStreamLength;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffStreamBlock.GetOwningStream : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderStream(FBlock)^.bhsOwningStream;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffStreamBlock.GetPropertyCell(const Row, Column : Integer) : string;
|
||
|
begin
|
||
|
if Column > Pred(ciFileBlockColumns) then
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in column %d when there are only %d columns in the view',
|
||
|
[Column, ciFileBlockColumns]);
|
||
|
|
||
|
{ Does this cell come from the common block view? }
|
||
|
if Row < ciFileBlockRows then
|
||
|
Result := inherited GetPropertyCell(Row, Column)
|
||
|
else
|
||
|
case Row of
|
||
|
5 : if Column = 0 then
|
||
|
Result := 'Next stream block'
|
||
|
else
|
||
|
Result := IntToStr(GetNextStrmBlock);
|
||
|
6 : if Column = 0 then
|
||
|
Result := 'Stream type'
|
||
|
else
|
||
|
Result := MapSigToStr(GetStreamType);
|
||
|
7 : if Column = 0 then
|
||
|
Result := 'Stream length'
|
||
|
else
|
||
|
Result := IntToStr(GetStreamLength);
|
||
|
8 : if Column = 0 then
|
||
|
Result := 'Owning stream'
|
||
|
else
|
||
|
Result := IntToStr(GetOwningStream);
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in row %d when there are only %d rows in the view',
|
||
|
[Row, ciFileBlockRows + ciStreamRows]);
|
||
|
end; { case }
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffStreamBlock.GetPropertyRowCount : Integer;
|
||
|
begin
|
||
|
Result := ciFileBlockRows + ciStreamRows;
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
|
||
|
{===TffIndexBlock====================================================}
|
||
|
function TffIndexBlock.GetIndexBlockType : Byte;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiBlockType;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetIsLeafPage : Boolean;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiIsLeafPage;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetNodeLevel : Byte;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiNodeLevel;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetKeysAreRefs : Boolean;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiKeysAreRefs;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetIndexNum : Word;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiIndexNum;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetKeyLength : Word;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiKeyLength;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetKeyCount : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiKeyCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetMaxKeyCount : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiMaxKeyCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetPrevPageRef : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderIndex(FBlock)^.bhiPrevPageRef;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetPropertyCell(const Row, Column : Integer) : string;
|
||
|
begin
|
||
|
if Column > Pred(ciFileBlockColumns) then
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in column %d when there are only %d columns in the view',
|
||
|
[Column, ciFileBlockColumns]);
|
||
|
|
||
|
{ Does this cell come from the common block view? }
|
||
|
if Row < ciFileBlockRows then
|
||
|
Result := inherited GetPropertyCell(Row, Column)
|
||
|
else
|
||
|
case Row of
|
||
|
5 : if Column = 0 then
|
||
|
Result := 'Index block type'
|
||
|
else
|
||
|
Result := FlagStr(GetIndexBlockType, 'Header', 'B-Tree page');
|
||
|
6 : if Column = 0 then
|
||
|
Result := 'Is leaf page'
|
||
|
else
|
||
|
Result := BooleanValue('Yes', 'No', GetIsLeafPage);
|
||
|
7 : if Column = 0 then
|
||
|
Result := 'Node level'
|
||
|
else
|
||
|
Result := IntToStr(GetNodeLevel);
|
||
|
8 : if Column = 0 then
|
||
|
Result := 'Keys are refs'
|
||
|
else
|
||
|
Result := BooleanValue('Yes', 'No', GetKeysAreRefs);
|
||
|
9 : if Column = 0 then
|
||
|
Result := 'Index number'
|
||
|
else
|
||
|
Result := IntToStr(GetIndexNum);
|
||
|
10: if Column = 0 then
|
||
|
Result := 'Key length'
|
||
|
else
|
||
|
Result := IntToStr(GetKeyLength);
|
||
|
11: if Column = 0 then
|
||
|
Result := 'Key count'
|
||
|
else
|
||
|
Result := IntToStr(GetKeyCount);
|
||
|
12: if Column = 0 then
|
||
|
Result := 'Max key count'
|
||
|
else
|
||
|
Result := IntToStr(GetMaxKeyCount);
|
||
|
13: if Column = 0 then
|
||
|
Result := 'Previous page reference'
|
||
|
else
|
||
|
Result := IntToStr(GetPrevPageRef);
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in row %d when there are only %d rows in the view',
|
||
|
[Row, ciFileBlockRows + ciIndexBlockRows]);
|
||
|
end; { case }
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexBlock.GetPropertyRowCount : Integer;
|
||
|
begin
|
||
|
Result := ciFileBlockRows + ciIndexBlockRows;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffIndexBlock.VerifyRepair(const Repair : Boolean);
|
||
|
var
|
||
|
Inx : Integer;
|
||
|
InxBlockNum,
|
||
|
DataBlockNum : TffWord32;
|
||
|
RefNum, TempI64 : TffInt64;
|
||
|
PageNumBlock : PPageNumBlock;
|
||
|
Modified : Boolean;
|
||
|
Block : PffBlock;
|
||
|
RelMethod : TffReleaseMethod;
|
||
|
DataRefBlock : PRefBlock;
|
||
|
ValidStr : string;
|
||
|
Info : TffGeneralFileInfo;
|
||
|
begin
|
||
|
inherited;
|
||
|
Modified := False;
|
||
|
try
|
||
|
|
||
|
{ Get the previous page & verify it is an index block. }
|
||
|
if PrevPageRef <> ffc_W32NoValue then
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, PrevPageRef, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
if PffBlockHeaderIndex(Block)^.bhiSignature <> ffc_SigIndexBlock then
|
||
|
DoReportError(rciInvalidInxPrefPageRef, [PrevPageRef]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInvalidInxPrefPageRef, [PrevPageRef]);
|
||
|
end;
|
||
|
|
||
|
{ Get the general file info. }
|
||
|
if Assigned(FOnGetInfo) then
|
||
|
FOnGetInfo(Info)
|
||
|
else
|
||
|
raise Exception.Create('File interface must provide OnGetInfo handler.');
|
||
|
|
||
|
{ Is this a leaf page? }
|
||
|
if IsLeafPage then begin
|
||
|
{ Yes. Verify that all reference numbers point to data pages & to
|
||
|
valid records. }
|
||
|
DataRefBlock := PRefBlock(@FBlock^[ffc_BlockHeaderSizeIndex]);
|
||
|
|
||
|
{ Loop through the existing keys. }
|
||
|
for Inx := 0 to pred(KeyCount) do begin
|
||
|
{ Get the block number. }
|
||
|
RefNum := DataRefBlock^[Inx];
|
||
|
ffShiftI64R(RefNum, FFileInfo^.fiLog2BlockSize, TempI64);
|
||
|
DataBlockNum := TempI64.iLow;
|
||
|
|
||
|
{ Load the page. Is it a data block? }
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, DataBlockNum, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
if PffBlockHeaderData(Block)^.bhdSignature <> ffc_SigDataBlock then begin
|
||
|
{ It is not a data block. Determine the validity of the key's
|
||
|
ref number. If it is valid then it will point to the
|
||
|
start of a record given the block size & record length. }
|
||
|
if FFVerifyRefNr(RefNum, Info.Log2BlockSize,
|
||
|
Info.RecLenPlusTrailer) then
|
||
|
ValidStr := 'The RefNum is valid.'
|
||
|
else
|
||
|
ValidStr := 'The RefNum is invalid.';
|
||
|
DoReportError(rciInvalidLeafKeyBlockRef,
|
||
|
[Inx, BlockNum, IndexNum, DataBlockNum,
|
||
|
RefNum.iHigh, RefNum.iLow, ValidStr]);
|
||
|
end
|
||
|
else begin
|
||
|
{ It is a data block. Verify the key in the index page points
|
||
|
to a valid record. }
|
||
|
if not FFVerifyRefNr(RefNum, Info.Log2BlockSize,
|
||
|
Info.RecLenPlusTrailer) then
|
||
|
DoReportError(rciInvalidLeafKeyRefNum,
|
||
|
[Inx, BlockNum, IndexNum, DataBlockNum,
|
||
|
RefNum.iHigh, RefNum.iLow]);
|
||
|
end; { if..else }
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
ValidStr := 'The RefNum validity is undetermined.';
|
||
|
DoReportError(rciInvalidLeafKeyBlockRef,
|
||
|
[Inx, BlockNum, IndexNum, DataBlockNum,
|
||
|
RefNum.iHigh, RefNum.iLow, ValidStr]);
|
||
|
end;
|
||
|
end; { for }
|
||
|
end
|
||
|
else begin
|
||
|
{ This is an internal page. Verify the following:
|
||
|
1. The referenced parent page actually exists and is an index block.
|
||
|
2. Each referenced subpage actually exists and is an index block.
|
||
|
|
||
|
First, get a handle on the page numbers and reference numbers.
|
||
|
Page numbers point to an index page (used if the key searched for is
|
||
|
less than the key at this spot).
|
||
|
Reference numbers point to a data page (use if we have found the key
|
||
|
we are searching for in the node page). }
|
||
|
PageNumBlock := PPageNumBlock(@FBlock^[ffc_BlockHeaderSizeIndex]);
|
||
|
DataRefBlock := PRefBlock(@FBlock^[ffc_BlockHeaderSizeIndex +
|
||
|
(MaxKeyCount * SizeOfPageNum)]);
|
||
|
|
||
|
{ Now loop through the existing keys. }
|
||
|
for Inx := 0 to pred(KeyCount) do begin
|
||
|
{ Get the index block number. }
|
||
|
InxBlockNum := PageNumBlock^[Inx];
|
||
|
RefNum := DataRefBlock^[Inx];
|
||
|
try
|
||
|
{ Load the referenced index block. Is it an index page? }
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, InxBlockNum, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
if PffBlockHeaderIndex(Block)^.bhiSignature <> ffc_SigIndexBlock then begin
|
||
|
{ No, it is not an index page. Determine the validity of the
|
||
|
reference number. It is valid if it points to the start of a
|
||
|
record given the block size & record length. }
|
||
|
if FFVerifyRefNr(RefNum, Info.Log2BlockSize,
|
||
|
Info.RecLenPlusTrailer) then
|
||
|
ValidStr := 'The RefNum is valid.'
|
||
|
else
|
||
|
ValidStr := 'The RefNum is invalid.';
|
||
|
DoReportError(rciInvalidIntrnalKeyBlockRef,
|
||
|
[Inx, BlockNum, IndexNum, InxBlockNum,
|
||
|
RefNum.iHigh, RefNum.iLow, ValidStr]);
|
||
|
end
|
||
|
else begin
|
||
|
{ Yes, the target page is an index page. Now verify this key points
|
||
|
to a valid record. }
|
||
|
if not FFVerifyRefNr(RefNum, Info.Log2BlockSize,
|
||
|
Info.RecLenPlusTrailer) then
|
||
|
DoReportError(rciInvalidIntrnalKeyRefNum,
|
||
|
[Inx, BlockNum, IndexNum, InxBlockNum,
|
||
|
RefNum.iHigh, RefNum.iLow]);
|
||
|
end; { if }
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
ValidStr := 'The RefNum validity is undetermined.';
|
||
|
DoReportError(rciInvalidIntrnalKeyBlockRef,
|
||
|
[Inx, BlockNum, IndexNum, InxBlockNum,
|
||
|
RefNum.iHigh, RefNum.iLow, ValidStr]);
|
||
|
end;
|
||
|
end; { for }
|
||
|
end; { if..else }
|
||
|
finally
|
||
|
if Modified then
|
||
|
EndUpdate;
|
||
|
end;
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
|
||
|
{===TffIndexHeaderBlock==============================================}
|
||
|
constructor TffIndexHeaderBlock.Create(BufMgr : TffBufferManager;
|
||
|
FileInfo : PffFileInfo;
|
||
|
TI : PffTransInfo;
|
||
|
const BlockNum : TffWord32);
|
||
|
begin
|
||
|
inherited;
|
||
|
FDataColumns := -1;
|
||
|
FIndexHead := PffIndexHeader(@FBlock^[ffc_BlockHeaderSizeIndex]);
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexHeaderBlock.GetDataCell(const Row, Column : Integer) : string;
|
||
|
begin
|
||
|
if Column > Pred(ciIndexBlockRows) then
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in column %d when there are only %d columns in the view',
|
||
|
[Column, ciFileBlockColumns]);
|
||
|
|
||
|
case Column of
|
||
|
0 : Result := IntToStr(Row + 1);
|
||
|
1 : Result := IntToStr(FIndexHead^.bihIndexKeyLen[Row]);
|
||
|
2 : Result := IntToStr(FIndexHead^.bihIndexKeyCount[Row]);
|
||
|
3 : Result := IntToStr(FIndexHead^.bihIndexRoot[Row]);
|
||
|
4 : Result := IntToStr(FIndexHead^.bihIndexPageCount[Row]);
|
||
|
5 : Result := MapFlagsToStr(FIndexHead^.bihIndexFlags[Row]);
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in row %d when there are only %d rows in the view',
|
||
|
[Row, ffcl_MaxIndexes]);
|
||
|
end; { case }
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexHeaderBlock.GetDataColCaption(const Index : Integer) : string;
|
||
|
begin
|
||
|
case Index of
|
||
|
0 : Result := 'Index';
|
||
|
1 : Result := 'Key length';
|
||
|
2 : Result := '# keys';
|
||
|
3 : Result := 'Root page';
|
||
|
4 : Result := '# pages';
|
||
|
5 : Result := 'Flags';
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for caption %d when there are only %d columns in the view',
|
||
|
[Index, ciIndexHeaderDataColumns]);
|
||
|
end; { case }
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexHeaderBlock.GetDataColCount : Integer;
|
||
|
begin
|
||
|
Result := ciIndexHeaderDataColumns;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexHeaderBlock.GetDataColWidth(const Index : Integer) : Integer;
|
||
|
begin
|
||
|
case Index of
|
||
|
0 : Result := 50;
|
||
|
1 : Result := 65;
|
||
|
2 : Result := 65;
|
||
|
3 : Result := 75;
|
||
|
4 : Result := 65;
|
||
|
5 : Result := 90;
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for width %d when there are only %d columns in the view',
|
||
|
[Index, ciIndexHeaderDataColumns]);
|
||
|
end; { case }
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffIndexHeaderBlock.GetDataRowCount : Integer;
|
||
|
var
|
||
|
Inx : Integer;
|
||
|
begin
|
||
|
if FDataColumns < 0 then begin
|
||
|
FDataColumns := 0;
|
||
|
for Inx := 0 to Pred(ffcl_MaxIndexes) do
|
||
|
if FIndexHead^.bihIndexKeyLen[Inx] > 0 then
|
||
|
inc(FDataColumns)
|
||
|
else
|
||
|
Break;
|
||
|
end; { if }
|
||
|
Result := FDataColumns;
|
||
|
{ Future: Obtain # of indices from dictionary or file header block. }
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffIndexHeaderBlock.VerifyRepair(const Repair : Boolean);
|
||
|
var
|
||
|
Block : PffBlock;
|
||
|
Modified : Boolean;
|
||
|
Row,
|
||
|
Rows : Integer;
|
||
|
Info : TffGeneralFileInfo;
|
||
|
RelMethod : TffReleaseMethod;
|
||
|
begin
|
||
|
{ Verify an OnGetInfo handler has been specified. }
|
||
|
if Assigned(FOnGetInfo) then
|
||
|
FOnGetInfo(Info)
|
||
|
else
|
||
|
raise Exception.Create('File interface must provide OnGetInfo handler.');
|
||
|
|
||
|
Modified := False;
|
||
|
try
|
||
|
{ Get the # of rows in the header. The # of rows should equal the # of
|
||
|
indices defined in the dictionary. }
|
||
|
Rows := GetDataRowCount;
|
||
|
if Rows <> Info.Dict.IndexCount then
|
||
|
DoReportError(rciInxHeaderInvalidRowCount,
|
||
|
[Rows, Info.Dict.IndexCount])
|
||
|
else begin
|
||
|
{ Walk through each row. }
|
||
|
for Row := 0 to Pred(Rows) do begin
|
||
|
{ Verify the index key length. }
|
||
|
if FIndexHead^.bihIndexKeyLen[Row] <> Info.Dict.IndexKeyLength[Row] then
|
||
|
DoReportError(rciInxHeaderInvalidKeyLen,
|
||
|
[Row, FIndexHead^.bihIndexKeyLen[Row],
|
||
|
Info.Dict.IndexKeyLength[Row]]);
|
||
|
|
||
|
{ Verify the index key count matches the number of records in the
|
||
|
table.
|
||
|
Future: This test would change if there were ever a type of index
|
||
|
that filtered out keys. }
|
||
|
if FIndexHead^.bihIndexKeyCount[Row] <> Info.RecordCount then
|
||
|
DoReportError(rciInxHeaderInvalidKeyCount,
|
||
|
[Row, FIndexHead^.bihIndexKeyCount[Row],
|
||
|
Info.RecordCount]);
|
||
|
|
||
|
{ There are no records in the table. Verify the index map does not
|
||
|
point to an index page. }
|
||
|
if (Info.RecordCount = 0) then begin
|
||
|
if FIndexHead^.bihIndexRoot[Row] <> ffc_W32NoValue then
|
||
|
DoReportError(rciInxHeaderInvalidRootPage,
|
||
|
[Row, FIndexHead^.bihIndexRoot[Row]]);
|
||
|
end
|
||
|
else if (FIndexHead^.bihIndexRoot[Row] <> ffc_W32NoValue) then
|
||
|
{ There are records. Verify the index root page is really an index
|
||
|
block. }
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, FIndexHead^.bihIndexRoot[Row],
|
||
|
FTI, ffc_ReadOnly, RelMethod);
|
||
|
try
|
||
|
{ Is it an index block? }
|
||
|
if (PffBlockHeaderIndex(Block)^.bhisignature <> ffc_SigIndexBlock) then
|
||
|
DoReportError(rciInxHeaderInvalidRootPage,
|
||
|
[Row, FIndexHead^.bihIndexRoot[Row]]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInxHeaderInvalidRootPage, [FIndexHead^.bihIndexRoot[Row]]);
|
||
|
end
|
||
|
else
|
||
|
DoReportError(rciInxHeaderNoRootPage, [Row]);
|
||
|
|
||
|
{ Future: Verify index page count. }
|
||
|
|
||
|
{ Verify index flags. If this is the first row then it should indicate
|
||
|
that keys are refs. }
|
||
|
if (Row = 0) then
|
||
|
if (FIndexHead^.bihIndexFlags[Row] and ffc_InxFlagKeysAreRefs) <>
|
||
|
ffc_InxFlagKeysAreRefs then
|
||
|
DoReportError(rciInxHeaderNoRefsFlag, []);
|
||
|
|
||
|
if Info.Dict.IndexDescriptor[Row].idDups then
|
||
|
if (FIndexHead^.bihIndexFlags[Row] and ffc_InxFlagAllowDups) <>
|
||
|
ffc_InxFlagAllowDups then
|
||
|
DoReportError(rciInxHeaderNoDupsFlag, [Row, Row]);
|
||
|
|
||
|
|
||
|
end; { for }
|
||
|
end;
|
||
|
finally
|
||
|
if Modified then
|
||
|
EndUpdate;
|
||
|
end;
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
|
||
|
{===TffDataBlock=====================================================}
|
||
|
function TffDataBlock.GetDataCell(const Row, Column : Integer) : string;
|
||
|
var
|
||
|
FieldValue : TffVCheckValue;
|
||
|
IsNull : Boolean;
|
||
|
Info : TffGeneralFileInfo;
|
||
|
RecPtrDel,
|
||
|
RecPtrData : PffByteArray;
|
||
|
Offset : Integer;
|
||
|
begin
|
||
|
if Row > Pred(GetRecCount) then
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in row %d when there are only %d records in the view',
|
||
|
[Row, GetRecCount]);
|
||
|
|
||
|
{ Get the general file info. }
|
||
|
if Assigned(FOnGetInfo) then
|
||
|
FOnGetInfo(Info)
|
||
|
else
|
||
|
raise Exception.Create('File interface must provide OnGetInfo handler.');
|
||
|
|
||
|
if Column < FNumDataColumns then begin
|
||
|
Result := '-';
|
||
|
FillChar(FieldValue, SizeOf(FieldValue), 0);
|
||
|
{ Position two pointers to the beginning of the record. The first points
|
||
|
to the deleted flag. The second points to the start of the record. }
|
||
|
Offset := ffc_BlockHeaderSizeData + (Info.RecLenPlusTrailer * Row);
|
||
|
RecPtrDel := @FBlock[Offset];
|
||
|
RecPtrData := @FBlock[Offset + 1];
|
||
|
|
||
|
{ Is the record deleted? }
|
||
|
if Column = 0 then
|
||
|
Result := IntToStr(Row)
|
||
|
else if PByte(RecPtrDel)^ = $FF then begin
|
||
|
if Column = 1 then
|
||
|
Result := 'Y';
|
||
|
end
|
||
|
else if Column > 1 then begin
|
||
|
Info.Dict.GetRecordField(Column - 2, RecPtrData, IsNull, @FieldValue);
|
||
|
if IsNull then
|
||
|
Result := '<null>'
|
||
|
else
|
||
|
Result := FFVCheckValToString(FieldValue,
|
||
|
Info.Dict.FieldType[Column - 2]);
|
||
|
end; { if..else }
|
||
|
end
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in column %d when there are only %d columns in the view',
|
||
|
[Column, FNumDataColumns]);
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetDataColCaption(const Index : Integer) : string;
|
||
|
var
|
||
|
Info : TffGeneralFileInfo;
|
||
|
begin
|
||
|
if Index < FNumDataColumns then begin
|
||
|
{ Get the general file info. }
|
||
|
if Assigned(FOnGetInfo) then
|
||
|
FOnGetInfo(Info)
|
||
|
else
|
||
|
raise Exception.Create('File interface must provide OnGetInfo handler.');
|
||
|
if Index = 0 then
|
||
|
Result := 'Slot'
|
||
|
else if Index = 1 then
|
||
|
Result := 'Deleted?'
|
||
|
else
|
||
|
Result := Info.Dict.FieldName[Index - 2];
|
||
|
end
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for caption %d when there are only %d columns in the view',
|
||
|
[Index, ciIndexHeaderDataColumns]);
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetDataColCount : Integer;
|
||
|
var
|
||
|
Info : TffGeneralFileInfo;
|
||
|
begin
|
||
|
{ Get the general file info. }
|
||
|
if Assigned(FOnGetInfo) then
|
||
|
FOnGetInfo(Info)
|
||
|
else
|
||
|
raise Exception.Create('File interface must provide OnGetInfo handler.');
|
||
|
Result := Info.Dict.FieldCount + 2;
|
||
|
{ The first extra column is the slot # of the record (base 0) & the second
|
||
|
extra column used to indicate whether the record is deleted. }
|
||
|
FNumDataColumns := Result;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetDataColWidth(const Index : Integer) : Integer;
|
||
|
begin
|
||
|
if Index < FNumDataColumns then begin
|
||
|
if Index = 1 then
|
||
|
Result := 70
|
||
|
else
|
||
|
Result := 50
|
||
|
end
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for width %d when there are only %d columns in the view',
|
||
|
[Index, FNumDataColumns]);
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetDataRowCount : Integer;
|
||
|
begin
|
||
|
Result := GetRecCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetNextDataBlock : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderData(FBlock)^.bhdNextDataBlock;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetPrevDataBlock : TffWord32;
|
||
|
begin
|
||
|
Result := PffBlockHeaderData(FBlock)^.bhdPrevDataBlock;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetPropertyCell(const Row, Column : Integer) : string;
|
||
|
begin
|
||
|
if Column > Pred(ciFileBlockColumns) then
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in column %d when there are only %d columns in the view',
|
||
|
[Column, ciFileBlockColumns]);
|
||
|
|
||
|
{ Does this cell come from the common block view? }
|
||
|
if Row < ciFileBlockRows then
|
||
|
Result := inherited GetPropertyCell(Row, Column)
|
||
|
else
|
||
|
case Row of
|
||
|
5 : if Column = 0 then
|
||
|
Result := 'Record count'
|
||
|
else
|
||
|
Result := IntToStr(GetRecCount);
|
||
|
6 : if Column = 0 then
|
||
|
Result := 'Record length'
|
||
|
else
|
||
|
Result := IntToStr(GetRecLen);
|
||
|
7 : if Column = 0 then
|
||
|
Result := 'Next data block'
|
||
|
else
|
||
|
Result := IntToStr(GetNextDatablock);
|
||
|
8 : if Column = 0 then
|
||
|
Result := 'Previous data block'
|
||
|
else
|
||
|
Result := IntToStr(GetPrevDataBlock);
|
||
|
else
|
||
|
raise Exception.CreateFmt
|
||
|
('Cannot ask for cell in row %d when there are only %d rows in the view',
|
||
|
[Row, ciFileBlockRows + ciDataBlockRows]);
|
||
|
end; { case }
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetPropertyRowCount : Integer;
|
||
|
begin
|
||
|
Result := ciFileBlockRows + ciDataBlockRows;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetRecCount : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderData(FBlock)^.bhdRecCount;
|
||
|
end;
|
||
|
{--------}
|
||
|
function TffDataBlock.GetRecLen : Longint;
|
||
|
begin
|
||
|
Result := PffBlockHeaderData(FBlock)^.bhdRecLength;
|
||
|
end;
|
||
|
{--------}
|
||
|
{ The following code was copied from unit FFTBBLOB. }
|
||
|
function TffDataBlock.IsEmptyLookupEntry(Entry : PffBLOBLookupEntry) : Boolean;
|
||
|
const
|
||
|
ciEmptyVal1 = 808464432;
|
||
|
{ This is because the lookup segments are fillchar'd with 'O' instead of 0.
|
||
|
We have to check all 3 fields in the lookup entry for this value so that
|
||
|
we avoid a case where the value is valid. }
|
||
|
ciEmptyVal2 = 1179010630;
|
||
|
{ Another value that indicates an empty lookup entry. }
|
||
|
begin
|
||
|
Result := ((Entry^.bleSegmentOffset.iLow = ciEmptyVal1) and
|
||
|
(Entry^.bleSegmentOffset.iHigh = ciEmptyVal1) and
|
||
|
(Entry^.bleContentLength = ciEmptyVal1)) or
|
||
|
((Entry^.bleSegmentOffset.iLow = ciEmptyVal2) and
|
||
|
(Entry^.bleSegmentOffset.iHigh = ciEmptyVal2) and
|
||
|
(Entry^.bleContentLength = ciEmptyVal2));
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffDataBlock.SetNextDataBlock(const Value : TffWord32);
|
||
|
begin
|
||
|
PffBlockHeaderData(FBlock)^.bhdNextDataBlock := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffDataBlock.SetPrevDataBlock(const Value : TffWord32);
|
||
|
begin
|
||
|
PffBlockHeaderData(FBlock)^.bhdPrevDataBlock := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffDataBlock.SetRecCount(const Value : Longint);
|
||
|
begin
|
||
|
PffBlockHeaderData(FBlock)^.bhdRecCount := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffDataBlock.SetRecLen(const Value : Longint);
|
||
|
begin
|
||
|
PffBlockHeaderData(FBlock)^.bhdRecLength := Value;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffDataBlock.VerifyBLOB(const BLOBNr : TffInt64;
|
||
|
var ErrCode : Integer);
|
||
|
var
|
||
|
BLOBBlock : PffBlock;
|
||
|
BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock;
|
||
|
BLOBBlockNum : TffWord32;
|
||
|
BLOBHeader : PffBLOBHeader;
|
||
|
EntryCount : Integer;
|
||
|
LookupBlock, ContentBlock : TffWord32;
|
||
|
LookupEntry : PffBLOBLookupEntry;
|
||
|
ContentEntry : PffBLOBSegmentHeader;
|
||
|
LookupSegBlk, ContentSegBlk : PffBlock;
|
||
|
LookupSegPtr : PffBLOBSegmentHeader;
|
||
|
NextSeg : TffInt64;
|
||
|
OffsetInBlock, ContentOffsetInBlock : TffWord32;
|
||
|
aLkpRelMethod,
|
||
|
aContRelMethod,
|
||
|
aHdRelMethod : TffReleaseMethod;
|
||
|
ByteCount,
|
||
|
CurByteCount : Longint;
|
||
|
begin
|
||
|
ErrCode := 0;
|
||
|
CurByteCount := 0;
|
||
|
LookupSegBlk := nil;
|
||
|
|
||
|
{ Read and verify the BLOB header block for this BLOB number. }
|
||
|
try
|
||
|
BLOBBlock := ReadVfyBlobBlock2(FFileInfo,
|
||
|
FTI,
|
||
|
ffc_ReadOnly,
|
||
|
BLOBNr,
|
||
|
BLOBBlockNum,
|
||
|
OffsetInBlock,
|
||
|
aHdRelMethod);
|
||
|
except
|
||
|
ErrCode := rciBLOBInvalidRefNr;
|
||
|
Exit;
|
||
|
end;
|
||
|
|
||
|
BLOBHeader := @BLOBBlock^[OffsetInBlock];
|
||
|
|
||
|
{ Verify the BLOB has not been deleted. }
|
||
|
if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then begin
|
||
|
ErrCode := rciBLOBDeleted;
|
||
|
Exit;
|
||
|
end
|
||
|
else if (BLOBHeader^.bbhSignature <> ffc_SigBLOBSegHeader) then begin
|
||
|
{ The BLOB header has an invalid signature. }
|
||
|
ErrCode := rciBLOBHeaderSignature;
|
||
|
Exit;
|
||
|
end
|
||
|
else if BLOBHeader^.bbh1stLookupSeg.iLow = ffc_W32NoValue then
|
||
|
{ The BLOB has been truncated to length zero. This is a valid situation &
|
||
|
there is nothing else to do. }
|
||
|
Exit
|
||
|
else if (BLOBHeader^.bbhSegCount = ffc_FileBLOB) or
|
||
|
(BLOBHeader^.bbhSegCount = ffc_BLOBLink) then
|
||
|
{ This is a file BLOB or a BLOB link. There is nothing else to do so
|
||
|
exit the routine. }
|
||
|
Exit;
|
||
|
|
||
|
ByteCount := BLOBHeader^.bbhBLOBLength;
|
||
|
try
|
||
|
{ Get the lookup segment block and set up offset for 1st lookup entry. }
|
||
|
try
|
||
|
LookupSegBlk := ReadVfyBlobBlock2(FFileInfo, FTI, ffc_ReadOnly,
|
||
|
BLOBHeader^.bbh1stLookupSeg,
|
||
|
LookupBlock, OffsetInBlock,
|
||
|
aLkpRelMethod);
|
||
|
except
|
||
|
ErrCode := rciBLOBInvalidLookupRefNr;
|
||
|
Exit;
|
||
|
end;
|
||
|
LookupSegPtr := @LookupSegBlk^[OffsetInBlock];
|
||
|
OffsetInBlock := OffsetInBlock + sizeof(TffBLOBSegmentHeader);
|
||
|
|
||
|
EntryCount := 0;
|
||
|
while True do begin
|
||
|
inc(EntryCount);
|
||
|
LookupEntry := @LookupSegBlk^[OffsetInBlock];
|
||
|
{ If there are no more lookup entries then verification has finished. }
|
||
|
if (CurByteCount >= ByteCount) or
|
||
|
IsEmptyLookupEntry(LookupEntry) then
|
||
|
Exit;
|
||
|
|
||
|
inc(CurByteCount, LookupEntry^.bleContentLength);
|
||
|
|
||
|
{ Verify the segment is valid. }
|
||
|
ContentSegBlk := nil;
|
||
|
aContRelMethod := nil;
|
||
|
try
|
||
|
ContentSegBlk := ReadVfyBlobBlock2(FFileInfo, FTI, ffc_ReadOnly,
|
||
|
LookupEntry^.bleSegmentOffset,
|
||
|
ContentBlock, ContentOffsetInBlock,
|
||
|
aContRelMethod);
|
||
|
except
|
||
|
ErrCode := rciBLOBInvalidContentRefNr;
|
||
|
Exit;
|
||
|
end;
|
||
|
|
||
|
try
|
||
|
ContentEntry := @ContentSegBlk^[ContentOffsetInBlock];
|
||
|
if PffBlockHeaderBLOB(ContentSegBlk)^.bhbSignature <> ffc_SigBLOBBlock then begin
|
||
|
ErrCode := rciBLOBContentBlockSignature;
|
||
|
Exit;
|
||
|
end
|
||
|
else if ContentEntry^.bshSignature <> ffc_SigBLOBSegContent then begin
|
||
|
ErrCode := rciBLOBContentSegSignature;
|
||
|
Exit;
|
||
|
end
|
||
|
else begin
|
||
|
{ See if we're at the end of the lookup segment. }
|
||
|
if (LookupSegPtr^.bshSegmentLen <
|
||
|
(sizeof(TffBLOBSegmentHeader) +
|
||
|
(succ(EntryCount) * sizeof(TffBLOBLookupEntry)))) then begin
|
||
|
NextSeg := LookupSegPtr^.bshNextSegment;
|
||
|
if NextSeg.iLow <> ffc_W32NoValue then begin
|
||
|
aLkpRelMethod(LookupSegBlk);
|
||
|
try
|
||
|
LookupSegBlk := ReadVfyBlobBlock2(FFileInfo, FTI, ffc_ReadOnly,
|
||
|
NextSeg,
|
||
|
LookupBlock, OffsetInBlock,
|
||
|
aLkpRelMethod);
|
||
|
except
|
||
|
ErrCode := rciBLOBInvalidLookupRefNr;
|
||
|
Exit;
|
||
|
end;
|
||
|
LookupSegPtr := @LookupSegBlk^[OffsetInBlock];
|
||
|
OffsetInBlock := OffsetInBlock + sizeof(TffBLOBSegmentHeader);
|
||
|
EntryCount := 0;
|
||
|
end
|
||
|
else
|
||
|
break;
|
||
|
end else
|
||
|
OffsetInBlock := OffsetInBlock + sizeof(TffBLOBLookupEntry);
|
||
|
end;
|
||
|
finally
|
||
|
if Assigned(aContRelMethod) then
|
||
|
aContRelMethod(ContentSegBlk);
|
||
|
end;
|
||
|
end; {while}
|
||
|
finally
|
||
|
if assigned(LookupSegBlk) then
|
||
|
aLkpRelMethod(LookupSegBlk);
|
||
|
aHdRelMethod(BLOBBlock);
|
||
|
end;
|
||
|
end;
|
||
|
{--------}
|
||
|
procedure TffDataBlock.VerifyRepair(const Repair : Boolean);
|
||
|
var
|
||
|
BLOBInx,
|
||
|
Inx : Integer;
|
||
|
IsNull,
|
||
|
Modified : Boolean;
|
||
|
Block : PffBlock;
|
||
|
RelMethod : TffReleaseMethod;
|
||
|
Info : TffGeneralFileInfo;
|
||
|
RecPtrDel,
|
||
|
RecPtrData : PffByteArray;
|
||
|
Offset : Longint;
|
||
|
BLOBNr : TffInt64;
|
||
|
ErrCode : Integer;
|
||
|
begin
|
||
|
inherited;
|
||
|
Modified := False;
|
||
|
try
|
||
|
{ Get the general file info. }
|
||
|
if Assigned(FOnGetInfo) then
|
||
|
FOnGetInfo(Info)
|
||
|
else
|
||
|
raise Exception.Create('File interface must provide OnGetInfo handler.');
|
||
|
|
||
|
{ Does the record count match the file header? }
|
||
|
if RecordCount <> Info.RecordsPerBlock then begin
|
||
|
DoReportError(rciInvalidDataBlockRecCount,
|
||
|
[BlockNum, RecordCount, Info.RecordsPerBlock]);
|
||
|
if Repair then begin
|
||
|
BeginUpdate;
|
||
|
Modified := True;
|
||
|
RecordCount := Info.RecordCount;
|
||
|
DoReportFix(rciInvalidDataBlockRecCount, [BlockNum, RecordCount]);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
{ Does the record length match? }
|
||
|
if RecordLen <> Info.Dict.RecordLength then begin
|
||
|
DoReportError(rciInvalidDataBlockRecLen,
|
||
|
[BlockNum, RecordLen, Info.Dict.RecordLength]);
|
||
|
if Repair then begin
|
||
|
BeginUpdate;
|
||
|
Modified := True;
|
||
|
RecordLen := Info.Dict.RecordLength;
|
||
|
DoReportFix(rciInvalidDataBlockRecLen, [BlockNum, RecordLen]);
|
||
|
end;
|
||
|
end;
|
||
|
|
||
|
{ Verify the next data block is really a data block. }
|
||
|
if NextDataBlock <> ffc_W32NoValue then begin
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, NextDataBlock, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
if PffBlockHeaderData(Block)^.bhdSignature <> ffc_SigDataBlock then
|
||
|
DoReportError(rciInvalidNextDataBlock, [BlockNum, NextDataBlock]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInvalidNextDataBlock, [BlockNum, NextDataBlock]);
|
||
|
end;
|
||
|
end; { if }
|
||
|
|
||
|
{ Verify the previous data block is really a data block. }
|
||
|
if PrevDataBlock <> ffc_W32NoValue then begin
|
||
|
try
|
||
|
Block := FBufMgr.GetBlock(FFileInfo, PrevDataBlock, FTI, ffc_ReadOnly,
|
||
|
RelMethod);
|
||
|
try
|
||
|
if PffBlockHeaderData(Block)^.bhdSignature <> ffc_SigDataBlock then
|
||
|
DoReportError(rciInvalidPrevDataBlock, [BlockNum, PrevDataBlock]);
|
||
|
finally
|
||
|
RelMethod(Block);
|
||
|
end;
|
||
|
except
|
||
|
DoReportError(rciInvalidPrevDataBlock, [BlockNum, PrevDataBlock]);
|
||
|
end;
|
||
|
end; { if }
|
||
|
|
||
|
{ If this table has BLOB fields & there is only 1 file then verify the
|
||
|
BLOBs. }
|
||
|
{ Future: Handle BLOBs that are in a separate file. }
|
||
|
if Info.Dict.HasBLOBFields and (Info.Dict.FileCount = 1) then begin
|
||
|
{ Loop through the records in the block. If the record is not deleted
|
||
|
then check its BLOB references.
|
||
|
If verifying then suggested repair method is to pack.
|
||
|
However, when repairing, any invalid BLOB references will be nulled.
|
||
|
Packing the table then removes the invalid BLOBs from the table. }
|
||
|
Offset := ffc_BlockHeaderSizeData;
|
||
|
for Inx := 0 to Pred(RecordCount) do begin
|
||
|
RecPtrDel := @FBlock[Offset];
|
||
|
RecPtrData := @FBlock[Offset + 1];
|
||
|
{ Note: Adding +1 to offset skips the leading deleted flag. }
|
||
|
{ Has the record been deleted? }
|
||
|
if PByte(RecPtrDel)^ <> $FF then begin
|
||
|
{ No. Check each BLOB field. }
|
||
|
for BLOBInx := 0 to Pred(Info.BLOBFieldCount) do begin
|
||
|
Info.Dict.GetRecordField(Info.BLOBFields[BLOBInx],
|
||
|
RecPtrData, IsNull, @BLOBNr);
|
||
|
if not IsNull then begin
|
||
|
{ If have a BLOB reference then verify the BLOB. }
|
||
|
VerifyBLOB(BLOBNr, ErrCode);
|
||
|
{ If there is an error then report it. }
|
||
|
if ErrCode <> 0 then begin
|
||
|
DoReportError(ErrCode,
|
||
|
[Info.BLOBFieldNames[BLOBInx],
|
||
|
BLOBNr.iHigh, BLOBNr.iLow,
|
||
|
Info.KeyFieldValues(RecPtrData),
|
||
|
Inx, BlockNum]);
|
||
|
{ If repairing then null out the BLOB reference. }
|
||
|
if Repair then begin
|
||
|
BeginUpdate;
|
||
|
Modified := True;
|
||
|
RecPtrData := @FBlock[Offset + 1];
|
||
|
Info.Dict.SetRecordFieldNull(Info.BLOBFields[BLOBInx],
|
||
|
RecPtrData, True);
|
||
|
DoReportFix(ErrCode, [Info.BLOBFieldNames[BLOBInx],
|
||
|
Info.KeyFieldValues(RecPtrData),
|
||
|
Inx, BlockNum]);
|
||
|
end;
|
||
|
end;
|
||
|
end; { if }
|
||
|
end; { for }
|
||
|
end; { if }
|
||
|
{ Move to next record. }
|
||
|
inc(Offset, Info.RecLenPlusTrailer);
|
||
|
end; { for }
|
||
|
end; { if }
|
||
|
|
||
|
finally
|
||
|
if Modified then
|
||
|
EndUpdate;
|
||
|
end;
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
|
||
|
{===TffBLOBBlock=====================================================}
|
||
|
procedure TffBLOBBlock.VerifyRepair(const Repair : Boolean);
|
||
|
begin
|
||
|
inherited;
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
|
||
|
{===TffStreamBlock===================================================}
|
||
|
procedure TffStreamBlock.VerifyRepair(const Repair : Boolean);
|
||
|
begin
|
||
|
inherited;
|
||
|
end;
|
||
|
{====================================================================}
|
||
|
initialization
|
||
|
|
||
|
Tffv2FileInterface.Register('FlashFiler 2 repair interface');
|
||
|
|
||
|
finalization
|
||
|
|
||
|
Tffv2FileInterface.Unregister;
|
||
|
|
||
|
end.
|
||
|
|