{*********************************************************} {* FlashFiler: Table BLOB access *} {*********************************************************} (* ***** 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} {!!.11 - Added logging} { Uncomment the following define to enable BLOB tracing. } {.$DEFINE BLOBTrace} unit fftbblob; interface uses Classes, {!!.03} Windows, SysUtils, ffconst, ffllbase, ffsrmgr, ffllexcp, ffsrbase, ffsrlock, fffile, fftbbase; {---BLOB Link method types---} type TffBLOBLinkGetLength = function(const aTableName : TffTableName; const aBLOBNr : TffInt64; var aLength : Longint) : TffResult of object; { Declaration of method to be called when trying to find the length of a BLOB visible through a BLOB link. } TffBLOBLinkRead = function(const aTableName : TffTableName; const aBLOBNr : TffInt64; const aOffset : TffWord32; {!!.06} const aLen : TffWord32; {!!.06} var aBLOB; var aBytesRead : TffWord32) {!!.06} : TffResult of object; { Declaration of a method to be called when trying to read a BLOB visible through a BLOB link. } {---BLOB maintenance---} procedure FFTblAddBLOB(aFI : PffFileInfo; aTI : PffTransInfo; var aBLOBNr : TffInt64); {-add a new, empty (length 0) BLOB, return new BLOB number} procedure FFTblAddBLOBLink(aFI : PffFileInfo; aTI : PffTransInfo; const aTableName : TffTableName; const aTableBLOBNr : TffInt64; var aBLOBNr : TffInt64); {-Add a new BLOB link, return new BLOB number. } procedure FFTblAddFileBLOB(aFI : PffFileInfo; aTI : PffTransInfo; const aFileName : TffFullFileName; var aBLOBNr : TffInt64); {-add a new file BLOB, return new BLOB number} procedure FFTblDeleteBLOB(aFI : PffFileInfo; aTI : PffTransInfo; const aBLOBNr : TffInt64); {-delete a BLOB; BLOB number will no longer be valid after this} function FFTblFreeBLOB(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64) : boolean; {-if the BLOB length is zero, delete it; return true if deleted} function FFTblGetBLOBLength(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aLengthMethod : TffBLOBLinkGetLength; var aFBError: TffResult) : Longint; {-return the length of the BLOB} function FFTblGetFileNameBLOB(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; var aFileName : TffFullFileName ) : Boolean; {-return True if the given BLOB nr refers to a file BLOB, and the filename is returned in aFileName} function FFTblIsBLOBLink(aFI : PffFileInfo; {!!.11 - New} aTI : PffTransInfo; aBLOBNr : TffInt64; var aSrcTableName : TffTableName; var aSrcTableBLOBNr : TffInt64) : Boolean; { Checks to see if aBLOBNr is a BLOB Link. If it is, it returns the the offset of the source as aSrcTableBLOBNr in aSrcTableName {Begin !!.03} procedure FFTblListBLOBSegments(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aStream : TStream); { List the segments comprising the BLOB. } {End !!.03} {Begin !!.11} type TffBaseBLOBEngine = class; { foward declaration } TffBLOBEngineClass = class of TffBaseBLOBEngine; TffBaseBLOBEngine = class(TffObject) { Base class representing an engine to read, write, & truncate BLOBs. } public class function GetEngine(aFI : PffFileInfo) : TffBaseBLOBEngine; { Returns the engine instance to be used for the specified file. } procedure Read(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aOffset : TffWord32; aLen : TffWord32; aReadMethod : TffBLOBLinkRead; var aBLOB; var aBytesRead : TffWord32; var aFBError : TffResult); virtual; abstract; { Read all or part of a BLOB} procedure Truncate(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aLen : TffWord32); virtual; abstract; { Truncate the BLOB to the specified length. Does *not* delete BLOB if length 0. } procedure Write(aFI : PffFileInfo; aTI : PffTransInfo; const aBLOBNr : TffInt64; aOffset : TffWord32; aLen : TffWord32; const aBLOB); virtual; abstract; { Write to or append to a BLOB. } end; TffBLOBEngine = class(TffBaseBLOBEngine) { This class provides an interface to BLOBs in 2.1.0.1 and later. The logic supports the improved nesting algorithm that recycles all available BLOB segments regardless of size. } {Begin !!.12} protected function IsEmptyLookupEntry(Entry : PffBLOBLookupEntry) : Boolean; {End !!.12} public procedure Read(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aOffset : TffWord32; aLen : TffWord32; aReadMethod : TffBLOBLinkRead; var aBLOB; var aBytesRead : TffWord32; var aFBError : TffResult); override; { Read all or part of a BLOB} procedure Truncate(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aLen : TffWord32); override; { Truncate the BLOB to the specified length. Does *not* delete BLOB if length 0. } procedure Write(aFI : PffFileInfo; aTI : PffTransInfo; const aBLOBNr : TffInt64; aOffset : TffWord32; aLen : TffWord32; const aBLOB); override; { Write to or append to a BLOB. } end; Tff210BLOBEngine = class(TffBaseBLOBEngine) { This class provides an interface to BLOBs that is compatible with tables created under versions 2.0.0.0 to 2.1.0.0. } public procedure Read(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aOffset : TffWord32; aLen : TffWord32; aReadMethod : TffBLOBLinkRead; var aBLOB; var aBytesRead : TffWord32; var aFBError : TffResult); override; { Read all or part of a BLOB} procedure Truncate(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aLen : TffWord32); override; { Truncate the BLOB to the specified length. Does *not* delete BLOB if length 0. } procedure Write(aFI : PffFileInfo; aTI : PffTransInfo; const aBLOBNr : TffInt64; aOffset : TffWord32; aLen : TffWord32; const aBLOB); override; { Write to or append to a BLOB. } end; function FFTblRebuildLookupSegments(aFI : PffFileInfo; aTI : PffTransInfo; aNewBLOBSize : TffWord32; aOldBLOBSize : TffWord32; const aBLOBNr : TffInt64) : TffInt64; {-rebuilds all lookup segment(s) for a BLOB that is growing} {End !!.11} implementation uses fflllog, {!!.13} ffsrbde, ffsrblob; resourcestring ffcBLOBSegExpected = ' Expected %s segment but segment marked with ''%s''.'; ffcBLOBSegHeader = 'header'; const ffc_FileBLOB = -1; ffc_BLOBLink = -2; {Begin !!.11} var FFBLOBEngine : TffBLOBEngine; FF210BLOBEngine : Tff210BLOBEngine; {End !!.11} {Begin !!.13} {$IFDEF BLOBTrace} var btLog : TffEventLog; procedure Logbt(aMsg : string; args : array of const); begin if btLog <> nil then btLog.WriteStringFmt(aMsg, args); end; {$ENDIF} {End !!.13} {== Calculation routines =============================================} function EstimateSegmentCount(const aBLOBSize, aMaxSegSize : Integer) : Integer; begin Result := ((aBLOBSize * 2) div aMaxSegSize) + 1; end; {Begin !!.11} function CalcBLOBSegNumber(const aOffset : TffWord32; const aBlockSize : TffWord32; var aOfsInSeg : TffWord32) : TffWord32; {-Calculate the segment number for an offset into a BLOB.} {-aOfsInSeg tells us how much of the last segment (result) we're using} var MaxSegSize : TffWord32; begin {offset 0 is in the 1st segment} if aOffset = 0 then begin Result := 1; aOfsInSeg := 0; end else begin MaxSegSize := (((aBlockSize - ffc_BlockHeaderSizeBLOB) div ffc_BLOBSegmentIncrement) * ffc_BLOBSegmentIncrement) - sizeof(TffBLOBSegmentHeader); aOfsInSeg := 0; Result := aOffset div MaxSegSize; aOfsInSeg := aOffset - (Result * MaxSegSize); if aOfsInSeg > 0 then inc(Result) else if (aOfsInSeg = 0) and (aOffset <> 0) then aOfsInSeg := MaxSegSize; end; {if..else} end; {=====================================================================} {== BLOB link routines ===============================================} function BLOBLinkGetLength(aBLOBHeader : PffBLOBHeader; aGetLengthMethod : TffBLOBLinkGetLength; var aLength : Longint) : TffResult; var BLOBData : PffByteArray absolute aBLOBHeader; BLOBNr : TffInt64; TableName : TffFullFileName; TableNameLen : Byte; begin { Get the length of the table name. } Move(BLOBData^[sizeof(TffBLOBHeader)], TableNameLen, sizeOf(TableNameLen)); Inc(TableNameLen); { Copy the file name to TableName. } Move(BLOBData^[sizeof(TffBLOBHeader)], TableName, TableNameLen); { Get the table's BLOB number. } Move(BLOBData^[SizeOf(TffBLOBHeader) + TableNameLen], BlobNr, SizeOf(TffInt64)); Result := aGetLengthMethod(TableName, BlobNr, aLength); end; {--------} procedure BLOBLinkGetTableNameAndRefNr(aBLOBBlock : PffBlock; {!!.11 - New} aBlockOffset : Integer; var aTableName : TffTableName; var aBLOBNr : TffInt64); var TableNameLen : Byte; begin { Get the length of the table name. } Inc(aBlockOffset, SizeOf(TffBLOBHeader)); Move(aBLOBBlock^[aBlockOffset], TableNameLen, SizeOf(TableNameLen)); Inc(TableNameLen); { Copy the file name to TableName. } Move(aBLOBBlock^[aBlockOffset], aTableName, TableNameLen); { Get the table's BLOB number. } Move(aBLOBBlock^[aBlockOffset + TableNameLen], aBLOBNr, SizeOf(TffInt64)); end; {--------} function BLOBLinkRead(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aOffset : Longint; aLen : Longint; aReadMethod : TffBLOBLinkRead; var aBLOB; var aBytesRead : TffWord32) {!!.06} : TffResult; var BLOBBlock : PffBlock; BLOBNr : TffInt64; OffsetInBlock : TffWord32; {!!.11} {TableNameLen : Byte;} {!!.11} TableName : TffTableName; aFHRelMethod : TffReleaseMethod; begin BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_ReadOnly, aBLOBNr, OffsetInBlock, aFHRelMethod); try BLOBLinkGetTableNameAndRefNr(BLOBBlock, {!!.11} OffsetInBlock, TableName, BLOBNr); Result := aReadMethod(TableName, BlobNr, aOffset, aLen, aBLOB, aBytesRead); finally aFHRelMethod(BLOBBlock); end; end; {=====================================================================} {== File BLOB routines ===============================================} function FileBLOBLength(aBLOBHeader : PffBLOBHeader; var aLength : Longint) : TffResult; var BLOBFile : PffFileInfo; BLOBData : PffByteArray absolute aBLOBHeader; FileName : TffFullFileName; FileNameLen : Byte; TmpLen : TffInt64; begin Result := 0; {Get the length of the file name} Move(BLOBData^[sizeof(TffBLOBHeader)], FileNameLen, sizeOf(FileNameLen)); {copy the file name to FileName} Move(BLOBData^[sizeof(TffBLOBHeader)], FileName, succ(FileNameLen)); try BLOBFile := FFAllocFileInfo(FileName, FFExtractExtension(FileName), nil); try FFOpenFile(BLOBFile, omReadOnly, smShared, false, false); try TmpLen := FFPositionFileEOF(BLOBFile); aLength := TmpLen.iLow; finally FFCloseFile(BLOBFile); end;{try..finally} finally FFFreeFileInfo(BLOBFile); end;{try..finally} except on E : EffException do begin case E.ErrorCode of fferrOpenFailed : Result := DBIERR_FF_FileBLOBOpen; fferrCloseFailed : Result := DBIERR_FF_FileBLOBClose; fferrReadFailed : Result := DBIERR_FF_FileBLOBRead; fferrSeekFailed : Result := DBIERR_FF_FileBLOBRead; else raise end;{case} end; end;{try..except} end; {--------} function FileBLOBRead(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aOffset : Longint; aLen : Longint; var aBLOB; var aBytesRead : TffWord32) {!!.06} : TffResult; var BLOBFile : PffFileInfo; BLOBBlock : PffBlock; OffsetInBlock : TffWord32; {!!.11} FileNameLen : Byte; FileName : TffFullFileName; TempI64 : TffInt64; aFHRelMethod : TffReleaseMethod; begin Result := 0; BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_ReadOnly, aBLOBNr, OffsetInBlock, aFHRelMethod); try {Get the length of the file name} Move(BLOBBlock^[(OffsetInBlock + sizeof(TffBLOBHeader))], FileNameLen, sizeOf(FileNameLen)); {copy the file name to FileName} Move(BLOBBlock^[(OffsetInBlock + sizeof(TffBLOBHeader))], FileName, succ(FileNameLen)); try BLOBFile := FFAllocFileInfo(FileName, FFExtractExtension(FileName), nil); try FFOpenFile(BLOBFile, omReadOnly, smShared, false, false); try TempI64.iLow := aOffset; TempI64.iHigh := 0; FFPositionFile(BLOBFile, TempI64); aBytesRead := FFReadFile(BLOBFile, aLen, aBLOB); finally FFCloseFile(BLOBFile); end;{try..finally} finally FFFreeFileInfo(BLOBFile); end;{try..finally} except on E : EffException do begin case E.ErrorCode of fferrOpenFailed : Result := DBIERR_FF_FileBLOBOpen; fferrCloseFailed : Result := DBIERR_FF_FileBLOBClose; fferrReadFailed : Result := DBIERR_FF_FileBLOBRead; fferrSeekFailed : Result := DBIERR_FF_FileBLOBRead; else raise end;{case} end; end;{try..except} finally aFHRelMethod(BLOBBlock); end; end; {=====================================================================} {== BLOB maintenance =================================================} procedure FFTblAddBLOB(aFI : PffFileInfo; aTI : PffTransInfo; var aBLOBNr : TffInt64); var FileHeader : PffBlockHeaderFile; BLOBHeaderPtr : PffBLOBHeader; BLOBBlock : PffBlock; SegSize : TffWord32; {!!.11} OffsetInBlock : TffWord32; {!!.11} aBlkRelMethod, aFHRelMethod : TffReleaseMethod; begin {$IFDEF BLOBTrace} {!!.11} Logbt('FFTblAddBLOB.Begin', []); {$ENDIF} { First get the file header, block 0. } FileHeader := PffBlockHeaderFile(FFBMGetBlock(aFI, aTI, 0, ffc_MarkDirty, aFHRelMethod)); try { Create a new BLOB header. } SegSize := ffc_BLOBHeaderSize; {!!.11} aBLOBNr := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, {!!.11} SegSize); {!!.11} BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OffsetInBlock, aBlkRelMethod); try BLOBHeaderPtr := @BLOBBlock^[OffsetInBlock]; {set up the new BLOB header} with BLOBHeaderPtr^ do begin bbhSignature := ffc_SigBLOBSegHeader; bbhSegmentLen := (((sizeof(TffBLOBHeader) + pred(ffc_BLOBSegmentIncrement)) div ffc_BLOBSegmentIncrement) * ffc_BLOBSegmentIncrement); bbhBLOBLength := 0; bbhSegCount := 0; bbh1stLookupSeg.iLow := ffc_W32NoValue; {!!.11} end; {we've got one more BLOB} inc(FileHeader^.bhfBLOBCount); finally aBlkRelMethod(BLOBBlock); end; finally aFHRelMethod(PffBlock(FileHeader)); end; end; {--------} procedure FFTblAddBLOBLink(aFI : PffFileInfo; aTI : PffTransInfo; const aTableName : TffTableName; const aTableBLOBNr : TffInt64; var aBLOBNr : TffInt64); var FileHeader : PffBlockHeaderFile; BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; SegSize : TffWord32; {!!.11} OffsetInBlock : TffWord32; {!!.11} BLOBHeaderPtr : PffBLOBHeader; LinkLen, NameLen : TffWord32; {!!.11} aBlkRelMethod, aFHRelMethod : TffReleaseMethod; begin { First get the file header, block 0. } FileHeader := PffBlockHeaderFile(FFBMGetBlock(aFI, aTI, 0, ffc_MarkDirty, aFHRelMethod)); try { Create a new BLOB header. } NameLen := succ(Length(aTableName)); LinkLen := succ(Length(aTableName) + SizeOf(aTableBLOBNr)); SegSize := ffc_BLOBHeaderSize + LinkLen; {!!.11} aBLOBNr := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, {!!.11} SegSize); {!!.11} if (aBLOBNr.iLow <> ffc_W32NoValue) then begin BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OffsetInBlock, aBlkRelMethod); BLOBHeaderPtr := @BLOBBlock^[OffsetInBlock]; end else begin aBLOBNr.iLow := ffc_W32NoValue; Exit; end; { Set up the new BLOB header. } with BLOBHeaderPtr^ do begin bbhSignature := ffc_SigBLOBSegHeader; bbhBLOBLength := 0; bbhSegCount := ffc_BLOBLink; bbh1stLookupSeg.iLow := ffc_W32NoValue; end; { Write aTableName & the table's BLOB number after BLOBHeader. Note that length of string is automatically stored as the first byte of the string. } Move(aTableName, BLOBBlock^[(OffsetInBlock + sizeof(TffBLOBHeader))], NameLen); Move(aTableBLOBNr, BLOBBlock^[(OffsetInBlock + SizeOf(TffBLOBHeader) + NameLen)], SizeOf(TffInt64)); { We've got one more BLOB. } inc(FileHeader.bhfBLOBCount); aBlkRelMethod(BLOBBlock); finally aFHRelMethod(PffBlock(FileHeader)); end; end; {--------} procedure FFTblAddFileBLOB(aFI : PffFileInfo; aTI : PffTransInfo; const aFileName : TffFullFileName; var aBLOBNr : TffInt64); var FileHeader : PffBlockHeaderFile; BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; SegSize : TffWord32; {!!.11} OffsetInBlock : TffWord32; {!!.11} BLOBHeaderPtr : PffBLOBHeader; FileNameLen : Integer; aBlkRelMethod, aFHRelMethod : TffReleaseMethod; begin {first get the file header, block 0} FileHeader := PffBlockHeaderFile(FFBMGetBlock(aFI, aTI, 0, ffc_MarkDirty, aFHRelMethod)); try {create a new BLOB header} FileNameLen := succ(Length(aFileName)); SegSize := ffc_BLOBHeaderSize + FileNameLen; {!!.11} aBLOBNr := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, {!!.11} SegSize); {!!.11} if (aBLOBNr.iLow <> ffc_W32NoValue) then begin BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OffsetInBlock, aBlkRelMethod); BLOBHeaderPtr := @BLOBBlock^[OffsetInBlock]; end else begin aBLOBNr.iLow := ffc_W32NoValue; exit; end; {set up the new BLOB header} with BLOBHeaderPtr^ do begin bbhSignature := ffc_SigBLOBSegHeader; bbhBLOBLength := 0; bbhSegCount := ffc_FileBLOB; bbh1stLookupSeg.iLow := ffc_W32NoValue; end; { Write aFileName after BLOBHeader. Note that length of string is automatically stored as the first byte of the string. } Move(aFileName, BLOBBlock^[(OffsetInBlock + sizeof(TffBLOBHeader))], FileNameLen); {we've got one more BLOB} inc(FileHeader.bhfBLOBCount); aBlkRelMethod(BLOBBlock); finally aFHRelMethod(PffBlock(FileHeader)); end; end; {--------} procedure FFTblDeleteBLOBPrim(aFI : PffFileInfo; aTI : PffTransInfo; BLOBHeader : PffBLOBHeader); var OffsetInBlock : TffWord32; {!!.11} LookupSegBlk : PffBlock; LookupSegOfs, {!!.03} TmpSegOfs : TffInt64; {!!.03} LookupSegPtr : PffBLOBSegmentHeader; LookupEntOfs : integer; LookupEntPtr : PffBLOBLookupEntry; EntryCount, {!!.03} RemainEntries : Integer; {!!.03} i : Integer; aRelMethod : TffReleaseMethod; begin {$IFDEF BLOBTrace} {!!.11} Logbt('FFTblDeleteBLOBPrim.Begin', []); {$ENDIF} { Assumption: File header block is exclusively locked. } { Get the BLOB's first lookup segment. } LookupSegOfs := BLOBHeader^.bbh1stLookupSeg; {Begin !!.03} { BLOB truncated to length 0? } if LookupSegOfs.iLow = ffc_W32NoValue then Exit; {End !!.03} LookupSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, LookupSegOfs, OffsetInBlock, aRelMethod); LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; LookupEntOfs := OffsetInBlock + sizeof(TffBLOBSegmentHeader); try { Get the first lookup entry in the lookup segment. } LookupEntPtr := @LookupSegBlk^[LookupEntOfs]; { Is this the only lookup segment? } if LookupSegPtr^.bshNextSegment.iLow <> ffc_W32NoValue then { No. Figure out number of lookup entries based on segment size. } EntryCount := FFCalcMaxLookupEntries(LookupSegPtr) else { Yes. Number of lookup entries = number of content segments. } EntryCount := BLOBHeader^.bbhSegCount; RemainEntries := BLOBHeader^.bbhSegCount; {!!.03} { Free each content segment. } dec(RemainEntries, EntryCount); {!!.03} for i := 1 to BLOBHeader^.bbhSegCount do begin aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, LookupEntPtr^.bleSegmentOffset); dec(EntryCount); { Need to move to another lookup segment? } if ((EntryCount = 0) and (LookupSegPtr^.bshNextSegment.iLow <> ffc_W32NoValue)) then begin {Yes. Get the location of the next lookup segment and delete the existing lookup segment. } TmpSegOfs := LookupSegPtr^.bshNextSegment; {!!.03} aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, LookupSegOfs); LookupSegOfs := TmpSegOfs; {!!.03} { Grab the next lookup segment. } aRelMethod(LookupSegBlk); LookupSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, LookupSegOfs, OffsetInBlock, aRelMethod); LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; LookupEntOfs := OffsetInBlock + sizeof(TffBLOBSegmentHeader); EntryCount := FFCalcMaxLookupEntries(LookupSegPtr); {Begin !!.03} if RemainEntries > EntryCount then dec(RemainEntries, EntryCount) else begin EntryCount := RemainEntries; RemainEntries := 0; end; end else { Grab the next lookup entry. } LookupEntOfs := LookupEntOfs + sizeof(TffBLOBLookupEntry); LookupEntPtr := @LookupSegBlk^[LookupEntOfs]; {End !!.03} end; {for} { Delete the last lookup segment.} aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, LookupSegOfs); finally aRelMethod(LookupSegBlk); end; end; {--------} procedure FFTblDeleteBLOB(aFI : PffFileInfo; aTI : PffTransInfo; const aBLOBNr : TffInt64); var FileHeader : PffBlockHeaderFile; BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBHeader : PffBLOBHeader; OffsetInBlock : TffWord32; {!!.11} aBlkRelMethod, aFHRelMethod : TffReleaseMethod; begin {$IFDEF BLOBTrace} {!!.11} Logbt('FFTblDeleteBLOB.Begin', []); {$ENDIF} {first get the file header, block 0} FileHeader := PffBlockHeaderFile(FFBMGetBlock(aFI, aTI, 0, ffc_MarkDirty, aFHRelMethod)); try {read and verify the BLOB header block} BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OffsetInBlock, aBlkRelMethod); BLOBHeader := @BLOBBlock^[OffsetInBlock]; {Begin !!.01} { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); {End !!.01} try FFTblDeleteBLOBPrim(aFI, aTI, BLOBHeader); { Delete the BLOB header} aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, aBLOBNr); { We've got one less BLOB. } dec(FileHeader.bhfBLOBCount); finally aBlkRelMethod(BLOBBlock); end; finally aFHRelMethod(PffBlock(FileHeader)); end; end; {--------} function FFTblFreeBLOB(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64) : Boolean; var BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBBlockNum : TffWord32; BLOBHeader : PffBLOBHeader; FileHeader : PffBlockHeaderFile; OffsetInBlock: TffWord32; {!!.11} TempI64 : TffInt64; aBlkRelMethod, aFHRelMethod : TffReleaseMethod; begin {$IFDEF BLOBTrace} {!!.11} Logbt('FFTblFreeBLOB.Begin', []); Logbt(' aBLOBNr = %d:%d', [aBLOBNr.iLow, aBLOBNr.iHigh]); {$ENDIF} { Assume we won't delete. } Result := false; FileHeader := nil; {now get the BLOB block} ffShiftI64R(aBLOBNr, aFI^.fiLog2BlockSize, TempI64); BLOBBlockNum := TempI64.iLow; { Read and verify the BLOB header block. } BLOBBlock := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, aBLOBNr, BLOBBlockNum, OffsetInBlock, aBlkRelMethod); BLOBHeader := @BLOBBlock^[OffsetInBlock]; {Begin !!.01} { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); {End !!.01} try {don't bother doing anything if the BLOB's length > 0} if (BLOBHeader^.bbhBLOBLength > 0) then Exit; { We don't need to obtain exclusive locks on file header page or BLOB page because the BLOB resource manager's DeleteSegment routine will do so. } { Delete the BLOB's header. } aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, aBLOBNr); { One less BLOB. } FileHeader := PffBlockHeaderFile(FFBMGetBlock(aFI, aTI, 0, ffc_MarkDirty, aFHRelMethod)); dec(FileHeader.bhfBLOBCount); { We did delete. } Result := true; finally if assigned(FileHeader) then aFHRelMethod(PffBlock(FileHeader)); aBlkRelMethod(BLOBBlock); end; end; {--------} function FFTblGetBLOBLength(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aLengthMethod : TffBLOBLinkGetLength; var aFBError : TffResult) : Longint; var BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBBlockNum : TffWord32; BLOBHeader : PffBLOBHeader; OffsetInBlock : TffWord32; {!!.11} aRelMethod : TffReleaseMethod; begin {$IFDEF BLOBTrace} {!!.11} Logbt('FFTblGetBLOBLength.Begin', []); Logbt(' aBLOBNr = %d:%d', [aBLOBNr.iLow, aBLOBNr.iHigh]); {$ENDIF} aFBError := DBIERR_NONE; { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, aBLOBNr, BLOBBlockNum, OffsetInBlock, aRelMethod); try BLOBHeader := @BLOBBlock^[OffsetInBlock]; {Begin !!.01} { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); {End !!.01} { Verify this is a header segment. } if (BLOBHeader^.bbhSignature <> ffc_SigBLOBSegHeader) then FFRaiseException(EffServerException, ffStrResServer, fferrBadBLOBSeg, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh, format(ffcBLOBSegExpected, [ffcBLOBSegHeader, char(BLOBHeader^.bbhSignature)])]); { What kind of BLOB are we dealing with? } case BLOBHeader^.bbhSegCount of ffc_FileBLOB : { File BLOB } aFBError := FileBLOBLength(BLOBHeader, Result); ffc_BLOBLink : { BLOB link } begin Assert(assigned(aLengthMethod)); aFBError := BLOBLinkGetLength(BLOBHeader, aLengthMethod, Result); end; else { Standard BLOB } Result := BLOBHeader^.bbhBLOBLength; end; finally aRelMethod(BLOBBlock); end; end; {--------} function FFTblGetFileNameBLOB(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; var aFileName : TffFullFileName ) : Boolean; var BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBBlockNum : TffWord32; BLOBHeader : PffBLOBHeader; FileNameLen : Integer; OffsetInBlock : TffWord32; {!!.11} aRelMethod : TffReleaseMethod; begin {read and verify the BLOB header block for this BLOB number} BLOBBlock := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, aBLOBNr, BLOBBlockNum, OffsetInBlock, aRelMethod); BLOBHeader := @BLOBBlock^[OffsetInBlock]; {Begin !!.01} { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); {End !!.01} Result := BLOBHeader^.bbhSegCount = ffc_FileBLOB; if Result then begin {get the length of the file name} Move(BLOBBlock^[(OffsetInBlock + sizeof(TffBLOBHeader))], FileNameLen, 1); {move the file name to aFileName} Move(BLOBBlock^[(OffsetInBlock + sizeof(TffBLOBHeader))], aFileName, succ(FileNameLen)); end; aRelMethod(BLOBBlock); end; {--------} function FFTblIsBLOBLink(aFI : PffFileInfo; {!!.11 - Start} aTI : PffTransInfo; aBLOBNr : TffInt64; var aSrcTableName : TffTableName; var aSrcTableBLOBNr : TffInt64) : Boolean; var BLOBBlock : PffBlock; BLOBHeader : PffBLOBHeader; aHdRelMethod : TffReleaseMethod; BLOBBLockNum : TffWord32; OffsetInBlock : TffWord32; {!!.11} begin { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, aBLOBNr, BLOBBlockNum, OffsetInBlock, aHdRelMethod); try BLOBHeader := @BLOBBlock^[OffsetInBlock]; Result := BLOBHeader^.bbhSegCount = ffc_BLOBLink; if (Result) then BLOBLinkGetTableNameAndRefNr(BLOBBlock, OffsetInBlock, aSrcTableName, aSrcTableBLOBNr); finally aHdRelMethod(BLOBBlock); end; end; {--------} {!!.11 - End} {Begin !!.03} {--------} procedure WriteToStream(const aMsg : string; aStream : TStream); begin aStream.Write(aMsg[1], Length(aMsg)); end; {--------} procedure FFTblListBLOBSegments(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aStream : TStream); var BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBBlockNum : TffWord32; BLOBHeader : PffBLOBHeader; EntryCount : Integer; LookupBlock, ContentBlock : TffWord32; {!!.11} LookupEntry : PffBLOBLookupEntry; ContentEntry : PffBLOBSegmentHeader; {!!.11} LookupSegBlk, ContentSegBlk : PffBlock; {!!.11} LookupSegPtr : PffBLOBSegmentHeader; NextSeg : TffInt64; OffsetInBlock, ContentOffsetInBlock : TffWord32; {!!.11} aLkpRelMethod, aContRelMethod, {!!.11} aHdRelMethod : TffReleaseMethod; begin LookupSegBlk := nil; { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, aBLOBNr, BLOBBlockNum, OffsetInBlock, aHdRelMethod); BLOBHeader := @BLOBBlock^[OffsetInBlock]; { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); { BLOB truncated to length zero? } if BLOBHeader^.bbh1stLookupSeg.iLow = ffc_W32NoValue then begin WriteToStream('BLOB has been truncated to length zero.', aStream); WriteToStream(#0, aStream); Exit; end; try { Are we dealing with a file BLOB or a BLOB link? } case BLOBHeader^.bbhSegCount of ffc_FileBLOB : { file BLOB } begin WriteToStream('This is a file BLOB.', aStream); Exit; end; ffc_BLOBLink : { BLOB link } begin WriteToStream('This is a BLOB link.', aStream); Exit; end; end; { case } { Get the lookup segment block and set up offset for 1st lookup entry. } LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, BLOBHeader^.bbh1stLookupSeg, LookupBlock, OffsetInBlock, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; OffsetInBlock := OffsetInBlock + sizeof(TffBLOBSegmentHeader); { Walk through the BLOB segment linked list. } WriteToStream(Format('Segment list for BLOB %d:%d '+ #13#10, [aBLOBNr.iHigh, aBLOBNr.iLow]), aStream); EntryCount := 0; while True do begin inc(EntryCount); LookupEntry := @LookupSegBlk^[OffsetInBlock]; {Begin !!.11} { Verify the segment is valid. } ContentSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, LookupEntry^.bleSegmentOffset, ContentBlock, ContentOffsetInBlock, aContRelMethod); ContentEntry := @ContentSegBlk^[ContentOffsetInBlock]; if PffBlockHeaderBLOB(ContentSegBlk)^.bhbSignature <> ffc_SigBLOBBlock then raise Exception.CreateFmt ('Invalid BLOB block signature, block: %d', [ContentBlock]) else if ContentEntry^.bshSignature <> ffc_SigBLOBSegContent then raise Exception.CreateFmt ('Invalid signature for content segment, offset: %d,%d, signature: %s', [LookupEntry^.bleSegmentOffset.iHigh, LookupEntry^.bleSegmentOffset.iLow, char(ContentEntry^.bshSignature)]) else begin WriteToStream(Format('Segment %d, %d:%d, Len %d' + #13#10, [EntryCount, LookupEntry^.bleSegmentOffset.iHigh, LookupEntry^.bleSegmentOffset.iLow, LookupEntry^.bleContentLength]), aStream); {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); LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, NextSeg, {!!.11} LookupBlock, OffsetInBlock, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; OffsetInBlock := OffsetInBlock + sizeof(TffBLOBSegmentHeader); EntryCount := 0; end else break; end else OffsetInBlock := OffsetInBlock + sizeof(TffBLOBLookupEntry); end; {End !!.11} end; {while} finally if assigned(LookupSegBlk) then aLkpRelMethod(LookupSegBlk); aHdRelMethod(BLOBBlock); WriteToStream(#0, aStream); end; end; { End !!.03} {Begin !!.11} {--------} function FFTblRebuildLookupSegments(aFI : PffFileInfo; aTI : PffTransInfo; aNewBLOBSize : TffWord32; aOldBLOBSize : TffWord32; const aBLOBNr : TffInt64) : TffInt64; {This function takes an existing lookup segment chain & grows it to accomodate a larger BLOB. } var NewBLOBBlock : PffBlock; NewLookupHeader : PffBLOBSegmentHeader; OldBLOBBlock : PffBlock; OldLookupHeader : PffBLOBSegmentHeader; OldLookupEntry : PffBLOBLookupEntry; OldLookupBlk : PffBlock; OldLookupOfs : TffWord32; OldBLOBHeader : PffBLOBHeader; NewSegCount : TffWord32; OldSegCount : Longint; SegBytesUsed : TffWord32; EntriesToGo : Longint; MaxEntries : Longint; NewOfsInBlock : TffWord32; OldOfsInBlock : TffWord32; EntInOldSeg : Longint; EntInNewSeg : Longint; CurrentCount : Longint; OldHeaderOfs : TffInt64; TempI64 : TffInt64; aRelMethod : TffReleaseMethod; aRelList : TffPointerList; SegSize : TffWord32; begin { We use the following list to track the RAM pages we've accessed and the release method associated with each RAM page. At the end of this routine, we will call the release method for each RAM page. } aRelList := TffPointerList.Create; try { Get the old lookup header before we replace it with a new one. } OldBLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OldOfsInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(OldBLOBBlock, TffInt64(aRelMethod))); OldBLOBHeader := PffBLOBHeader(@OldBLOBBlock^[OldOfsInBlock]); OldHeaderOfs := OldBLOBHeader^.bbh1stLookupSeg; { Determine number of segments needed to hold the entire BLOB. } NewSegCount := CalcBLOBSegNumber(aNewBLOBSize, aFI^.fiBlockSize, SegBytesUsed); { Can the number of lookup entries required for the number of segments fit within one lookup segment? } if ((NewSegCount * ffc_BLOBLookupEntrySize) <= (aFI^.fiMaxSegSize - ffc_BLOBSegmentHeaderSize)) then begin { Yes. Create a new lookup segment. } SegSize := (NewSegCount * ffc_BLOBLookupEntrySize) + ffc_BLOBSegmentHeaderSize; Result := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, SegSize); NewBLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, Result, NewOfsInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(NewBLOBBlock, TffInt64(aRelMethod))); NewLookupHeader := @NewBLOBBlock^[NewOfsInBlock]; { Setup our new lookup header. } with NewLookupHeader^ do begin bshSignature := ffc_SigBLOBSegLookup; bshParentBLOB := aBLOBNr; bshNextSegment.iLow := ffc_W32NoValue; end; end else begin { No. We need a chain of lookup segments. } EntriesToGo := NewSegCount; MaxEntries := (aFI^.fiMaxSegSize - ffc_BLOBSegmentHeaderSize) div ffc_BLOBLookupEntrySize; SegSize := (MaxEntries * ffc_BLOBLookupEntrySize) + ffc_BLOBSegmentHeaderSize; Result := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, SegSize); dec(EntriesToGo, MaxEntries); NewBLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, Result, NewOfsInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(NewBLOBBlock, TffInt64(aRelMethod))); NewLookupHeader := @NewBLOBBlock^[NewOfsInBlock]; NewLookupHeader^.bshSignature := ffc_SigBLOBSegHeader; NewLookupHeader^.bshParentBLOB := aBLOBNr; while EntriesToGo > 0 do begin if EntriesToGo > MaxEntries then begin { We need this lookup segment & at least one more. } SegSize := (MaxEntries * ffc_BLOBLookupEntrySize) + ffc_BLOBSegmentHeaderSize; NewLookupHeader^.bshNextSegment := aFI^.fiBLOBrscMgr.NewSegment (aFI, aTI, SegSize, SegSize); dec(EntriesToGo, MaxEntries); NewBLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, NewLookupHeader^.bshNextSegment, NewOfsInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(NewBLOBBlock, TffInt64(aRelMethod))); end else begin { This is the last lookup segment needed. } SegSize := (EntriesToGo * ffc_BLOBLookupEntrySize) + ffc_BLOBSegmentHeaderSize; NewLookupHeader^.bshNextSegment := aFI^.fiBLOBrscMgr.NewSegment (aFI, aTI, SegSize, SegSize); dec(EntriesToGo, EntriesToGo); NewBLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, NewLookupHeader^.bshNextSegment, NewOfsInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(NewBLOBBlock, TffInt64(aRelMethod))); end; {if..else} { Initialize the segment. } NewLookupHeader := @NewBLOBBlock^[NewOfsInBlock]; NewLookupHeader^.bshSignature := ffc_SigBLOBSegHeader; NewLookupHeader^.bshParentBLOB := aBLOBNr; NewLookupHeader^.bshNextSegment.iLow := ffc_W32NoValue; end; {while} {Reset the new lookup segment to the 1st one in the chain.} NewBLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, Result, NewOfsInBlock, aRelMethod); NewLookupHeader := @NewBLOBBlock^[NewOfsInBlock]; end; {if..else} { Now that we have our newly-sized lookup header(s) and entries, we need to copy the old entries into the new header. } if aOldBLOBSize = 0 then OldSegCount := 0 else OldSegCount := CalcBLOBSegNumber(aOldBLOBSize, aFI^.fiBlockSize, SegBytesUsed); if OldSegCount <> 0 then begin OldLookupBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, OldBLOBHeader^.bbh1stLookupSeg, OldLookupOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(OldLookupBlk, TffInt64(aRelMethod))); OldLookupHeader := @OldLookupBlk^[OldLookupOfs]; OldLookupOfs := OldLookupOfs + sizeof(TffBLOBSegmentHeader); { Point to the 1st lookup entry. } OldLookupEntry := PffBLOBLookupEntry(@OldLookupBlk^[OldLookupOfs]); { Get the block offset to where the first new lookup entry goes. } NewBLOBBlock := ReadBLOBBlock(aFI, aTI, Result, NewOfsInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(NewBLOBBlock, TffInt64(aRelMethod))); NewOfsInBlock := NewOfsInBlock + sizeof(TffBLOBSegmentHeader); { Is the old lookup segment followed by another lookup segment? } if OldLookupHeader^.bshNextSegment.iLow <> ffc_W32NoValue then { Yes. It must have the maximum number of lookup entries so figure out how many that is. } EntInOldSeg := FFCalcMaxLookupEntries(OldLookupHeader) else { No. The number of lookup entries equals the number of segments in the BLOB. } EntInOldSeg := OldSegCount; { Figure out the maximum number of entries for the new lookup segment. } EntInNewSeg := FFCalcMaxLookupEntries(NewLookupHeader); CurrentCount := 0; while CurrentCount < OldSegCount do begin { Move over all lookup entries from the old lookup segment to the new lookup segment. } Move(OldLookupEntry^, NewBLOBBlock^[NewOfsInBlock], EntInOldSeg * sizeof(TffBLOBLookupEntry)); inc(CurrentCount, EntInOldSeg); dec(EntInNewSeg, EntInOldSeg); { Save a pointer to the beginning of our old lookup segment. We will need it to delete the lookup segment later. } TempI64 := OldHeaderOfs; { Is there a lookup segment after this one? } if OldLookupHeader^.bshNextSegment.iLow <> ffc_W32NoValue then begin { Yes. Move to it. } OldHeaderOfs := OldLookupHeader^.bshNextSegment; OldBLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, OldHeaderOfs, OldLookupOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(OldBLOBBlock, TffInt64(aRelMethod))); OldLookupBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, OldLookupHeader^.bshNextSegment, OldLookupOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(OldLookupBlk, TffInt64(aRelMethod))); OldLookupHeader := @OldLookupBlk^[OldLookupOfs]; inc(OldLookupOfs, sizeof(TffBLOBSegmentHeader)); OldLookupEntry := PffBLOBLookupEntry(@OldBLOBBlock^[OldLookupOfs]); { Since the lookup segment was followed by another lookup segment, we know this is a max-size segment full of entries. } EntInOldSeg := FFCalcMaxLookupEntries(OldLookupHeader); end; { Delete the old lookup segment now that we have copied all its entries. } aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, TempI64); { Check if we've filled up our current (target) header} if (EntInNewSeg = 0) and (NewLookupHeader^.bshNextSegment.iLow <> ffc_W32NoValue) then begin NewBLOBBlock := ReadBlobBlock(aFI, aTI, NewLookupHeader^.bshNextSegment, NewOfsInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(NewBLOBBlock, TffInt64(aRelMethod))); NewLookupHeader := @NewBLOBBlock^[NewOfsInBlock]; NewOfsInBlock := NewOfsInBlock + sizeof(TffBLOBSegmentHeader); EntInNewSeg := FFCalcMaxLookupEntries(NewLookupHeader); end; end; {while} end; {if} OldBLOBHeader^.bbh1stLookupSeg := Result; finally for CurrentCount := 0 to pred(aRelList.Count) do begin FFDeallocReleaseInfo(aRelList[CurrentCount]); end; aRelList.Free; end; end; {====================================================================} {===TffBaseBLOBEngine================================================} class function TffBaseBLOBEngine.GetEngine(aFI : PffFileInfo) : TffBaseBLOBEngine; begin if aFI.fiFFVersion <= ffVersion2_10 then Result := FF210BLOBEngine else Result := FFBLOBEngine; end; {====================================================================} {===TffBLOBEngine====================================================} procedure TffBLOBEngine.Read(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aOffset : TffWord32; aLen : TffWord32; aReadMethod : TffBLOBLinkRead; var aBLOB; var aBytesRead : TffWord32; var aFBError : TffResult); var aCntRelMethod, aLkpRelMethod, aHdRelMethod : TffReleaseMethod; BLOBAsBytes : PffBLOBArray; BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBBlockNum : TffWord32; BLOBHeader : PffBLOBHeader; BytesToCopy : TffWord32; ContentBlock, LookupSegOfs : TffWord32; ContentSegBlk : PffBlock; ContentSegOfs : TffInt64; DestOffset : TffWord32; MaxLookupEntries : Integer; LookupBlock : TffWord32; LookupEntry : PffBLOBLookupEntry; LookupSegBlk : PffBlock; LookupSegPtr : PffBLOBSegmentHeader; OffsetInBlock : TffWord32; StartBytesUsed, BLOBPos : TffWord32; CurrLookupEntry : Integer; {$IFDEF BLOBTrace} LookupSegCount : Integer; {$ENDIF} NextSeg : TffInt64; {!!.11} begin {$IFDEF BLOBTrace} Logbt('FFTblReadBLOB.Begin', []); Logbt(' aBLOBNr = %d:%d', [aBLOBNr.iLow, aBLOBNr.iHigh]); Logbt(' aOffset = %d', [aOffset]); Logbt(' aLen = %d', [aLen]); try {$ENDIF} BLOBAsBytes := @aBLOB; ContentSegBlk := nil; LookupSegBlk := nil; DestOffset := 0; aFBError := 0; {Exit if aLen = 0} if aLen = 0 then Exit; { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, aBLOBNr, BLOBBlockNum, OffsetInBlock, aHdRelMethod); BLOBHeader := @BLOBBlock^[OffsetInBlock]; {$IFDEF BLOBTrace} Logbt(' BLOB.Length: %d, 1st lookup segment: %d:%d', [BLOBHeader^.bbhBLOBLength, BLOBHeader^.bbh1stLookupSeg.iLow, BLOBHeader^.bbh1stLookupSeg.iHigh]); {$ENDIF} { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); try { Are we dealing with a file BLOB or a BLOB link? } case BLOBHeader^.bbhSegCount of ffc_FileBLOB : { file BLOB } begin aFBError := FileBLOBRead(aFI, aTI, aBLOBNr, aOffset, aLen, aBLOB, aBytesRead); Exit; end; ffc_BLOBLink : { BLOB link } begin aFBError := BLOBLinkRead(aFI, aTI, aBLOBNr, aOffset, aLen, aReadMethod, aBLOB, aBytesRead); Exit; end; end; { case } { Make sure that the offset is within BLOB. } if (FFCmpDW(aOffset, BLOBHeader^.bbhBLOBLength) >= 0) then begin aBytesRead := 0; Exit; end; { Get the lookup segment block and set up offset for 1st lookup entry. } LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, BLOBHeader^.bbh1stLookupSeg, LookupBlock, LookupSegOfs, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; LookupSegOfs := LookupSegOfs + ffc_BLOBSegmentHeaderSize; { Calculate the number of bytes we can (= "are going to") read. } aBytesRead := ffMinDW(aLen, BLOBHeader^.bbhBLOBLength - aOffset); { How many entries are in the current lookup segment? } MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); CurrLookupEntry := 1; {$IFDEF BLOBTrace} LookupSegCount := 1; Logbt(' Lookup segment - Max entries: %d', [MaxLookupEntries]); {$ENDIF} { Position to where we are to start reading. } BLOBPos := 0; StartBytesUsed := 0; while (BLOBPos < aOffset) do begin LookupEntry := @LookupSegBlk^[LookupSegOfs]; {$IFDEF BLOBTrace} Logbt(' Lookup entry %d points to ' + 'segment %d:%d with %d bytes', [CurrLookupEntry, LookupEntry^.bleSegmentOffset.iHigh, LookupEntry^.bleSegmentOffset.iLow, LookupEntry^.bleContentLength]); {$ENDIF} { Does this entry point to the segment where we should start copying data? } if ((BLOBPos + LookupEntry^.bleContentLength) >= aOffset) then begin { Yes. We found the starting point. } ContentSegOfs := LookupEntry^.bleSegmentOffset; StartBytesUsed := aOffset - BLOBPos; { NOTE: We will start reading from this segment, so we don't want to move past it. } Break; end else begin { Nope. Update and keep moving. } BLOBPos := BLOBPos + LookupEntry^.bleContentLength; LookupSegOfs := LookupSegOfs + ffc_BLOBLookupEntrySize; CurrLookupEntry := CurrLookupEntry + 1; end; { Have we reached the end of this lookup segment? } if (CurrLookupEntry > MaxLookupEntries) then begin { Get the lookup segment block and set up offset for 1st lookup entry. } NextSeg := LookupSegPtr^.bshNextSegment; {!!.11} aLkpRelMethod(LookupSegBlk); LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, NextSeg, {!!.11} LookupBlock, LookupSegOfs, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; LookupSegOfs := LookupSegOfs + ffc_BLOBSegmentHeaderSize; { How many entries are in the current lookup segment? } MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); CurrLookupEntry := 1; {$IFDEF BLOBTrace} LookupSegCount := LookupSegCount + 1; Logbt(' Moving to lookup segment %d', [LookupSegCount]); Logbt(' Lookup segment - Max entries: %d', [MaxLookupEntries]); {$ENDIF} end; end; { Read what we need. } BLOBPos := 0; while (BLOBPos < aBytesRead) do begin { Read the BLOB content segment. } if (ContentSegBlk <> nil) then aCntRelMethod(ContentSegBlk); LookupEntry := @LookupSegBlk^[LookupSegOfs]; {$IFDEF BLOBTrace} Logbt(' Lookup entry %d points to segment %d:%d with %d bytes', [CurrLookupEntry, LookupEntry^.bleSegmentOffset.iHigh, LookupEntry^.bleSegmentOffset.iLow, LookupEntry^.bleContentLength]); {$ENDIF} ContentSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, LookupEntry^.bleSegmentOffset, ContentBlock, OffsetInBlock, aCntRelMethod); OffsetInBlock := OffsetInBlock + ffc_BLOBSegmentHeaderSize; if (StartBytesUsed > 0) then begin { This is the first segment we're reading from. This will normally be in the middle of a segment. } BytesToCopy := LookupEntry^.bleContentLength - StartBytesUsed; OffsetInBlock := OffsetInBlock + StartBytesUsed; end else begin { copying from middle segments } BytesToCopy := LookupEntry^.bleContentLength; end; BytesToCopy := ffMinL(BytesToCopy, (aBytesRead - BLOBPos)); Move(ContentSegBlk^[OffsetInBlock], BLOBAsBytes^[DestOffset], BytesToCopy); BLOBPos := BLOBPos + BytesToCopy; DestOffset := DestOffset + BytesToCopy; {$IFDEF BLOBTrace} Logbt(' Read %d bytes from lookup segment %d, entry %d', [BytesToCopy, LookupSegCount, CurrLookupEntry]); {$ENDIF} CurrLookupEntry := CurrLookupEntry + 1; StartBytesUsed := 0; { Have we reached the end of this lookup segment? } if ((BLOBPos < aBytesRead) and (CurrLookupEntry > MaxLookupEntries)) then begin NextSeg := LookupSegPtr^.bshNextSegment; {!!.11} aLkpRelMethod(LookupSegBlk); { Get the lookup segment block and set up offset for 1st lookup entry. } LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, NextSeg, {!!.11} LookupBlock, LookupSegOfs, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; LookupSegOfs := LookupSegOfs + ffc_BLOBSegmentHeaderSize; { How many entries are in the current lookup segment? } MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); CurrLookupEntry := 1; {$IFDEF BLOBTrace} LookupSegCount := LookupSegCount + 1; Logbt(' Moving to lookup segment %d', [LookupSegCount]); Logbt(' Lookup segment - Max entries: %d', [MaxLookupEntries]); {$ENDIF} end else begin LookupSegOfs := LookupSegOfs + ffc_BLOBLookupEntrySize; end; end; {while} finally if assigned(ContentSegBlk) then aCntRelMethod(ContentSegBlk); if assigned(LookupSegBlk) then aLkpRelMethod(LookupSegBlk); aHdRelMethod(BLOBBlock); end; {$IFDEF BLOBTrace} except Logbt('*** FFTblReadBLOB Exception ***', []); raise; end {$ENDIF} end; {--------} function TffBLOBEngine.IsEmptyLookupEntry(Entry : PffBLOBLookupEntry) : Boolean; { Revised !!.13} const ciEmptyVal1 = 808464432; { This is because lookup segments prior to 2.13 were 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 = ffc_W32NoValue) or ((Entry^.bleSegmentOffset.iLow = 0) and (Entry^.bleSegmentOffset.iHigh = 0)) or ((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 TffBLOBEngine.Truncate(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aLen : TffWord32); {Updated !!.12} var aRelList : TffPointerList; // aLkpRelMethod, {Deleted !!.13} aRelMethod : TffReleaseMethod; NextLookupSeg : TffInt64; ContOffset, {!!.13} BLOBPos, CurrLookupEntry, LookupBlock, MaxLookupEntries, OffsetInBlock, StartBytesUsed : TffWord32; NewSegCount : Integer; BLOBBlock : PffBlock; ContentSegBlk, {!!.13} LookupSegBlk : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBHeader : PffBLOBHeader; {Begin !!.13} ContentSegOfs : TffInt64; ContentSegPtr, {End !!.13} LookupSegPtr : PffBLOBSegmentHeader; LookupEntry : PffBLOBLookupEntry; {$IFDEF BLOBTrace} LookupSegCount : Integer; {$ENDIF} begin {$IFDEF BLOBTrace} Logbt('Entering FFTblTruncateBLOB', []); Logbt(' aBLOBNr = %d:%d', [aBLOBNr.iLow, aBLOBNr.iHigh]); Logbt(' aLen = %d', [aLen]); LookupSegCount := 1; {$ENDIF} // aLkpRelMethod := nil; {Deleted !!.13} { We use the following list to track the RAM pages we've accessed and the release method associated with each RAM page. At the end of this routine, we will call the release method for each RAM page. } aRelList := TffPointerList.Create; try { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(BLOBBlock,TffInt64(aRelMethod))); BLOBHeader := @BLOBBlock^[OffsetInBlock]; { Check if we're trying to truncate a zero-length BLOB or to the BLOB's current length. } if (BLOBHeader^.bbhBLOBLength = aLen) then Exit; { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); { Verify this is a header segment. } if (BLOBHeader^.bbhSignature <> ffc_SigBLOBSegHeader) then FFRaiseException(EffServerException, ffStrResServer, fferrBadBLOBSeg, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh, Format(ffcBLOBSegExpected, [ffcBLOBSegHeader, Char(BLOBHeader^.bbhSignature)])]); { We can't write to a file BLOB. } if (BLOBHeader^.bbhSegCount = -1) then FFRaiseException(EffServerException, ffStrResServer, fferrFileBLOBWrite, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh]); { Make sure the truncated length <= current BLOB length. } if (aLen > BLOBHeader^.bbhBLOBLength) then FFRaiseException(EffServerException, ffStrResServer, fferrLenMismatch, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh, aLen, BLOBHeader^.bbhBLOBLength]); { If the new length is greater than 0, we will lop off some content segments. The content segment that becomes the last content segment must be updated. } NewSegCount := 0; if (aLen > 0) then begin { Grab the first lookup segment. } NextLookupSeg := BLOBHeader^.bbh1stLookupSeg; LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_MarkDirty, {!!.13} NextLookupSeg, LookupBlock, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(LookupSegBlk,TffInt64(aRelMethod))); LookupSegPtr := PffBLOBSegmentHeader(@LookupSegBlk^[OffsetInBlock]); MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); OffsetInBlock := OffsetInBlock + ffc_BLOBSegmentHeaderSize; CurrLookupEntry := 1; { Position to where we are to start truncating. } BLOBPos := 0; StartBytesUsed := 0; while (BLOBPos < aLen) do begin NewSegCount := NewSegCount + 1; LookupEntry := @LookupSegBlk^[OffsetInBlock]; {$IFDEF BLOBTrace} Logbt(' Lookup entry %d points to a segment with %d bytes', [CurrLookupEntry, LookupEntry^.bleContentLength]); {$ENDIF} if ((BLOBPos + LookupEntry^.bleContentLength) >= aLen) then begin {!!.13} { We found the starting point. } StartBytesUsed := aLen - BLOBPos; Break; end else begin BLOBPos := BLOBPos + LookupEntry^.bleContentLength; CurrLookupEntry := CurrLookupEntry + 1; end; { Have we reached the end of this lookup segment? } if ((BLOBPos < aLen) and (CurrLookupEntry > MaxLookupEntries)) then begin { Get the lookup segment block and set up offset for 1st lookup entry. } NextLookupSeg := LookupSegPtr^.bshNextSegment; // if Assigned(aLkpRelMethod) then {Deleted !!.13} // aLkpRelMethod(LookupSegBlk); {Deleted !!.13} LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_MarkDirty, {!!.13} NextLookupSeg, LookupBlock, OffsetInBlock, aRelMethod); {!!.13} aRelList.Append(FFAllocReleaseInfo(LookupSegBlk,TffInt64(aRelMethod))); {!!.13} LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; OffsetInBlock := OffsetInBlock + ffc_BLOBSegmentHeaderSize; { How many entries are in the current lookup segment? } MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); CurrLookupEntry := 1; {$IFDEF BLOBTrace} LookupSegCount := LookupSegCount + 1; Logbt(' Moving to lookup segment %d', [LookupSegCount]); Logbt(' Lookup segment - Max entries: %d', [MaxLookupEntries]); {$ENDIF} end else OffsetInBlock := OffsetInBlock + ffc_BLOBLookupEntrySize; end; { while } { We should now be positioned on the last lookup entry to be retained by the truncation. Update the length of its content segment. } LookupEntry := @LookupSegBlk^[OffsetInBlock]; BLOBPos := BLOBPos + LookupEntry^.bleContentLength; LookupEntry^.bleContentLength := StartBytesUsed; {Begin !!.13} { Update the content segment's NextSegment pointer. } ContentSegOfs := LookupEntry^.bleSegmentOffset; ContentSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, ContentSegOfs, ContOffset, aRelMethod); aRelList.Append(FFAllocReleaseInfo(ContentSegBlk,TffInt64(aRelMethod))); {!!.13} ContentSegPtr := @ContentSegBlk^[ContOffset]; ContentSegPtr^.bshNextSegment.iLow := ffc_W32NoValue; {End !!.13} { Delete the content & lookup segments that are no longer needed. First, obtain the number of extraneous lookup entries in the current lookup segment. } while (BLOBPos < BLOBHeader^.bbhBLOBLength) do begin CurrLookupEntry := CurrLookupEntry + 1; OffsetInBlock := OffsetInBlock + ffc_BLOBLookupEntrySize; LookupEntry := @LookupSegBlk^[OffsetInBlock]; { Have we reached the end of this lookup segment? } if (CurrLookupEntry > MaxLookupEntries) then begin if LookupSegPtr^.bshNextSegment.iLow = ffc_W32NoValue then Break else begin { Get the lookup segment block and set up offset for 1st lookup entry. } NextLookupSeg := LookupSegPtr^.bshNextSegment; // if Assigned(aLkpRelMethod) then {Deleted !!.13} // aLkpRelMethod(LookupSegBlk); {Deleted !!.13} LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_MarkDirty, NextLookupSeg, LookupBlock, OffsetInBlock, aRelMethod); {!!.13} aRelList.Append(FFAllocReleaseInfo(LookupSegBlk,TffInt64(aRelMethod))); {!!.13} LookupSegPtr^.bshNextSegment.iLow := ffc_W32NoValue; LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; { Move ahead to first lookup entry. } OffsetInBlock := OffSetInBlock + ffc_BLOBSegmentHeaderSize; LookupEntry := @LookupSegBlk^[OffsetInBlock]; { How many entries are in the current lookup segment? } MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); CurrLookupEntry := 1; {$IFDEF BLOBTrace} LookupSegCount := LookupSegCount + 1; Logbt(' Moving to lookup segment %d', [LookupSegCount]); Logbt(' Lookup segment - Max entries: %d', [MaxLookupEntries]); {$ENDIF} end end else if IsEmptyLookupEntry(LookupEntry) then { Have we encountered an empty lookup segment? If so then this indicates the end of the BLOB content. } Break; if (StartBytesUsed = 0) then BLOBPos := BLOBPos + LookupEntry^.bleContentLength else StartBytesUsed := 0; aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, LookupEntry^.bleSegmentOffset); FillChar(LookupEntry^, ffc_BLOBLookupEntrySize, 0); {!!.13} end; { while } LookupSegPtr^.bshNextSegment.iLow := ffc_W32NoValue; end else begin { We are truncating to length of 0. } FFTblDeleteBLOBPrim(aFI, aTI, BLOBHeader); { Reset the lookup segment field and the segment count. FFTblFreeBLOB will get rid of the BLOB header if the BLOB is still at length 0. } BLOBHeader^.bbh1stLookupSeg.iLow := ffc_W32NoValue; end; { Set the new BLOB length and segment count in the BLOB header. } BLOBHeader^.bbhBLOBLength := aLen; { Set the new segment count in the BLOB header. } BLOBHeader^.bbhSegCount := NewSegCount; finally for OffsetInBlock := 0 to (aRelList.Count - 1) do FFDeallocReleaseInfo(aRelList[OffsetInBlock]); aRelList.Free; end; end; {--------} procedure TffBLOBEngine.Write(aFI : PffFileInfo; aTI : PffTransInfo; const aBLOBNr : TffInt64; aOffset : TffWord32; {offset in blob to start writing} aLen : TffWord32; {bytes from aOffset to stop writing} const aBLOB); var aLkpRelMethod, aRelMethod : TffReleaseMethod; aRelList : TffPointerList; ContentSegOfs : TffInt64; BLOBPos, BytesCopied, BytesToCopy, BytesToGo, CurrLookupEntry, LookupBlock, LookupEntOfs, LookupSegOfs, MaxLookupEntries, NewSize, OffsetInBlock, SegBytesLeft, SegSize, StartBytesUsed, TargetOffset, TempWord : TffWord32; MinSegSize : Integer; BLOBBlock, ContentSegBlk, LookupSegBlk, PrevContSegBlk : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBHeader : PffBLOBHeader; BLOBAsBytes : PffBLOBArray; LookupEntry : PffBLOBLookupEntry; ContentSegPtr, LookupSegPtr, PrevContentSegPtr, TempSegPtr : PffBLOBSegmentHeader; NewSegment : Boolean; {$IFDEF BLOBTrace} LookupSegCount : Integer; {$ENDIF} NextSeg : TffInt64; {!!.11} begin {$IFDEF BLOBTrace} Logbt('Entering FFTblWriteBLOB', []); Logbt(' aBLOBNr = %d:%d', [aBLOBNr.iLow, aBLOBNr.iHigh]); Logbt(' aOffset = %d', [aOffset]); Logbt(' aLen = %d', [aLen]); try {$ENDIF} BLOBAsBytes := @aBLOB; ContentSegOfs.iLow := ffc_W32NoValue; LookupSegBlk := nil; { We use the following list to track the RAM pages we've accessed and the release method associated with each RAM page. At the end of this routine, we will call the release method for each RAM page. } aRelList := TffPointerList.Create; try { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(BLOBBlock, TffInt64(aRelMethod))); BLOBHeader := @BLOBBlock^[OffsetInBlock]; { Verify the new length (aLen + aOffset) doesn't exceed max. } NewSize := FFMaxL(aOffset + aLen, BLOBHeader^.bbhBLOBLength); if (NewSize > ffcl_MaxBLOBLength) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBTooBig, [NewSize]); { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); { For a file BLOB raise an error. } if (BLOBHeader^.bbhSegCount = -1) then FFRaiseException(EffServerException, ffStrResServer, fferrFileBLOBWrite, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh]); { Verify the offset is within, or at the end of, the BLOB. } if (aOffset > BLOBHeader^.bbhBLOBLength) then FFRaiseException(EffServerException, ffStrResServer, fferrOfsNotInBlob, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh, aOffset, BLOBHeader^.bbhBLOBLength]); { If there's not one, we'll need a lookup segment. } if (BLOBHeader^.bbh1stLookupSeg.iLow = ffc_W32NoValue) then begin NewSegment := True; TempWord := EstimateSegmentCount(NewSize, aFI^.fiMaxSegSize); TempWord := (TempWord * ffc_BLOBLookupEntrySize) + ffc_BLOBSegmentHeaderSize; TempWord := FFMinDW(TempWord, aFI^.fiMaxSegSize); BLOBHeader^.bbh1stLookupSeg := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, TempWord, (TempWord div 2)); {$IFDEF BLOBTrace} Logbt(' Built first lookup segment: %d:%d', [BLOBHeader^.bbh1stLookupSeg.iLow, BLOBHeader^.bbh1stLookupSeg.iHigh]); {$ENDIF} end else begin NewSegment := False; {$IFDEF BLOBTrace} Logbt(' First lookup segment established: %d:%d', [BLOBHeader^.bbh1stLookupSeg.iLow, BLOBHeader^.bbh1stLookupSeg.iHigh]); {$ENDIF} end; { Get the first lookup segment. } LookupSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, BLOBHeader^.bbh1stLookupSeg, LookupSegOfs, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; if (NewSegment) then begin LookupSegPtr^.bshParentBLOB := aBLOBNr; LookupSegPtr^.bshSignature := ffc_SigBLOBSegLookup; LookupSegPtr^.bshNextSegment.iLow := ffc_W32NoValue; end; MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); LookupEntOfs := LookupSegOfs + SizeOf(TffBLOBSegmentHeader); CurrLookupEntry := 1; {$IFDEF BLOBTrace} LookupSegCount := 1; Logbt(' Lookup segment - Max entries: %d', [MaxLookupEntries]); {$ENDIF} { Position to where we are to start writing. } BLOBPos := 0; LookupEntry := nil; StartBytesUsed := 0; while (BLOBPos < aOffset) do begin LookupEntry := @LookupSegBlk^[LookupEntOfs]; {$IFDEF BLOBTrace} Logbt(' Lookup entry %d points to a segment with %d bytes', [CurrLookupEntry, LookupEntry^.bleContentLength]); {$ENDIF} { Does this entry point to the segment where we should start copying data? } if ((BLOBPos + LookupEntry^.bleContentLength) >= aOffset) then begin { Yes. We found the starting point. } ContentSegOfs := LookupEntry^.bleSegmentOffset; StartBytesUsed := aOffset - BLOBPos; { NOTE: We will be making updates to this segment, so we don't want to move past it. } Break; end else begin { Nope. Update and keep moving. } BLOBPos := BLOBPos + LookupEntry^.bleContentLength; LookupEntOfs := LookupEntOfs + ffc_BLOBLookupEntrySize; CurrLookupEntry := CurrLookupEntry + 1; end; { Have we reached the end of this lookup segment? } if (CurrLookupEntry > MaxLookupEntries) then begin { Get the lookup segment block and set up offset for 1st lookup entry. } NextSeg := LookupSegPtr^.bshNextSegment; {!!.11} aLkpRelMethod(LookupSegBlk); LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_MarkDirty, NextSeg, {!!.11} LookupBlock, LookupSegOfs, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; LookupEntOfs := LookupSegOfs + SizeOf(TffBLOBSegmentHeader); { How many entries are in the current lookup segment? } MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); CurrLookupEntry := 1; {$IFDEF BLOBTrace} LookupSegCount := LookupSegCount + 1; Logbt(' Moving to lookup segment %d', [LookupSegCount]); Logbt(' Lookup segment - Max entries: %d', [MaxLookupEntries]); {$ENDIF} end; end; { We may need to initialize the previous content segment so that we can maintain the chain. } if ((BLOBPos = 0) and (BLOBHeader^.bbhBLOBLength > 0)) then begin LookupEntry := @LookupSegBlk^[LookupEntOfs]; ContentSegOfs := LookupEntry^.bleSegmentOffset; end; ContentSegPtr := nil; if (ContentSegOfs.iLow <> ffc_W32NoValue) then begin { Get the previous content segment. } ContentSegOfs := LookupEntry^.bleSegmentOffset; ContentSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, ContentSegOfs, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(ContentSegBlk, TffInt64(aRelMethod))); ContentSegPtr := @ContentSegBlk^[OffsetInBlock]; {$IFDEF BLOBTrace} Logbt(' Initialized 1st content segment to write to: %d:%d', [ContentSegOfs.iLow, ContentSegOfs.iHigh]); Logbt(' Total segment length: %d', [ContentSegPtr^.bshSegmentLen]); Logbt(' Bytes to keep: %d', [StartBytesUsed]); {$ENDIF} end; { I've been using BLOBPos to track where I was at in the existing BLOB, if any. Now, I'm going to be using it to track where we are in the source (data being added to the BLOB). } BLOBPos := 0; { Now we're positioned and ready to start copying the source data to the BLOB. } BytesToGo := aLen; while (BytesToGo > 0) do begin { Are we overwriting an existing segment? } if (ContentSegOfs.iLow <> ffc_W32NoValue) then begin { Yes. Get the location of the existing segment so we can update it. } BytesToCopy := BytesToGo; {$IFDEF BLOBTrace} Logbt(' Updating existing segment: %d:%d.', [ContentSegOfs.iLow, ContentSegOfs.iHigh]); {$ENDIF} end else begin { Nope. We'll have to intialize a new lookup entry and get a new content segment. } NewSegment := True; LookupEntry := @LookupSegBlk^[LookupEntOfs]; { Update the previous content segment so we can chain it to the next one later. } PrevContentSegPtr := ContentSegPtr; { Figure out how many bytes we "want" to copy. } BytesToCopy := ffMinL(aFI^.fiMaxSegSize, BytesToGo); { Get a new content segment} SegSize := BytesToCopy; MinSegSize := ffc_BLOBSegmentIncrement; ContentSegOfs := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, MinSegSize); LookupEntry^.bleSegmentOffset := ContentSegOfs; LookupEntry^.bleContentLength := 0; { Increment the segment count. } BLOBHeader^.bbhSegCount := BLOBHeader^.bbhSegCount + 1; if (PrevContentSegPtr <> nil) then begin PrevContentSegPtr^.bshNextSegment := ContentSegOfs; end; {$IFDEF BLOBTrace} Logbt(' Created new segment: %d:%d.', [ContentSegOfs.iLow, ContentSegOfs.iHigh]); {$ENDIF} end; { Get the content segment. } ContentSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, ContentSegOfs, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(ContentSegBlk, TffInt64(aRelMethod))); ContentSegPtr := @ContentSegBlk^[OffsetInBlock]; if (NewSegment) then begin ContentSegPtr^.bshSignature := ffc_SigBLOBSegContent; ContentSegPtr^.bshParentBLOB := aBLOBNr; ContentSegPtr^.bshNextSegment.iLow := ffc_W32NoValue; NewSegment := False; end; { We may not have gotten an optimal size segment, so we need to update how many bytes we can copy based on the actual segment size. } StartBytesUsed := StartBytesUsed + ffc_BLOBSegmentHeaderSize; TargetOffset := OffsetInBlock + StartBytesUsed; SegBytesLeft := ContentSegPtr^.bshSegmentLen - StartBytesUsed; BytesToCopy := FFMinL(BytesToCopy, SegBytesLeft); { Copy. } Move(BLOBAsBytes^[BLOBPos], ContentSegBlk^[TargetOffset], BytesToCopy); BytesToGo := BytesToGo - BytesToCopy; BLOBPos := BLOBPos + BytesToCopy; Assert(BytesToGo <= aLen, 'BLOB writing is out of whack'); {$IFDEF BLOBTrace} Logbt(' Copied %d bytes to lookup segment %d, entry %d, content segment %d:%d', [BytesToCopy, LookupSegCount, CurrLookupEntry, ContentSegOfs.iLow, ContentSegOfs.iHigh]); {$ENDIF} StartBytesUsed := StartBytesUsed - ffc_BLOBSegmentHeaderSize; { Update the content length of the lookup entry. We have several cases to account for: 1. Write X bytes to empty segment. Length = X. 2. Suffix X bytes to end of segment containing Y bytes. Length = X + Y. 3. Write X bytes to segment containing Y bytes where X <= Y and (aOffset + X) <= Y. Length = Y. 4. Write X bytes to segment containing Y bytes where X <= Y and (aOffset + X) > Y. Length = # untouched bytes + Y. These cases are all handled by the following IF statement. } if (StartBytesUsed + BytesToCopy > LookupEntry^.bleContentLength) then begin LookupEntry^.bleContentLength := StartBytesUsed + BytesToCopy; end; {$IFDEF BLOBTrace} Logbt(' Last lookup entry now points to segment with %d bytes', [LookupEntry^.bleContentLength]); {$ENDIF} CurrLookupEntry := CurrLookupEntry + 1; StartBytesUsed := 0; { Have we reached the end of this lookup segment? } if ((BytesToGo > 0) and (CurrLookupEntry > MaxLookupEntries)) then begin { Is there another lookup segment in this chain? } if (LookupSegPtr^.bshNextSegment.iLow = ffc_W32NoValue) then begin { No. We'll have to get a new one and add it to the chain. } TempWord := EstimateSegmentCount(BytesToGo, aFI^.fiMaxSegSize); TempWord := (TempWord * ffc_BLOBLookupEntrySize) + ffc_BLOBSegmentHeaderSize; TempWord := FFMinDW(TempWord, aFI^.fiMaxSegSize); { Use ContentSegPtr to hold the new lookup segment's offset temporarily. } ContentSegOfs := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, TempWord, TempWord); {$IFDEF BLOBTrace} Logbt(' Creating new lookup segment: %d:%d.', [ContentSegOfs.iLow, ContentSegOfs.iHigh]); {$ENDIF} end else begin { Yes. Assign it to our temp variable. } ContentSegOfs := LookupSegPtr^.bshNextSegment; {$IFDEF BLOBTrace} Logbt(' Moving to next lookup segment.', [ContentSegOfs.iLow, ContentSegOfs.iHigh]); {$ENDIF} end; { Get the lookup segment block and set up offset for 1st lookup entry. } aLkpRelMethod(LookupSegBlk); LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_MarkDirty, ContentSegOfs, LookupBlock, LookupSegOfs, aLkpRelMethod); { Intialize the segment on if it's new. } if ((LookupSegPtr <> nil) and (LookupSegPtr^.bshNextSegment.iLow <> ffc_W32NoValue)) then begin LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; LookupEntOfs := LookupSegOfs + ffc_BLOBSegmentHeaderSize; LookupEntry := @LookupSegBlk^[LookupEntOfs]; ContentSegOfs := LookupEntry^.bleSegmentOffset; end else begin { Chain the last lookup segment to the new one. } LookupSegPtr^.bshNextSegment := ContentSegOfs; LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; LookupSegPtr^.bshParentBLOB := aBLOBNr; LookupSegPtr^.bshSignature := ffc_SigBLOBSegLookup; LookupSegPtr^.bshNextSegment.iLow := ffc_W32NoValue; LookupEntOfs := LookupSegOfs + ffc_BLOBSegmentHeaderSize; LookupEntry := @LookupSegBlk^[LookupEntOfs]; ContentSegOfs.iLow := ffc_W32NoValue; end; { How many entries are in the current lookup segment? } MaxLookupEntries := FFCalcMaxLookupEntries(LookupSegPtr); CurrLookupEntry := 1; {$IFDEF BLOBTrace} LookupSegCount := LookupSegCount + 1; Logbt(' Moving to lookup segment %d', [LookupSegCount]); Logbt(' Lookup segment - Max entries: %d', [MaxLookupEntries]); {$ENDIF} end else begin LookupEntOfs := LookupEntOfs + ffc_BLOBLookupEntrySize; LookupEntry := @LookupSegBlk^[LookupEntOfs]; ContentSegOfs := ContentSegPtr^.bshNextSegment; end; end; { If the BLOB has grown, we need to update its length. NOTE: BLOBs can't be truncated via a write operation. } if (NewSize > BLOBHeader^.bbhBLOBLength) then BLOBHeader^.bbhBLOBLength := NewSize; finally if (LookupSegBlk <> nil) then aLkpRelMethod(LookupSegBlk); for OffsetInBlock := 0 to (aRelList.Count - 1) do FFDeallocReleaseInfo(aRelList[OffsetInBlock]); aRelList.Free; end; {$IFDEF BLOBTrace} except on E:Exception do begin Logbt('*** FFTblWriteBLOB Error: %s', [E.Message]); raise; end; end; {$ENDIF} end; {====================================================================} {===Tff210BLOBEngine=================================================} procedure Tff210BLOBEngine.Read(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aOffset : TffWord32; aLen : TffWord32; aReadMethod : TffBLOBLinkRead; var aBLOB; var aBytesRead : TffWord32; var aFBError : TffResult); var BLOBAsBytes : PffBLOBArray; BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBBlockNum : TffWord32; BLOBHeader : PffBLOBHeader; BytesToCopy : Longint; CmpRes : integer; ContentBlock : TffWord32; ContentSegBlk : PffBlock; ContentSegOfs : TffWord32; DestOffset : Longint; EndBytesUsed : TffWord32; EndSegInx : Integer; EntryCount : Integer; LookupBlock : TffWord32; LookupEntry : PffBLOBLookupEntry; LookupSegBlk : PffBlock; LookupSegPtr : PffBLOBSegmentHeader; NextSeg : TffInt64; OffsetInBlock : TffWord32; SegInx : Integer; StartBytesUsed : TffWord32; StartSegInx : Integer; aCntRelMethod, aLkpRelMethod, aHdRelMethod : TffReleaseMethod; begin BLOBAsBytes := @aBLOB; ContentSegBlk := nil; LookupSegBlk := nil; aFBError := 0; {Exit if aLen = 0} if aLen = 0 then Exit; { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, aBLOBNr, BLOBBlockNum, OffsetInBlock, aHdRelMethod); BLOBHeader := @BLOBBlock^[OffsetInBlock]; { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); try { Are we dealing with a file BLOB or a BLOB link? } case BLOBHeader^.bbhSegCount of ffc_FileBLOB : { file BLOB } begin aFBError := FileBLOBRead(aFI, aTI, aBLOBNr, aOffset, aLen, aBLOB, aBytesRead); Exit; end; ffc_BLOBLink : { BLOB link } begin aFBError := BLOBLinkRead(aFI, aTI, aBLOBNr, aOffset, aLen, aReadMethod, aBLOB, aBytesRead); Exit; end; end; { case } { Make sure that the offset is within BLOB. } CmpRes := FFCmpDW(aOffset, BLOBHeader^.bbhBLOBLength); if (CmpRes >= 0) then begin aBytesRead := 0; Exit; end; { Get the lookup segment block and set up offset for 1st lookup entry. } LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, BLOBHeader^.bbh1stLookupSeg, LookupBlock, OffsetInBlock, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; OffsetInBlock := OffsetInBlock + sizeof(TffBLOBSegmentHeader); { Calculate the number of bytes we can (= "are going to") read. } aBytesRead := ffMinDW(aLen, BLOBHeader^.bbhBLOBLength - aOffset); { Calculate the starting & ending index of the segments to read. } if aOffset = 0 then begin StartSegInx := 0; StartBytesUsed := 0; end else StartSegInx := CalcBLOBSegNumber(aOffset, aFI^.fiBlockSize, StartBytesUsed); EndSegInx := CalcBLOBSegNumber(aOffset + aBytesRead, aFI^.fiBlockSize, EndBytesUsed); { Walk through the BLOB segment linked list, reading segments as we go, copying to the BLOB when required. } SegInx := 1; DestOffset := 0; EntryCount := 0; ContentBlock := 0; while (SegInx <= EndSegInx) do begin inc(EntryCount); LookupEntry := @LookupSegBlk^[OffsetInBlock]; {if we should read from this block, do so} if (SegInx >= StartSegInx) then begin { Read the BLOB content segment. } if assigned(ContentSegBlk) then aCntRelMethod(ContentSegBlk); ContentSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, LookupEntry^.bleSegmentOffset, ContentBlock, ContentSegOfs, aCntRelMethod); ContentSegOfs := ContentSegOfs + sizeof(TffBLOBSegmentHeader); if SegInx = StartSegInx then begin { move from starting offset to dest } BytesToCopy := LookupEntry^.bleContentLength - StartBytesUsed; ContentSegOfs := ContentSegOfs + StartBytesUsed; end else if SegInx = EndSegInx then begin { move up to ending offset to dest } BytesToCopy := EndBytesUsed; end else begin { copying from middle segments } BytesToCopy := LookupEntry^.bleContentLength; end; BytesToCopy := ffMinL(BytesToCopy, aBytesRead); Move(ContentSegBlk^[ContentSegOfs], BLOBAsBytes^[DestOffset], BytesToCopy); inc(DestOffset, BytesToCopy); end; { if } {see if we're at the end of the lookup segment} if ((SegInx <> EndSegInx) and (LookupSegPtr^.bshSegmentLen < (sizeof(TffBLOBSegmentHeader) + (succ(EntryCount) * sizeof(TffBLOBLookupEntry))))) then begin NextSeg := LookupSegPtr^.bshNextSegment; aLkpRelMethod(LookupSegBlk); LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_ReadOnly, NextSeg, {!!.11} LookupBlock, OffsetInBlock, aLkpRelMethod); LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; OffsetInBlock := OffsetInBlock + sizeof(TffBLOBSegmentHeader); EntryCount := 0; end else OffsetInBlock := OffsetInBlock + sizeof(TffBLOBLookupEntry); inc(SegInx); end; {while} finally if assigned(ContentSegBlk) then aCntRelMethod(ContentSegBlk); if assigned(LookupSegBlk) then aLkpRelMethod(LookupSegBlk); aHdRelMethod(BLOBBlock); end; end; {--------} procedure Tff210BLOBEngine.Truncate(aFI : PffFileInfo; aTI : PffTransInfo; aBLOBNr : TffInt64; aLen : TffWord32); var BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBHeader : PffBLOBHeader; EntryCount : TffWord32; OffsetInBlock : TffWord32; NewSegCount : TffWord32; OldSegCount : TffWord32; i : Integer; IsNewTailSeg : Boolean; LookupBlock : TffWord32; LookupSegBlk : PffBlock; LookupSegOfs : TffInt64; LookupSegPtr : PffBLOBSegmentHeader; LookupEntOfs : TffWord32; LookupEntPtr : PffBLOBLookupEntry; OldUsedSpace : TffWord32; NewUsedSpace : TffWord32; OldContSegOfs : TffWord32; OldContSegBlk : PffBlock; OldContSegPtr : PffBLOBSegmentHeader; NewContSegOfs : TffWord32; NewContSegBlk : PffBlock; NewContSegPtr : PffBLOBSegmentHeader; NextLookupSeg : TffInt64; UpdatedContSeg : TffInt64; SegEntries : TffWord32; TailEntry : TffWord32; TotEntries : TffWord32; aRelList : TffPointerList; aRelMethod : TffReleaseMethod; SegSize : TffWord32; begin { We use the following list to track the RAM pages we've accessed and the release method associated with each RAM page. At the end of this routine, we will call the release method for each RAM page. } aRelList := TffPointerList.Create; try { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(BLOBBlock,TffInt64(aRelMethod))); BLOBHeader := @BLOBBlock^[OffsetInBlock]; { Check if we're trying to truncate a zero-length BLOB or to the BLOB's current length. } if ((BLOBHeader^.bbhBLOBLength = aLen) or ((BLOBHeader^.bbhBLOBLength = 0) and (aLen = 0))) then Exit; { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); { Verify this is a header segment. } if (BLOBHeader^.bbhSignature <> ffc_SigBLOBSegHeader) then FFRaiseException(EffServerException, ffStrResServer, fferrBadBLOBSeg, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh, format(ffcBLOBSegExpected, [ffcBLOBSegHeader, char(BLOBHeader^.bbhSignature)])]); { We can't write to a file BLOB. } if (BLOBHeader^.bbhSegCount = -1) then FFRaiseException(EffServerException, ffStrResServer, fferrFileBLOBWrite, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh]); { Make sure the truncated length <= current BLOB length. } if (aLen > BLOBHeader^.bbhBLOBLength) then FFRaiseException(EffServerException, ffStrResServer, fferrLenMismatch, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh, aLen, BLOBHeader^.bbhBLOBLength]); { If the new length is greater than 0, we will lop off some content segments. The content segment that becomes the last content segment must be resized. } if aLen > 0 then begin { Calculate the number of segments for the old and new lengths. } OldSegCount := CalcBLOBSegNumber(BLOBHeader^.bbhBLOBLength, aFI^.fiBlockSize, OldUsedSpace); NewSegCount := CalcBLOBSegNumber(aLen, aFI^.fiBlockSize, NewUsedSpace); { Grab the first lookup segment. } NextLookupSeg := BLOBHeader^.bbh1stLookupSeg; LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_MarkDirty, {!!.12} NextLookupSeg, LookupBlock, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(LookupSegBlk,TffInt64(aRelMethod))); LookupSegPtr := PffBLOBSegmentHeader(@LookupSegBlk^[OffsetInBlock]); TotEntries := 0; { Calculate # of entries in this lookup segment. } SegEntries := FFCalcMaxLookupEntries(LookupSegPtr); { Walk through the lookup segments until we find the lookup segment containing the new tail lookup entry. } while ((TotEntries + SegEntries) < NewSegCount) do begin Inc(TotEntries, SegEntries); { Grab the offset of the next lookup segment. } NextLookupSeg := LookupSegPtr^.bshNextSegment; LookupSegBlk := ReadVfyBlobBlock2(aFI, aTI, ffc_MarkDirty, NextLookupSeg, {!!.12} LookupBlock, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(LookupSegBlk,TffInt64(aRelMethod))); LookupSegPtr := PffBLOBSegmentHeader(@LookupSegBlk^[OffsetInBlock]); SegEntries := FFCalcMaxLookupEntries(LookupSegPtr); end; { Find the lookup entry that will now point to the new tail content segment. } TailEntry := pred(NewSegCount - TotEntries); { base zero } LookupEntOfs := (OffsetInBlock + sizeof(TffBLOBSegmentHeader) + (TailEntry * sizeof(TffBLOBLookupEntry))); LookupEntPtr := PffBLOBLookupEntry(@LookupSegBlk^[LookupEntOfs]); { Grab the content segment pointed to by this lookup entry. We will copy over some of those bytes to the new tail content segment. } UpdatedContSeg := LookupEntPtr^.bleSegmentOffset; { Obtain the new tail content segment. } SegSize := NewUsedSpace + sizeof(TffBLOBSegmentHeader); LookupEntPtr^.bleSegmentOffset := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, SegSize); LookupEntPtr^.bleContentLength := NewUsedSpace; { Initialize the new content segment header. } NewContSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, LookupEntPtr^.bleSegmentOffset, NewContSegOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(NewContSegBlk, TffInt64(aRelMethod))); NewContSegPtr := PffBLOBSegmentHeader(@NewContSegBlk^[NewContSegOfs]); NewContSegPtr^.bshSignature := ffc_SigBLOBSegContent; NewContSegPtr^.bshParentBLOB := aBLOBNr; NewContSegPtr^.bshNextSegment.iLow := ffc_W32NoValue; { If there is more than one content segment in the truncated BLOB, make sure the next to last content segment points to the new tail content segment. } if NewSegCount > 1 then begin LookupEntPtr := PffBLOBLookupEntry(@LookupSegBlk^[LookupEntOfs - sizeof(TffBLOBLookupEntry)]); OldContSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, LookupEntPtr^.bleSegmentOffset, OldContSegOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(OldContSegBlk, TffInt64(aRelMethod))); OldContSegPtr := PffBLOBSegmentHeader(@OldContSegBlk^[OldContSegOfs]); { Restore LookupEntPtr. } LookupEntPtr := PffBLOBLookupEntry(@LookupSegBlk^[LookupEntOfs]); OldContSegPtr^.bshNextSegment := LookupEntPtr^.bleSegmentOffset; end; { Copy NewUsedSpace bytes from the old content segment to new tail content segment. } OldContSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, UpdatedContSeg, OldContSegOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(OldContSegBlk, TffInt64(aRelMethod))); Move(OldContSegBlk^[OldContSegofs + sizeof(TffBLOBSegmentHeader)], NewContSegBlk^[NewContSegOfs + sizeof(TffBLOBSegmentHeader)], NewUsedSpace); { Get rid of the old content segment. } aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, UpdatedContSeg); { Delete the content & lookup segments that are no longer needed. First, obtain the number of extraneous lookup entries in the current lookup segment. } EntryCount := FFCalcMaxLookupEntries(LookupSegPtr) - succ(TailEntry); { Initialize the lookup entry offset & pointer. They must point to the lookup entry after the new tail lookup entry. } LookupEntOfs := (OffsetInBlock + sizeof(TffBLOBSegmentHeader) + (succ(TailEntry) * sizeof(TffBLOBLookupEntry))); LookupEntPtr := PffBLOBLookupEntry(@LookupSegBlk^[LookupEntOfs]); { Save the offset of the current lookup segment. } LookupSegOfs := NextLookupSeg; IsNewTailSeg := True; { Free each content segment. } for i := succ(NewSegCount) to OldSegCount do begin aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, LookupEntPtr^.bleSegmentOffset); dec(EntryCount); { Need to move to another lookup segment? } if ((EntryCount = 0) and (LookupSegPtr^.bshNextSegment.iLow <> ffc_W32NoValue)) then begin {Yes. Get the location of the next lookup segment. } NextLookupSeg := LookupSegPtr^.bshNextSegment; { If this is not the new tail lookup segment then delete the lookup segment. } if IsNewTailSeg then IsNewTailSeg := False else aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, LookupSegOfs); { Grab the next lookup segment. } LookupSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, NextLookupSeg, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(LookupSegBlk, TffInt64(aRelMethod))); LookupSegPtr := @LookupSegBlk^[OffsetInBlock]; LookupEntOfs := OffsetInBlock + sizeof(TffBLOBSegmentHeader); LookupSegOfs := NextLookupSeg; EntryCount := FFCalcMaxLookupEntries(LookupSegPtr); end; { If this is the new tail lookup segment then zero out the lookup entry. } if IsNewTailSeg then FillChar(LookupEntPtr^, sizeof(TffBLOBLookupEntry), 0); {!!.13} { Grab the next lookup entry. } LookupEntOfs := LookupEntOfs + sizeof(TffBLOBLookupEntry); LookupEntPtr := @LookupSegBlk^[LookupEntOfs]; end; {for} { Delete the last lookup segment if it's not the new tail segment.} if not IsNewTailSeg then aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, LookupSegOfs); { Set the new segment count in the BLOB header. } BLOBHeader^.bbhSegCount := NewSegCount; end else begin {we are truncating to length of 0} FFTblDeleteBLOBPrim(aFI, aTI, BLOBHeader); { Reset the lookup segment field and the segment count. FFTblFreeBLOB will get rid of the BLOB header if the BLOB is still at length 0. } BLOBHeader^.bbh1stLookupSeg.iLow := ffc_W32NoValue; BLOBHeader^.bbhSegCount := 0; BLOBHeader^.bbhBLOBLength := 0; end; {set the new BLOB length and segment count in the BLOB header} BLOBHeader^.bbhBLOBLength := aLen; finally for OffsetInBlock := 0 to pred(aRelList.Count) do FFDeallocReleaseInfo(aRelList[OffsetInBlock]); aRelList.Free; end; end; {--------} procedure Tff210BLOBEngine.Write(aFI : PffFileInfo; aTI : PffTransInfo; const aBLOBNr : TffInt64; aOffset : TffWord32; {offset in blob to start writing} aLen : TffWord32; {bytes from aOffset to stop writing} const aBLOB); var AvailSpace : TffWord32; BLOBBlock : PffBlock; BLOBBlockHdr : PffBlockHeaderBLOB absolute BLOBBlock; BLOBHeader : PffBLOBHeader; OffsetInBlock : TffWord32; BLOBAsBytes : PffBLOBArray; StartSegInx : Integer; EndSegInx : Integer; OldEndSeg : Integer; SegInx : Integer; { index into the blob } BytesToCopy : TffWord32; BytesToGo : TffWord32; SrcOffset : Longint; LookupEntOfs : TffWord32; LookupEntPtr : PffBLOBLookupEntry; LookupSegOfs : TffWord32; LookupSegPtr : PffBLOBSegmentHeader; LookupSegBlk : PffBlock; ContentSegOfs : TffInt64; ContentSegBlk : PffBlock; ContentSegPtr : PffBLOBSegmentHeader; PrevContentSegPtr : PffBLOBSegmentHeader; StartBytesUsed : TffWord32; EndBytesUsed : TffWord32; EntryCount : TffWord32; SegBytesLeft : TffWord32; SegEntNumber : TffWord32; { index into the lookup segment } TargetOffset : TffWord32; TempSegOfs : TffInt64; TempSegBlk : PffBlock; TempSegPtr : PffBLOBSegmentHeader; TempOfsInBlk : TffWord32; NewSize : TffWord32; NewSizeW32 : TffWord32; BytesCopied : Longint; aRelMethod : TffReleaseMethod; aRelList : TffPointerList; SegSize : TffWord32; begin BLOBAsBytes := @aBLOB; NewSizeW32 := aOffset + aLen; { Verify the new length (aLen + aOffset) doesn't exceed max. } if (NewSizeW32 > ffcl_MaxBLOBLength) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBTooBig, [aOffset + aLen]); { We use the following list to track the RAM pages we've accessed and the release method associated with each RAM page. At the end of this routine, we will call the release method for each RAM page. } aRelList := TffPointerList.Create; try { Read and verify the BLOB header block for this BLOB number. } BLOBBlock := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, aBLOBNr, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(BLOBBlock, TffInt64(aRelMethod))); BLOBHeader := @BLOBBlock^[OffsetInBlock]; { Verify the BLOB has not been deleted. } if (BLOBHeader^.bbhSignature = ffc_SigBLOBSegDeleted) then FFRaiseException(EffServerException, ffStrResServer, fferrBLOBDeleted, [aFI^.fiName^, aBLOBNr.iHigh, aBLOBNr.iLow]); NewSize := FFMaxL(aOffset + aLen, BLOBHeader^.bbhBLOBLength); { For a file BLOB raise an error. } if (BLOBHeader^.bbhSegCount = -1) then FFRaiseException(EffServerException, ffStrResServer, fferrFileBLOBWrite, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh]); { Verify the offset is within, or at the end of, the BLOB. } if (aOffset > BLOBHeader^.bbhBLOBLength) then FFRaiseException(EffServerException, ffStrResServer, fferrOfsNotInBlob, [aFI^.fiName^, aBLOBNr.iLow, aBLOBNr.iHigh, aOffset, BLOBHeader^.bbhBLOBLength]); { If the BLOB is growing we need to rebuild the lookup segment(s). } if (NewSize > BLOBHeader^.bbhBLOBLength) then {the lookup segment(s) have to be rebuilt because the BLOB is growing} BLOBHeader^.bbh1stLookupSeg := FFTblRebuildLookupSegments(aFI, aTI, NewSize, BLOBHeader^.bbhBLOBLength, aBLOBNr); { Get the first lookup segment. } LookupSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, BLOBHeader^.bbh1stLookupSeg, LookupSegOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(LookupSegBlk, TffInt64(aRelMethod))); LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; EntryCount := FFCalcMaxLookupEntries(LookupSegPtr); { Calculate the last segment in which we will write data. } EndSegInx := CalcBLOBSegNumber(aOffset + aLen, aFI^.fiBlockSize, EndBytesUsed); if BLOBHeader^.bbhBLOBLength = 0 then begin OldEndSeg := 0; StartSegInx := 0; end else begin { Calculate the number of segments currently used. } OldEndSeg := CalcBLOBSegNumber(BLOBHeader^.bbhBLOBLength, aFI^.fiBlockSize, EndBytesUsed); { Calculate the segment in which we will start writing the data. } if aOffset = 0 then begin StartSegInx := 1; StartBytesUsed := 0; end else StartSegInx := CalcBLOBSegNumber(aOffset, aFI^.fiBlockSize, StartBytesUsed); end; ContentSegPtr := nil; PrevContentSegPtr := nil; SrcOffset := 0; BytesToGo := aLen; SegInx := 0; LookupEntOfs := LookupSegOfs + sizeof(TffBLOBSegmentHeader); LookupEntPtr := PffBLOBLookupEntry(@LookupSegBlk^[LookupEntOfs]); SegEntNumber := 0; { Walk through the lookup segments up to the current end segment. } while (SegInx < OldEndSeg) and (SegInx < EndSegInx) do begin { Is the segment one in which we are to write data? } if SegInx >= pred(StartSegInx) then begin { Get the content block. } ContentSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, LookupEntPtr^.bleSegmentOffset, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(ContentSegBlk, TffInt64(aRelMethod))); ContentSegPtr := @ContentSegBlk^[OffsetInBlock]; SegBytesLeft := ContentSegPtr^.bshSegmentLen - ffc_BLOBSegmentHeaderSize; TargetOffset := OffsetInBlock + ffc_BLOBSegmentHeaderSize; { If this is the first segment to which we are writing, adjust the starting points. } if SegInx = pred(StartSegInx) then begin dec(SegBytesLeft, StartBytesUsed); inc(TargetOffset, StartBytesUsed); end else StartBytesUsed := 0; { If this old segment is not the largest it could be & is not big enough to hold what is left then we need a new segment} if StartBytesUsed = 0 then AvailSpace := ContentSegPtr^.bshSegmentLen - ffc_BLOBSegmentHeaderSize else AvailSpace := SegBytesLeft; if ((ContentSegPtr^.bshSegmentLen < aFI^.fiMaxSegSize) and (AvailSpace < BytesToGo)) then begin { Calculate the size of the data in the new segment. } BytesToCopy := ffMinL(aFI^.fiMaxSegSize - ffc_BLOBSegmentHeaderSize, BytesToGo + LookupEntPtr^.bleContentLength); { Allocate & retrieve the new segment. } SegSize := BytesToCopy + ffc_BLOBSegmentHeaderSize; TempSegOfs := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, SegSize); TempSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, TempSegOfs, TempOfsInBlk, aRelMethod); aRelList.Append(FFAllocReleaseInfo(TempSegBlk, TffInt64(aRelMethod))); TempSegPtr := @TempSegBlk^[TempOfsInBlk]; TempSegPtr^.bshSignature := ffc_SigBLOBSegContent; TempSegPtr^.bshParentBLOB := aBLOBNr; { Preserve the existing data in the old content segment. } if LookupEntPtr^.bleContentLength > 0 then begin assert(LookupEntPtr^.bleContentLength = EndBytesUsed); Move(ContentSegBlk^[OffsetInBlock + ffc_BLOBSegmentHeaderSize], TempSegBlk^[TempOfsInBlk + ffc_BLOBSegmentHeaderSize], EndBytesUsed); { Decrement EndBytesUsed. } dec(EndBytesUsed, LookupEntPtr^.bleContentLength); end; SegBytesLeft := BytesToCopy - StartBytesUsed; TargetOffset := TempOfsInBlk + ffc_BLOBSegmentHeaderSize + StartBytesUsed; { Change the previous content segment's NextSegment field. } if Assigned(PrevContentSegPtr) then PrevContentSegPtr^.bshNextSegment := TempSegOfs; aFI^.fiBLOBrscMgr.DeleteSegment(aFI, aTI, LookupEntPtr^.bleSegmentOffset); LookupEntPtr^.bleSegmentOffset := TempSegOfs; OffsetInBlock := TempOfsInBlk; ContentSegBlk := TempSegBlk; ContentSegPtr := TempSegPtr; end; { Figure out how many bytes to copy. } BytesToCopy := ffMinL(SegBytesLeft, BytesToGo); { Copy. } Move(BLOBAsBytes^[SrcOffset], ContentSegBlk^[TargetOffset], BytesToCopy); dec(BytesToGo, BytesToCopy); inc(SrcOffset, BytesToCopy); { Update the content length of the lookup entry. We have several cases to account for: 1. Write X bytes to empty segment. Length = X. 2. Suffix X bytes to end of segment containing Y bytes. Length = X + Y. 3. Write X bytes to segment containing Y bytes where X <= Y and (aOffset + X) <= Y. Length = Y. 4. Write X bytes to segment containing Y bytes where X <= Y and (aOffset + X) > Y. Length = # untouched bytes + Y. These cases are all handled by the following IF statement. } if StartBytesUsed + BytesToCopy > LookupEntPtr^.bleContentLength then LookupEntPtr^.bleContentLength := StartBytesUsed + BytesToCopy end; inc(SegEntNumber); inc(SegInx); PrevContentSegPtr := ContentSegPtr; { If we're not done, we may need to move to the next lookup header. } if BytesToGo <> 0 then begin if SegEntNumber = EntryCount then begin { We filled all the segments in this segment, move to next one & reset SegInx} LookupSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, LookupSegPtr^.bshNextSegment, LookupSegOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(LookupSegBlk, TffInt64(aRelMethod))); LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; EntryCount := FFCalcMaxLookupEntries(LookupSegPtr); LookupEntOfs := LookupSegOfs + sizeof(TffBLOBSegmentHeader); LookupEntPtr := PffBLOBLookupEntry(@LookupSegBlk^[LookupEntOfs]); SegEntNumber := 0; end else begin LookupEntOfs := LookupEntOfs + sizeof(TffBLOBLookupEntry); LookupEntPtr := @LookupSegBlk^[LookupEntOfs]; end; end; end; {while} if (EndSegInx >= OldEndSeg) then begin { If newly-sized BLOB extends past old BLOB then add new segments. } BLOBHeader^.bbhSegCount := OldEndSeg; while BytesToGo > 0 do begin { Figure out how many bytes to copy. } BytesToCopy := ffMinL(BytesToGo, aFI^.fiMaxSegSize - ffc_BLOBSegmentHeaderSize); { Get a new content segment} SegSize := BytesToCopy + ffc_BLOBSegmentHeaderSize; ContentSegOfs := aFI^.fiBLOBrscMgr.NewSegment(aFI, aTI, SegSize, SegSize); { If exist, update prev segment to point to this new segment. } if Assigned(ContentSegPtr) then begin PrevContentSegPtr := ContentSegPtr; PrevContentSegPtr^.bshNextSegment := ContentSegOfs; end; { Increment the segment count & read in the new content segment. } inc(BLOBHeader^.bbhSegCount); ContentSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, ContentSegOfs, OffsetInBlock, aRelMethod); aRelList.Append(FFAllocReleaseInfo(ContentSegBlk, TffInt64(aRelMethod))); ContentSegPtr := @ContentSegBlk^[OffsetInBlock]; ContentSegPtr^.bshSignature := ffc_sigBLOBSegContent; ContentSegPtr^.bshParentBLOB := aBLOBNr; ContentSegPtr^.bshNextSegment.iLow := ffc_W32NoValue; { Get a new lookup entry. } LookupEntOfs := LookupSegOfs + sizeof(TffBLOBSegmentHeader) + (SegEntNumber * sizeof(TffBLOBLookupEntry)); LookupEntPtr := PffBLOBLookupEntry(@LookupSegBlk^[LookupEntOfs]); LookupEntPtr^.bleSegmentOffset := ContentSegOfs; LookupEntPtr^.bleContentLength := BytesToCopy; { Fill the content segment. } Move(BLOBAsBytes^[SrcOffset], ContentSegBlk^[OffsetInBlock + sizeof(TffBLOBSegmentHeader)], BytesToCopy); inc(SrcOffset, BytesToCopy); dec(BytesToGo, BytesToCopy); inc(SegEntNumber); { If we're not done, we may need to move to the next lookup segment. } if BytesToGo <> 0 then begin if SegEntNumber = EntryCount then begin { We filled all the segments in this segment, move to next one & reset SegEntNumber. } LookupSegBlk := ReadVfyBlobBlock(aFI, aTI, ffc_MarkDirty, LookupSegPtr^.bshNextSegment, LookupSegOfs, aRelMethod); aRelList.Append(FFAllocReleaseInfo(LookupSegBlk, TffInt64(aRelMethod))); LookupSegPtr := @LookupSegBlk^[LookupSegOfs]; EntryCount := FFCalcMaxLookupEntries(LookupSegPtr); OffsetInBlock := LookupSegOfs + sizeof(TffBLOBSegmentHeader); SegEntNumber := 0; end; {if} end; end; {while} end; {if} { If the BLOB has grown, we need to update its length. NOTE: BLOBs can't be truncated via a write operation. } if (NewSizeW32 > BLOBHeader^.bbhBLOBLength) then BLOBHeader^.bbhBLOBLength := NewSizeW32; finally for OffsetInBlock := 0 to pred(aRelList.Count) do FFDeallocReleaseInfo(aRelList[OffsetInBlock]); aRelList.Free; end; end; {====================================================================} initialization FFBLOBEngine := TffBLOBEngine.Create; FF210BLOBEngine := Tff210BLOBEngine.Create; {$IFDEF BLOBTrace} btLog := TffEventLog.Create(nil); btLog.FileName := 'BLOBTrace.log'; btLog.Enabled := True; {$ENDIF} finalization FFBLOBEngine.Free; FF210BLOBEngine.Free; {$IFDEF BLOBTrace} btLog.Flush; btLog.Free; {$ENDIF} {End !!.11} end.