unit DIFileCache;

{$I DI.inc}

interface

uses
  Classes, DISQLite3Cache;

type
  TDIFileBlock = record
    Len: Integer; // Count of data bytes in block.
    Data: record end;
  end;
  PDIFileBlock = ^TDIFileBlock;

  //------------------------------------------------------------------------------
  // TDIFileCache class
  //------------------------------------------------------------------------------

  { Provides buffered access to blocks of a file. }
  TDIFileCache = class
  private
    FBlockSize: Integer;
    FCache: TDISQLite3Cache;
    FFileName: String;
    FFileStream: TFileStream;
    FUpdateCount: Integer;
  public
    { }
    constructor Create(const AFileName: String; const ABlockSize: Integer);
    { }
    destructor Destroy; override;
    { Reads blocks from file and adds them to the internal cache. Returns a
      pointer to the last block added to the cache. }
    function AddBlocksToCache(ABlockIdx, ABlockCount: Integer): PDIFileBlock;
    procedure BeginUpdate;
    procedure EndUpdate;
    { Reads a block from disk into the cache and returns a pointer to
      the cached block. }
    function GetBlock(const ABlockIdx: Integer): PDIFileBlock;
    procedure Invalidate;

    property BlockSize: Integer read FBlockSize;
  end;

function BlockToHex(const ABlock: PDIFileBlock): String;

function BlockToText(const ABlock: PDIFileBlock): String;

implementation

uses
  SysUtils;

//------------------------------------------------------------------------------
// TDIFileCache class
//------------------------------------------------------------------------------

constructor TDIFileCache.Create(const AFileName: String; const ABlockSize: Integer);
begin
  FFileName := AFileName;
  FBlockSize := ABlockSize;
  FCache := TDISQLite3Cache.Create(SizeOf(TDIFileBlock) + ABlockSize);
  FCache.MaxCount := 396;
end;

//------------------------------------------------------------------------------

destructor TDIFileCache.Destroy;
begin
  if FUpdateCount > 0 then
    FFileStream.Free;
  FCache.Free;
end;

//------------------------------------------------------------------------------

function TDIFileCache.AddBlocksToCache(ABlockIdx, ABlockCount: Integer): PDIFileBlock;
var
  Pos: Int64;
begin
  Result := nil;
  BeginUpdate;
  try
    Pos := ABlockIdx * FBlockSize;
    if FFileStream.Seek(Pos, soFromBeginning) = Pos then
      repeat
        Result := FCache.GetItem(ABlockIdx);
        if not Assigned(Result) then
          Result := FCache.AddItem(ABlockIdx);
        Result.Len := FFileStream.Read(Result^.Data, FBlockSize);
        Inc(ABlockIdx);
        Dec(ABlockCount);
      until (Result.Len < FBlockSize) or (ABlockCount = 0);
  finally
    EndUpdate;
  end;
end;

//------------------------------------------------------------------------------

procedure TDIFileCache.BeginUpdate;
begin
  if FUpdateCount = 0 then
    FFileStream := TFileStream.Create(FFileName, fmOpenRead or fmShareDenyNone);
  Inc(FUpdateCount);
end;

//------------------------------------------------------------------------------

procedure TDIFileCache.EndUpdate;
begin
  if FUpdateCount > 0 then
    begin
      Dec(FUpdateCount);
      if FUpdateCount = 0 then
        FFileStream.Free;
    end;
end;

//------------------------------------------------------------------------------

function TDIFileCache.GetBlock(const ABlockIdx: Integer): PDIFileBlock;
begin
  Result := FCache.GetItem(ABlockIdx);
  if not Assigned(Result) then
    Result := AddBlocksToCache(ABlockIdx, 1);
end;

//------------------------------------------------------------------------------

procedure TDIFileCache.Invalidate;
begin
  FCache.Invalidate;
end;

//------------------------------------------------------------------------------
// Functions
//------------------------------------------------------------------------------

function BlockToHex(const ABlock: PDIFileBlock): String;
var
  lBlock: Integer;
  pBlock: ^Byte;
begin
  Result := '';
  pBlock := Pointer(@ABlock^.Data);
  lBlock := ABlock^.Len;
  while lBlock > 0 do
    begin
      Result := Result + IntToHex(pBlock^, 2) + ' ';
      Inc(pBlock); Dec(lBlock);
    end;
end;

//------------------------------------------------------------------------------

function BlockToText(const ABlock: PDIFileBlock): String;
var
  lBlock: Integer;
  pBlock: ^Byte;
  pResult: PChar;
begin
  lBlock := ABlock.Len;
  pBlock := Pointer(@ABlock^.Data);
  SetString (result, nil, lBlock);
  pResult := Pointer(Result);
  while lBlock > 0 do
    begin
      if pBlock^ >= 32 then
        pResult^ := char (pBlock^)
      else
        pResult^ := '.';
      Inc(pResult); inc (pBlock); Dec(lBlock);
    end;
end;

end.

