{ 27-07-1999 12:46:19 AM > [martin on MARTIN] update: Bugfixing (0.5) /  }
{ 26-07-1999 12:05:12 AM > [martin on MARTIN] checked out /Bugfixing }
{ 10-05-1999 10:37:11 PM > [martin on MARTIN] checked out /Reformatting
   according to Delphi guidelines. }
{ 10-05-1999 1:31:11 AM > [martin on MARTIN] update: Modifying to allow
   for improved error mechanism (0.3) /  }
{ 08-05-1999 8:16:25 PM > [martin on MARTIN] checked out /Modifying to
   allow for improved error mechanism }
{ 14-04-1999 11:59:15 PM > [martin on MARTIN] update: Changing dynamic
   methods to virtual. (0.2) /  }
{ 14-04-1999 11:53:38 PM > [martin on MARTIN] checked out /Changing dynamic
   methods to virtual. }
{ 06-04-1999 7:49:50 PM > [martin on MARTIN] checked out /Modifying Class
   Names }
{ 06-04-1999 1:46:39 AM > [martin on MARTIN] check in: (0.0) Initial Version
   / None }
unit MCHPipeTransactions;

{Martin Harvey 2/12/98}

{A transaction manager suitable for the Pipe socket object.

I'm not toally happy with the original error handling architecture, and since
I have to completely rewrite most of the ONCP stuff anyway, I might as well
temporarily clear the situation up.

Reset will reset the transaction manager and DOPManager. It will not touch the
underlying socket, which will be handled by the PipeONCPSession.

OnDisconnect will be handled by the ONCP and triggered when the socket indicates
disconnection.
OnFatalError will also be handled by the ONCP. However, it will trigger an automatic
call to reset.

}

interface

uses MCHTransactions,Classes,MCHMemoryStream,MCHPipeSocket;

type
  TMCHPipeTransactionManager = class(TMCHCustomTransactionManager)
  private
    FIncomingStream:TMCHMemoryStream;
    FSocket:TMCHPipeSocket;
    FTransactionStart,FTransactionEnd:integer;
  protected
    function GetActive:boolean;override;
    procedure DoFatalError(Msg:string);override;
  public
    constructor Create;
    destructor Destroy;override;
    procedure WriteTransactionFromStream(ExternalIn:TStream);override;
    procedure ReadTransactionToStream(ExternalOut:TStream);override;
    procedure Reset;override;
    {Socket handlers. Note that these are
    automatically called by TMCHPipeSocket}
    procedure HandleConnect;
    procedure HandleDisconnect;
    procedure HandleSockError;
    procedure HandleSockRead;
  published
    property Socket:TMCHPipeSocket read FSocket write FSocket;
    property OnFatalError;
  end;

{NB: Events that I can trigger are:

     procedure DoTransactionRecieved;virtual;
     procedure DoFatalError;virtual;
     procedure DoDestroy;virtual;
}
implementation

constructor TMCHPipeTransactionManager.Create;
begin
  inherited Create; {TMCHCustomTransactionManager.Create creates DOPManager}
  FIncomingStream := TMCHMemoryStream.Create;
end;

function TMCHPipeTransactionManager.GetActive:boolean;
begin
  Result := FIncomingStream.Size > 0;
end;

destructor TMCHPipeTransactionManager.Destroy;
begin
  FIncomingStream.Free;
  inherited Destroy;
end;

procedure TMCHPipeTransactionManager.DoFatalError(Msg:string);
begin
  Reset;
  inherited DoFatalError(Msg);
end;

procedure TMCHPipeTransactionManager.WriteTransactionFromStream(ExternalIn:TStream);

var
  InStream:TMCHMemoryStream;
  Size:integer;

begin
  InStream := TMCHMemoryStream.Create;
  try
    Size := ExternalIn.Size;
    InStream.WriteBuffer(Size,SizeOf(Size));
    ExternalIn.Seek(0,soFromBeginning);
    InStream.CopyFrom(ExternalIn,ExternalIn.Size);
    Socket.WriteData(InStream);
  finally
    InStream.Free;
  end;
end;

procedure TMCHPipeTransactionManager.ReadTransactionToStream(ExternalOut:TStream);

begin
{At this point, just copy the required amount of data}
  FIncomingStream.Seek(FTransactionStart,soFromBeginning);
  ExternalOut.CopyFrom(FIncomingStream,FTransactionEnd - FTransactionStart);
end;

procedure TMCHPipeTransactionManager.Reset;
begin
  FIncomingStream.Clear;
  inherited Reset;
end;

procedure TMCHPipeTransactionManager.HandleConnect;
begin
  Reset;
end;

procedure TMCHPipeTransactionManager.HandleDisconnect;
begin
  Reset;
end;

procedure TMCHPipeTransactionManager.HandleSockError;
begin
  Reset;
end;

procedure TMCHPipeTransactionManager.HandleSockRead;

var
  TempStream:TMCHMemoryStream;
  TrimFrom:integer;

begin
  {Copy as much data as possible onto the end of our internal buffer}
  FSocket.ReadData(FIncomingStream);
  {Now see how many transactions we can extract}
  TrimFrom := 0;
  FTransactionStart := SizeOf(FTransactionEnd); {Start index at end of size field}
  while FIncomingStream.Size >= (FTransactionStart) do
  begin
    FIncomingStream.Seek(FTransactionStart - SizeOf(FTransactionEnd),soFromBeginning);
    FIncomingStream.ReadBuffer(FTransactionEnd,SizeOf(FTransactionEnd)); {read transaction size}
    FTransactionEnd := FTransactionEnd + FTransactionStart; {Transaction end now at correct offset}
    if FIncomingStream.Size >= FTransactionEnd then
    begin
      DoTransactionRecieved; {This will hopefully call ReadTransactionToStream}
      TrimFrom := FTransactionEnd;
    end;
    {Now need to update readtransactionstart}
    FTransactionStart := FTransactionEnd + SizeOf(FTransactionEnd);
  end;
  {At which point we have read all the data we can}
  {Now need to remove all non required data}
  {Now copy all data from TrimFrom onwards into new stream}
  if TrimFrom > 0 then
  begin
    TempStream := TMCHMemoryStream.Create;
    try
      FIncomingStream.Seek(TrimFrom,soFromBeginning);
      if TrimFrom < FIncomingStream.Size then
        TempStream.CopyFrom(FIncomingStream,FIncomingStream.Size - TrimFrom);
    finally
      FIncomingStream.Free;
      FIncomingStream := TempStream;
    end;
  end;
end;

end.

