{ ############################################################################ }
{ #                                                                          # }
{ #  MirandaNG HistoryToDB Plugin v2.5                                       # }
{ #                                                                          # }
{ #  License: GPLv3                                                          # }
{ #                                                                          # }
{ #  Author: Grigorev Michael (icq: 161867489, email: sleuthhound@gmail.com) # }
{ #                                                                          # }
{ ############################################################################ }

//*****************************************************************//
//                                                                 //
//  TMapStream                                                     //
//  Copyright� BrandsPatch LLC                                     //
//  http://www.explainth.at                                        //
//                                                                 //
//  All Rights Reserved                                            //
//                                                                 //
//  Permission is granted to use, modify and redistribute          //
//  the code in this Delphi unit on the condition that this        //
//  notice is retained unchanged.                                  //
//                                                                 //
//  BrandsPatch declines all responsibility for any losses,        //
//  direct or indirect, that may arise  as a result of using       //
//  this code.                                                     //
//                                                                 //
//*****************************************************************//
unit MapStream;

interface

uses Windows, SysUtils, Classes, SyncObjs;

const MAXINTMINUS = MAXINT - 1;

type TMapBytes = array[0..MAXINTMINUS] of Byte;
     PMapBytes = ^TMapBytes;

type TMapStream = class(TObject)
private
  FHandle: THandle;
  FPosition: Integer;
  FSize: Integer;
  FTimeOut: Integer;
  procedure SetPosition(Value: Integer);
  function CountGood(Count: Integer):Boolean;
  function GrabEvent: Boolean;
  procedure ReleaseEvent;
protected
  FEvent: TEvent;
  FMemory: PMapBytes;
public
  property Position: Integer read FPosition write SetPosition;
  constructor CreateEx(const AName: String; ASize,ATimeOut: Integer);
  destructor Destroy; override;
  function Clear: Boolean;
  function CopyFrom(AStream: TStream;Count: Integer): Boolean;
  function ReadBuffer(P: Pointer; Count: Integer): Boolean;
  function WriteBuffer(P: Pointer; Count: Integer): Boolean;
end;

type ENoMapping = class(Exception);

implementation

// MapStream Create & Destroy
constructor TMapStream.CreateEx(const AName:String;ASize,ATimeOut:Integer);
begin
  inherited Create;
  FSize := ASize;
  FTimeOut := ATimeOut;
  if (FSize < 1) or (FSize > MAXINTMINUS) then FSize:=MAXWORD;
  if (FTimeOut < 1) or (FTimeOut > 5000) then FTimeOut:=2000;
  //2000ms timeout for safety
  FHandle:=CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,0,FSize,PChar(AName));
  //See the Windows Kernel32 CreateFileMapping function for information
  if (FHandle = 0) then ENoMapping.Create(Format('%s file mapping failed.',[AName]))
  else begin
    FMemory:=MapViewOfFile(FHandle,FILE_MAP_ALL_ACCESS,0,0,0);

    FEvent:=TEvent.Create(nil,True,True,Format('ExplainThat_%s_MAP',[AName]));
    {1. map a view of the file mapping into the address
        space of the parent application

    2.  create an event to prevent simultaneous access
        to the map by different apps. We try to make the
        event name unique so we don't end up with a handle to
        an unrelated event that already exists
    }
  end;
end;

destructor TMapStream.Destroy;
begin
  UnMapViewOfFile(FMemory);
  CloseHandle(FHandle);
  FEvent.Free;
  inherited;
end;

function TMapStream.CountGood(Count:Integer):Boolean;
begin
  Result:=(FPosition + Count < FSize);
end;

function TMapStream.GrabEvent:Boolean;
begin
  Result:=True;
  with FEvent do
  begin
    case WaitFor(FTimeOut) of
      wrSignaled:ReSetEvent;
      {locks the event for exclusive use by this app. Funny name, ReSetEvent, not
       our choice!}
      else Result:=False;
    end;
  end;
end;

procedure TMapStream.ReleaseEvent;
begin
  FEvent.SetEvent; //unlock the event so other apps can use it
end;

// MapStream Manipulation
function TMapStream.Clear:Boolean;
begin
  if GrabEvent then
  try
    FillChar(FMemory^[0],FSize,0);
    FPosition:=0;
    Result:=True;
  finally ReleaseEvent end else Result:=False;
end;

function TMapStream.CopyFrom(AStream:TStream;Count:Integer):Boolean;

  function SizeGood:Boolean;
  var i,ASize:Integer;
  begin
    ASize:=AStream.Size;
    if (Count = 0) or (Count > ASize) then
    begin
      Count:=ASize;
      AStream.Position:=0;
    end;
    Result:=(FPosition + Count < FSize);
    {make sure the copy block is not too big. Incidentally, also make Count = 0
     as in Delphi.TStream}
  end;

begin
  if SizeGood and GrabEvent then
  try
    AStream.ReadBuffer(Byte(FMemory^[FPosition]),Count);
    Result:=True;
  finally ReleaseEvent end else Result:=False;
end;

function TMapStream.ReadBuffer(P:Pointer;Count:Integer):Boolean;
begin
  if CountGood(Count) and GrabEvent then
  try
    Move(FMemory^[FPosition],P^,Count);
    inc(FPosition,Count);
    Result:=True;
  finally ReleaseEvent end else Result:=False;
end;

function TMapStream.WriteBuffer(P:Pointer;Count:Integer):Boolean;
begin
  if CountGood(Count) and GrabEvent then
  try
    Move(P^,FMemory^[FPosition],Count);
    inc(FPosition,Count);
    Result:=True;
  finally ReleaseEvent end else Result:=False;
end;

procedure TMapStream.SetPosition(Value:Integer);
begin
  if (Value < FSize) and (Value >= 0) then FPosition:=Value;
end;

end.