//TODO: AntiFreeze

{ $HDR$}
{**********************************************************************}
{ Unit archived using Team Coherence                                   }
{ Team Coherence is Copyright 2002 by Quality Software Components      }
{                                                                      }
{ For further information / comments, visit our WEB site at            }
{ http://www.TeamCoherence.com                                         }
{**********************************************************************}
{}
{ $Log:  11946: IdIOHandler.pas
{
{   Rev 1.67    2003.11.29 10:15:30 AM  czhower
{ InternalBuffer --> InputBuffer for consistency.
}
{
{   Rev 1.66    11/23/03 1:46:28 PM  RLebeau
{ Removed "var" specifier from TStrings parameter of ReadStrings().
}
{
    Rev 1.65    11/4/2003 10:27:56 PM  DSiders
  Removed exceptions moved to IdException.pas.
}
{
{   Rev 1.64    2003.10.24 10:44:52 AM  czhower
{ IdStream implementation, bug fixes.
}
{
{   Rev 1.63    10/22/03 2:05:40 PM  RLebeau
{ Fix for TIdIOHandler::Write(TStream) where it was not reading the stream into
{ the TIdBytes correctly.
}
{
{   Rev 1.62    10/19/2003 5:55:44 PM  BGooijen
{ Fixed todo in PerformCapture
}
{
{   Rev 1.61    2003.10.18 12:58:50 PM  czhower
{ Added comment
}
{
{   Rev 1.60    2003.10.18 12:42:04 PM  czhower
{ Intercept.Disconnect is now called
}
{
    Rev 1.59    10/15/2003 7:39:28 PM  DSiders
  Added a formatted resource string for the exception raised in
  TIdIOHandler.MakeIOHandler.
}
{
{   Rev 1.58    2003.10.14 1:26:50 PM  czhower
{ Uupdates + Intercept support
}
{
{   Rev 1.57    2003.10.11 5:48:22 PM  czhower
{ -VCL fixes for servers
{ -Chain suport for servers (Super core)
{ -Scheduler upgrades
{ -Full yarn support
}
{
{   Rev 1.56    9/10/2003 1:50:38 PM  SGrobety
{ Removed all "const" keywords from boolean parameter interfaces. Might trigger
{ changes in other units.
}
{
{   Rev 1.55    10/5/2003 10:39:56 PM  BGooijen
{ Write buffering
}
{
{   Rev 1.54    10/4/2003 11:03:12 PM  BGooijen
{ ReadStream, and functions with network ordering
}
{
{   Rev 1.53    10/4/2003 7:10:46 PM  BGooijen
{ ReadXXXXX
}
{
{   Rev 1.52    10/4/2003 3:55:02 PM  BGooijen
{ ReadString, and some Write functions
}
{
{   Rev 1.51    04/10/2003 13:38:32  HHariri
{ Write(Integer) support
}
{
{   Rev 1.50    10/3/2003 12:09:30 AM  BGooijen
{ DotNet
}
{
{   Rev 1.49    2003.10.02 8:29:14 PM  czhower
{ Changed names of byte conversion routines to be more readily understood and
{ not to conflict with already in use ones.
}
{
{   Rev 1.48    2003.10.02 1:18:50 PM  czhower
{ Changed read methods to be overloaded and more consistent. Will break some
{ code, but nearly all code that uses them is Input.
}
{
{   Rev 1.47    2003.10.02 10:16:26 AM  czhower
{ .Net
}
{
{   Rev 1.46    2003.10.01 9:11:16 PM  czhower
{ .Net
}
{
{   Rev 1.45    2003.10.01 2:46:36 PM  czhower
{ .Net
}
{
{   Rev 1.42    2003.10.01 11:16:32 AM  czhower
{ .Net
}
{
{   Rev 1.41    2003.10.01 1:37:34 AM  czhower
{ .Net
}
{
{   Rev 1.40    2003.10.01 1:12:34 AM  czhower
{ .Net
}
{
{   Rev 1.39    2003.09.30 1:22:56 PM  czhower
{ Stack split for DotNet
}
{
{   Rev 1.38    2003.09.18 5:17:58 PM  czhower
{ Implemented OnWork
}
{
{   Rev 1.37    2003.08.21 10:43:42 PM  czhower
{ Fix to ReadStream from Doychin
}
{
{   Rev 1.36    08/08/2003 17:32:26  CCostelloe
{ Removed "virtual" from function ReadLnSplit
}
{
{   Rev 1.35    07/08/2003 00:25:08  CCostelloe
{ Function ReadLnSplit added
}
{
{   Rev 1.34    2003.07.17 1:05:12 PM  czhower
{ More IOCP improvements.
}
{
{   Rev 1.33    2003.07.14 11:00:50 PM  czhower
{ More IOCP fixes.
}
{
{   Rev 1.32    2003.07.14 12:54:30 AM  czhower
{ Fixed graceful close detection if it occurs after connect.
}
{
{   Rev 1.31    2003.07.10 7:40:24 PM  czhower
{ Comments
}
{
{   Rev 1.30    2003.07.10 4:34:56 PM  czhower
{ Fixed AV, added some new comments
}
{
    Rev 1.29    7/1/2003 5:50:44 PM  BGooijen
  Fixed ReadStream
}
{
    Rev 1.28    6/30/2003 10:26:08 AM  BGooijen
  forgot to remove some code regarding to TIdBuffer.Find
}
{
    Rev 1.27    6/29/2003 10:56:26 PM  BGooijen
  Removed .Memory from the buffer, and added some extra methods
}
{
{   Rev 1.26    2003.06.25 4:30:00 PM  czhower
{ Temp hack fix for AV problem. Working on real solution now.
}
{
{   Rev 1.25    23/6/2003 22:33:14  GGrieve
{ fix CheckForDataOnSource - specify timeout
}
{
{   Rev 1.24    23/6/2003 06:46:52  GGrieve
{ allow block on checkForData
}
{
    Rev 1.23    6/4/2003 1:07:08 AM  BGooijen
  changed comment
}
{
    Rev 1.22    6/3/2003 10:40:34 PM  BGooijen
  FRecvBuffer bug fixed, it was freed, but never recreated, resulting in an AV
}
{
{   Rev 1.21    2003.06.03 6:28:04 PM  czhower
{ Made check for data virtual
}
{
{   Rev 1.20    2003.06.03 3:43:24 PM  czhower
{ Resolved InputBuffer inconsistency. Added new method and renamed old one.
}
{
{   Rev 1.19    5/25/2003 03:56:04 AM  JPMugaas
{ Updated for unit rename.
}
{
{   Rev 1.18    2003.04.17 11:01:12 PM  czhower
}
{
    Rev 1.17    4/16/2003 3:29:30 PM  BGooijen
  minor change in ReadBuffer
}
{
    Rev 1.16    4/1/2003 7:54:24 PM  BGooijen
  ReadLn default terminator changed to LF
}
{
    Rev 1.15    3/27/2003 3:24:06 PM  BGooijen
  MaxLine* is now published
}
{
{   Rev 1.14    2003.03.25 7:42:12 PM  czhower
{ try finally to WriteStrings
}
{
    Rev 1.13    3/24/2003 11:01:36 PM  BGooijen
  WriteStrings is now buffered to increase speed
}
{
    Rev 1.12    3/19/2003 1:02:32 PM  BGooijen
  changed class function ConstructDefaultIOHandler a little (default parameter)
}
{
    Rev 1.11    3/13/2003 10:18:16 AM  BGooijen
  Server side fibers, bug fixes
}
{
    Rev 1.10    3/5/2003 11:03:06 PM  BGooijen
  Added Intercept here
}
{
    Rev 1.9    2/25/2003 11:02:12 PM  BGooijen
  InputBufferToStream now accepts a bytecount
}
{
{   Rev 1.8    2003.02.25 1:36:00 AM  czhower
}
{
{   Rev 1.7    12-28-2002 22:28:16  BGooijen
{ removed warning, added initialization and finalization part.
}
{
{   Rev 1.6    12-16-2002 20:43:28  BGooijen
{ Added class function ConstructIOHandler(....), and removed some comments
}
{
{   Rev 1.5    12-15-2002 23:02:38  BGooijen
{ added SendBufferSize
}
{
{   Rev 1.4    12-15-2002 20:50:32  BGooijen
{ FSendBufferSize was not initialized
}
{
{   Rev 1.3    12-14-2002 22:14:54  BGooijen
{ improved method to detect timeouts in ReadLn.
}
{
{   Rev 1.2    12/11/2002 04:09:28 AM  JPMugaas
{ Updated for new API.
}
{
{   Rev 1.1    2002.12.07 12:25:56 AM  czhower
}
{
{   Rev 1.0    11/13/2002 08:44:50 AM  JPMugaas
}
unit IdIOHandler;

interface

uses
  Classes,
  IdAntiFreezeBase, IdBuffer, IdComponent, IdCoreGlobal, IdException,
  IdIntercept, IdStream, IdCoreResourceStrings;

const
  GRecvBufferSizeDefault = 32 * 1024;
  GSendBufferSizeDefault = 32 * 1024;
  IdMaxLineLengthDefault = 16 * 1024;

type
  TIdIOHandlerClass = class of TIdIOHandler;

  {
  How does this fit in in the hierarchy against TIdIOHandlerSocket
  Destination - Socket - otehr file descendats it

  TIdIOHandler should only implement an interface. No default functionality
  except very simple read/write functions such as ReadCardinal, etc. Functions
  that cannot really be optimized beyond their default implementations.

  Some default implementations offer basic non optmized implementations.

  Yes, I know this comment conflicts. Its being worked on.
  }
  TIdIOHandler = class(TIdComponent)
  protected
    FClosedGracefully: Boolean;
    FDestination: string;
    FHost: string;
    // IOHandlers typically receive more data than they need to complete each
    // request. They store this extra data in InputBuffer for future methods to
    // use. InputBuffer is what collects the input and keeps it if the current
    // method does not need all of it.
    //
    FInputBuffer: TIdBuffer;
    FIntercept: TIdConnectionIntercept;
    FMaxLineAction: TIdMaxLineAction;
    FMaxLineLength: Integer;
    FPort: Integer;
    FReadLnSplit: Boolean;
    FReadLnTimedOut: Boolean;
    FReadTimeOut: Integer;
       
    FRecvBuffer: TIdBuffer; // To be used by ReadFromStack only
    FRecvBufferSize: Integer;
    FSendBufferSize: Integer;

    FWriteBuffer: TIdBuffer;
    FWriteBufferThreshhold: Integer;
    //
    procedure BufferRemoveNotify(ASender: TObject; ABytes: Integer);
    function GetDestination: string; virtual;
    procedure InterceptReceive(
      var ABuffer: TIdBytes
      );
    procedure InterceptWrite(
      var ABuffer: TIdBytes
      );
    procedure PerformCapture(ADest: TObject; out VLineCount: Integer;
     ADelim: string; AIsRFCMessage: Boolean); virtual;
    procedure RaiseConnClosedGracefully;
    procedure SetDestination(AValue: string); virtual;
    procedure SetHost(AValue: string); virtual;
    procedure SetPort(AValue: Integer); virtual;
    procedure SetIntercept(AValue: TIdConnectionIntercept); virtual;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    // This is the main Read function which all other default implementations
    // use.
    function ReadFromSource(ARaiseExceptionIfDisconnected: Boolean = True;
     ATimeout: Integer = IdTimeoutDefault;
     ARaiseExceptionOnTimeout: Boolean = True): Integer; virtual;
     abstract;
    // This is the main write function which all other default implementations
    // use. If default implementations are used, this must be implemented.
    procedure WriteToDestination(ABuffer: TIdBytes); virtual; abstract;
  public
    procedure AfterAccept; virtual;
    constructor Create(AOwner: TComponent); override;
    function Connected: Boolean; virtual;
    destructor Destroy; override;
    // CheckForDisconnect allows the implementation to check the status of the
    // connection at the request of the user or this base class.
    procedure CheckForDisconnect(ARaiseExceptionIfDisconnected: Boolean = True;
     AIgnoreBuffer: Boolean = False); virtual; abstract;
    // Does not wait or raise any exceptions. Just reads whatever data is
    // available (if any) into the buffer. Must NOT raise closure exceptions.
    // It is used to get avialable data, and check connection status. That is
    // it can set status flags about the connection.
    procedure CheckForDataOnSource(ATimeout: Integer = 0); virtual; abstract;
    procedure Close; virtual;
    procedure CloseGracefully; virtual;
    class function MakeDefaultIOHandler(AOwner: TComponent = nil)
     : TIdIOHandler;
    class function MakeIOHandler(ABaseType: TIdIOHandlerClass;
     AOwner: TComponent = nil): TIdIOHandler;
    class procedure RegisterIOHandler;
    class procedure SetDefaultClass;
    function WaitFor(AString: string): string; virtual;
    // This is different thatn WriteToDestination. WriteToDestination goes
    // directly to the network or next level. WriteBuffer allows for buffering
    // using WriteBuffers. This should be the only call to WriteToDestination
    // unless the calls that bypass this are aware of WriteBuffering or are
    // intended to bypass it.
    procedure WriteBytes(
      ABuffer: TIdBytes;
      AWriteNow: Boolean = False
      ); virtual;

    procedure Open; virtual;
    function Readable(AMSec: Integer = IdTimeoutDefault): Boolean; virtual;
    //
    // Optimal Extra Methods
    //
    // These methods are based on the core methods. While they can be
    // overridden, they are so simple that it is rare a more optimal method can
    // be implemented. Because of this they are not overrideable.
    //
    //
    // Write Methods
    //
    // Only the ones that have a hope of being better optimized in descendants
    // have been marked virtual
    procedure Write(AOut: string); overload; virtual;
    procedure WriteLn(AOut: string = ''); virtual;
    procedure Write(ABuffer: TIdBuffer); overload; virtual;
    procedure Write(AValue: TStrings; AWriteLinesCount: Boolean = False);
              overload; virtual;
    procedure Write(AValue: Cardinal; AConvert: Boolean = True); overload;
    procedure Write(AValue: Integer; AConvert: Boolean = True); overload;
    procedure Write(AValue: SmallInt; AConvert: Boolean = True); overload;
    procedure Write(
      AStream: TIdStream;
      ASize: Integer = 0;
      AWriteByteCount: Boolean = False
      ); overload; virtual;
    // Not overloaded because it does not have a unique type for source
    // and could be easily unresolvable with future additions
    function WriteFile(
      AFile: String;
      AEnableTransferFile: Boolean = False
      ): Cardinal;
      virtual;
    //
    // Read methods
    //
    function AllData: string; virtual;
    function InputLn(AMask: String = ''; AEcho: Boolean = True;
     ATabWidth: Integer = 8; AMaxLineLength: Integer = -1): String; virtual;
    // Capture
    // Not virtual because each calls PerformCapture which is virtual
    procedure Capture(ADest: TStream; ADelim: string = '.';
              AIsRFCMessage: Boolean = True); overload;
    procedure Capture(ADest: TStream; out VLineCount: Integer;
              ADelim: string = '.'; AIsRFCMessage: Boolean = True);
              overload;
    procedure Capture(ADest: TStrings; ADelim: string = '.';
              AIsRFCMessage: Boolean = True); overload;
    procedure Capture(ADest: TStrings; out VLineCount: Integer;
              ADelim: string = '.'; AIsRFCMessage: Boolean = True);
              overload;
    //
    // Read___
    // Cannot overload, compiler cannot overload on return values
    //
    procedure ReadBytes(var VBuffer: TIdBytes; AByteCount: Integer); virtual;
    // ReadLn
    function ReadLn(ATerminator: string = LF;
             ATimeout: Integer = IdTimeoutDefault;
             AMaxLineLength: Integer = -1): string; virtual;
    function ReadLnWait(AFailCount: Integer = MaxInt): string; virtual;
    // Added for retrieving lines over 16K long}
    function ReadLnSplit(var AWasSplit: Boolean; ATerminator: string = LF;
             ATimeout: Integer = IdTimeoutDefault;
             AMaxLineLength: Integer = -1): string;
    // Read - Simple Types
    function ReadChar: Char;
    function ReadString(ABytes: Integer): string;
    function ReadCardinal(AConvert: Boolean = True): Cardinal;
    function ReadInteger(AConvert: Boolean = True): Integer;
    function ReadSmallInt(AConvert: boolean = true): SmallInt;
    //
    procedure ReadStream(AStream: TIdStream; AByteCount: LongInt = -1;
     AReadUntilDisconnect: Boolean = False); virtual;
    procedure ReadStrings(ADest: TStrings; AReadLinesCount: Integer = -1);
    //
    // WriteBuffering Methods
    //
    procedure WriteBufferCancel; virtual;
    procedure WriteBufferClear; virtual;
    procedure WriteBufferClose; virtual;
    procedure WriteBufferFlush(AByteCount: Integer = -1); virtual;
    procedure WriteBufferOpen(AThreshhold: Integer = -1); virtual;
    function WriteBufferingActive: Boolean;
    //
    // InputBuffer Methods
    //
    function InputBufferIsEmpty: Boolean;
    //
    // These two are direct access and do no reading of connection
    procedure InputBufferToStream(AStream: TIdStream; AByteCount: Integer = -1);
    function InputBufferAsString: string;
    //
    // Properties
    //
    property ClosedGracefully: Boolean read FClosedGracefully;
                                                                            
    // but new model requires it for writing. Will decide after next set
    // of changes are complete what to do with Buffer prop.
    //
    // Is used by SuperCore
    property InputBuffer: TIdBuffer read FInputBuffer;
    property ReadTimeout: Integer read FReadTimeOut write FReadTimeOut;
    property ReadLnTimedout:boolean read fReadLnTimedout ;
    property WriteBufferThreshhold: Integer read FWriteBufferThreshhold;
    //
    // Events
    //
    property OnWork;
    property OnWorkBegin;
    property OnWorkEnd;
  published
    property Destination: string read GetDestination write SetDestination;
    property Host: string read FHost write SetHost;
    property Intercept: TIdConnectionIntercept read FIntercept
     write SetIntercept;
    property MaxLineLength: Integer read FMaxLineLength write FMaxLineLength default IdMaxLineLengthDefault;
    property MaxLineAction: TIdMaxLineAction read FMaxLineAction write FMaxLineAction;
    property Port: Integer read FPort write SetPort;
    // RecvBufferSize is used by some methods that read large amounts of data.
    // RecvBufferSize is the amount of data that will be requested at each read
    // cycle. RecvBuffer is used to receive then send to the Intercepts, after
    // that it goes to InputBuffer
    property RecvBufferSize: Integer read FRecvBufferSize write FRecvBufferSize
     default GRecvBufferSizeDefault;
    // SendBufferSize is used by some methods that have to break apart large
    // amounts of data into smaller pieces. This is the buffer size of the
    // chunks that it will create and use.
    property SendBufferSize: Integer read FSendBufferSize write FSendBufferSize
     default GSendBufferSizeDefault;
  end;

implementation

uses
  IdStackConsts, // Remove these reference
  IdStack,
  SysUtils;

var
  GIOHandlerClassDefault: TIdIOHandlerClass = nil;
  GIOHandlerClassList: TList = nil;

{ TIdIOHandler }

procedure TIdIOHandler.Close;
begin
  if Intercept <> nil then begin
    Intercept.Disconnect;
  end;
end;

destructor TIdIOHandler.Destroy;
begin
  Close;
  FreeAndNil(FRecvBuffer);
  FreeAndNil(FInputBuffer);
  inherited;
end;

procedure TIdIOHandler.AfterAccept;
begin
  //
end;

procedure TIdIOHandler.Open;
begin
  // Recreate FRecvBuffer
  FreeAndNil(FRecvBuffer);
  FRecvBuffer := TIdBuffer.Create;
  //
  FInputBuffer := TIdBuffer.Create(BufferRemoveNotify);
  FClosedGracefully := False;
end;

procedure TIdIOHandler.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited Notification(AComponent, OPeration);
  if (Operation = opRemove) and (AComponent = FIntercept) then begin
    FIntercept := nil;
  end;
end;

procedure TIdIOHandler.SetIntercept(AValue: TIdConnectionIntercept);
begin
  FIntercept := AValue;
  // add self to the Intercept's free notification list
  if Assigned(FIntercept) then begin
    FIntercept.FreeNotification(Self);
  end;
end;

class procedure TIdIOHandler.SetDefaultClass;
begin
  GIOHandlerClassDefault := Self;
  RegisterIOHandler;
end;

class function TIdIOHandler.MakeDefaultIOHandler(AOwner: TComponent = nil)
 : TIdIOHandler;
begin
  Result := GIOHandlerClassDefault.Create(AOwner);
end;

class procedure TIdIOHandler.RegisterIOHandler;
begin
  if GIOHandlerClassList = nil then begin
    GIOHandlerClassList := TList.Create;
  end;
                                                                       
  // Use an array?
//  if GIOHandlerClassList.IndexOf(Self) = -1 then begin
//    GIOHandlerClassList.Add(Self);
//  end;
end;

{
  Creates an IOHandler of type ABaseType, or descendant.
}
class function TIdIOHandler.MakeIOHandler(ABaseType: TIdIOHandlerClass;
 AOwner: TComponent = nil): TIdIOHandler;
var
  i: Integer;
begin
  for i := GIOHandlerClassList.Count - 1 downto 0 do begin
    if TIdIOHandlerClass(GIOHandlerClassList.Items[i]).InheritsFrom(ABaseType) then begin
      Result := TIdIOHandlerClass(GIOHandlerClassList.Items[i]).Create(nil);
      Exit;
    end;
  end;
  raise EIdException.Create(Format(RSIOHandlerTypeNotInstalled, [ABaseType.ClassName]));
end;

function TIdIOHandler.GetDestination: string;
begin
  Result := FDestination;
end;

procedure TIdIOHandler.SetDestination(AValue: string);
begin
  FDestination := AValue;
end;

procedure TIdIOHandler.BufferRemoveNotify(ASender: TObject; ABytes: Integer);
begin
  DoWork(wmRead, ABytes);
end;

procedure TIdIOHandler.WriteBufferOpen(AThreshhold: Integer);
begin
  FWriteBuffer := TIdBuffer.Create;
  FWriteBufferThreshhold := AThreshhold;
end;

procedure TIdIOHandler.WriteBufferClose;
begin
  try
    WriteBufferFlush;
  finally FreeAndNil(FWriteBuffer); end;
end;

procedure TIdIOHandler.WriteBufferFlush(AByteCount: Integer);
begin
  if FWriteBuffer.Size > 0 then begin
    if (AByteCount = -1) or (FWriteBuffer.Size < AByteCount) then begin
      WriteBytes(FWriteBuffer.Bytes,true);
      WriteBufferClear;
    end else begin
      Write(FWriteBuffer);
      FWriteBuffer.Remove(AByteCount);
    end;
  end;
end;

procedure TIdIOHandler.Write(ABuffer: TIdBuffer);
begin
  WriteBytes(ABuffer.Bytes);
end;

procedure TIdIOHandler.WriteBufferClear;
begin
  FWriteBuffer.Clear;
end;

procedure TIdIOHandler.WriteBufferCancel;
begin
  WriteBufferClear;
  WriteBufferClose;
end;

procedure TIdIOHandler.Write(AOut: string);
begin
  if AOut <> '' then begin
    WriteBytes(ToBytes(AOut));
  end;
end;

procedure TIdIOHandler.Write(AValue: Cardinal; AConvert: boolean);
begin
  if AConvert then begin
    AValue := GStack.HostToNetwork(AValue);
  end;
  WriteBytes(ToBytes(AValue));
end;

procedure TIdIOHandler.Write(AValue: Integer; AConvert: Boolean = True);
begin
  if AConvert then begin
    AValue := Integer(GStack.HostToNetwork(LongWord(AValue)));
  end;
  WriteBytes(ToBytes(AValue));
end;

procedure TIdIOHandler.Write(AValue: TStrings; AWriteLinesCount: Boolean = False);
var
  i: Integer;
begin
  WriteBufferOpen; try
    if AWriteLinesCount then begin
      Write(AValue.Count);
    end;
    for i := 0 to AValue.Count - 1 do begin
      WriteLn(AValue.Strings[i]);
    end;
  // Kudzu: I had an except here and a close, but really even if error we should
  // write out whatever we have. Very doubtful any errors will occur in above
  // code anyways unless given bad input, which incurs bigger problems anyways.
  finally WriteBufferClose; end;
end;

procedure TIdIOHandler.Write(AValue: SmallInt; AConvert: boolean = true);
begin
  if AConvert then begin
    AValue := SmallInt(GStack.HostToNetwork(Word(AValue)));
  end;
  WriteBytes(ToBytes(AValue));
end;

constructor TIdIOHandler.Create(AOwner: TComponent);
begin
  inherited;
  FRecvBufferSize := GRecvBufferSizeDefault;
  FSendBufferSize := GSendBufferSizeDefault;
  FMaxLineLength := IdMaxLineLengthDefault;
end;

function TIdIOHandler.ReadString(ABytes: Integer): string;
var
  LBytes:TIdBytes;
begin
  SetLength(LBytes, ABytes);
  if ABytes > 0 then begin
    ReadBytes(LBytes, Length(LBytes));
    result := BytesToString(LBytes);
  end;
end;

procedure TIdIOHandler.ReadStrings(ADest: TStrings; AReadLinesCount: Integer = -1);
var
  i: Integer;
begin
  if AReadLinesCount <= 0 then begin
    AReadLinesCount := ReadInteger;
  end;
  for i := 0 to AReadLinesCount - 1 do begin
    ADest.Add(ReadLn);
  end;
end;

function TIdIOHandler.ReadSmallInt(AConvert: boolean = true): SmallInt;
var
  LBytes:TIdBytes;
begin
  ReadBytes(LBytes, sizeof(SmallInt));
  Result:=BytesToShort(LBytes);
  if AConvert then begin
    Result := SmallInt(GStack.NetworkToHost(Word(Result)));
  end;
end;

function TIdIOHandler.ReadChar: Char;
var
  LBytes:TIdBytes;
begin
  ReadBytes(LBytes, SizeOf(Char));
  Result:=char(LBytes[0]);
end;

function TIdIOHandler.ReadInteger(AConvert: boolean): Integer;
var
  LBytes:TIdBytes;
begin
  ReadBytes(LBytes, SizeOf(integer));
  Result:=BytesToInteger(LBytes);
  if AConvert then begin
    Result := Integer(GStack.NetworkToHost(LongWord(Result)));
  end;

end;

function TIdIOHandler.ReadCardinal(AConvert: boolean): Cardinal;
var
  LBytes:TIdBytes;
begin
  ReadBytes(LBytes, SizeOf(LBytes));
  Result := BytesToCardinal(LBytes);
  if AConvert then begin
    Result := GStack.NetworkToHost(Result);
  end;
end;

function TIdIOHandler.ReadLn(ATerminator: string = LF;
 ATimeout: Integer = IdTimeoutDefault; AMaxLineLength: Integer = -1): string;
var
  LInputBufferSize: Integer;
  LSize: Integer;
  LTermPos: Integer;
  LReadLnStartTime: Cardinal;
begin
  if AMaxLineLength = -1 then begin
    AMaxLineLength := MaxLineLength;
  end;
  // User may pass '' if they need to pass arguments beyond the first.
  if Length(ATerminator) = 0 then begin
    ATerminator := LF;
  end;
  FReadLnSplit := False;
  FReadLnTimedOut := False;
  LTermPos := 0;
  LSize := 1;
  LReadLnStartTime:=GetTickCount;
  repeat
    LInputBufferSize := FInputBuffer.Size;
    if LInputBufferSize > 0 then begin
      LTermPos := FInputBuffer.Find(ATerminator, LSize);
      LSize := LInputBufferSize;
    end;
    if (LTermPos - 1 > AMaxLineLength) and (AMaxLineLength <> 0) then begin
      if MaxLineAction = maException then begin
        raise EIdReadLnMaxLineLengthExceeded.Create(RSReadLnMaxLineLengthExceeded);
      end else begin
        FReadLnSplit := True;
        Result := FInputBuffer.Extract(AMaxLineLength);
        Exit;
      end;
    // ReadFromStack blocks - do not call unless we need to
    end else if LTermPos = 0 then begin
      if (LSize > AMaxLineLength) and (AMaxLineLength <> 0) then begin
        if MaxLineAction = maException then begin
          raise EIdReadLnMaxLineLengthExceeded.Create(RSReadLnMaxLineLengthExceeded);
        end else begin
          FReadLnSplit := True;
          Result := FInputBuffer.Extract(AMaxLineLength);
          Exit;
        end;
      end;
      // ReadLn needs to call this as data may exist in the buffer, but no EOL yet disconnected
      CheckForDisconnect(True, True);
      // Can only return -1 if timeout
      FReadLnTimedOut := ReadFromSource(True, ATimeout
       , ATimeout = IdTimeoutDefault) = -1;
      if not FReadLnTimedOut and (ATimeout >= 0) then begin
        if GetTickDiff(LReadLnStartTime, GetTickCount) >= Cardinal(ATimeout)
         then begin
          FReadLnTimedOut := True;
        end;
      end;
      if ReadLnTimedout then begin
        Result := '';
        Exit;
      end;
    end;
  until LTermPos > 0;
  dec(LTermPos);// Strip terminators (string len w/o first terminator char)
  Result := FInputBuffer.Extract(LTermPos + Length(ATerminator));// Extract actual data
  if (ATerminator = LF) and (LTermPos > 0) and (Result[LTermPos] = CR) then begin
    SetLength(Result, LTermPos - 1);
  end else begin
    SetLength(Result, LTermPos);
  end;
end;

function TIdIOHandler.ReadLnSplit(var AWasSplit: Boolean;
 ATerminator: string = LF;
 ATimeout: Integer = IdTimeoutDefault;
 AMaxLineLength: Integer = -1): string;
var
  LModeWasException: Boolean;
begin
  LModeWasException := False;
  if MaxLineAction = maException then begin
    MaxLineAction := maSplit;
    LModeWasException := True;
  end;
  Result := ReadLn(ATerminator, ATimeout, AMaxLineLength);
  AWasSplit := FReadLnSplit;
  if LModeWasException = True then begin
    MaxLineAction := maException;
  end;
end;

function TIdIOHandler.ReadLnWait(AFailCount: Integer = MaxInt): string;
var
  LAttempts: Integer;
begin
  Result := '';
  LAttempts := 0;
  while (Length(Result) = 0) and (LAttempts < AFailCount) do begin
    Inc(LAttempts);
    Result := Trim(ReadLn);
  end;
end;

procedure TIdIOHandler.Write(AStream: TIdStream; ASize: Integer = 0;
 AWriteByteCount: Boolean = FALSE);
var
  LBuffer: TIdBytes;
  LBufSize: Integer;
begin
  if ASize < 0 then begin //"-1" All form current position
    LBufSize := AStream.Stream.Seek(0,soFromCurrent);
    ASize := AStream.Stream.Seek(0,soFromEnd);
    AStream.Stream.Seek(LBufSize, soFromBeginning);
    ASize := ASize - LBufSize;
  end
  else if ASize = 0 then begin //"0" ALL
    ASize := AStream.Stream.Size;
    AStream.Stream.Position := 0;
  end;
  //else ">0" ACount bytes
  if AWriteByteCount then begin
  	Write(ASize);
  end;

  BeginWork(wmWrite, ASize); try
    while ASize > 0 do begin
      SetLength(LBuffer, FSendBufferSize); //BGO: bad for speed
      LBufSize := Min(ASize, FSendBufferSize);
      // Do not use ReadBuffer. Some source streams are real time and will not
      // return as much data as we request. Kind of like recv()
      // NOTE: We use .Size - size must be supported even if real time
      LBufSize := AStream.ReadBytes(LBuffer, LBufSize, 0, False);
      if LBufSize = 0 then begin
        raise EIdNoDataToRead.Create(RSIdNoDataToRead);
      end;
      SetLength(LBuffer, LBufSize);
      WriteBytes(LBuffer);
      Dec(ASize, LBufSize);
    end;
  finally
    EndWork(wmWrite);
    LBuffer := nil;
  end;
end;

//procedure TIdIOHandler.ReadBuffer(var VBuffer; AByteCount: Integer);
//begin
//  if (AByteCount > 0) and (@ABuffer <> nil) then begin
    // Read from stack until we have enough data
//    while (FInputBuffer.Size < AByteCount) do begin
//      ReadFromSource(false);
//      CheckForDisconnect(True, True);
//    end;
//    FInputBuffer.ExtractToBuffer(ABuffer, AByteCount);
//  end;
//end;

procedure TIdIOHandler.ReadBytes(var VBuffer:TIdBytes; AByteCount: Integer);
begin
  if AByteCount > 0 then begin
    // Read from stack until we have enough data
    while FInputBuffer.Size < AByteCount do begin
      ReadFromSource(False);
      CheckForDisconnect(True, True);
    end;
    FInputBuffer.ExtractToBytes(VBuffer, AByteCount);
  end;
end;

procedure TIdIOHandler.WriteLn(AOut: string);
begin
  // Do as one write so it only makes one call to network
  Write(AOut + EOL);
end;

function TIdIOHandler.Readable(AMSec: Integer): Boolean;
begin
  // In case descendant does not override this or other methods but implements the higher level
  // methods
  Result := False;
end;

procedure TIdIOHandler.SetHost(AValue: string);
begin
  FHost := AValue;
end;

procedure TIdIOHandler.SetPort(AValue: Integer);
begin
  FPort := AValue;
end;

function TIdIOHandler.Connected: Boolean;
begin
  CheckForDisconnect(False);
  Result :=
   (
    // Set when closed properly. Reflects actual socket state.
    (ClosedGracefully = False)
    // Created on Open. Prior to Open ClosedGracefully is still false.
    and (FInputBuffer <> nil)
   )
   // Buffer must be empty. Even if closed, we are "connected" if we still have
   // data
   or (InputBufferIsEmpty = False);
end;

procedure TIdIOHandler.ReadStream(AStream: TIdStream; AByteCount: Integer;
  AReadUntilDisconnect: Boolean);
var
  i: Integer;
  LBuf: TIdBytes;
  LBufSize: Integer;
  LWorkCount: Integer;

  procedure AdjustStreamSize(AStream: TIdStream; ASize: Integer);
  var
    LStreamPos: LongInt;
  begin
    LStreamPos := AStream.Stream.Position;
    AStream.Stream.Size := ASize;
    // Must reset to original size as in some cases size changes position
    if AStream.Stream.Position <> LStreamPos then begin
      AStream.Stream.Position := LStreamPos;
    end;
  end;

begin
  if (AByteCount = -1) and (AReadUntilDisconnect = False) then begin
    // Read size from connection
    AByteCount := ReadInteger;
  end;
  // Presize stream if we know the size - this reduces memory/disk allocations to one time
  if AByteCount > -1 then begin
    AdjustStreamSize(AStream, AStream.Stream.Position + AByteCount);
  end;

  if AReadUntilDisconnect then begin
    LWorkCount := High(LWorkCount);
    BeginWork(wmRead);
  end else begin
    LWorkCount := AByteCount;
    BeginWork(wmRead, LWorkCount);
  end;

  try
    // If data already exists in the buffer, write it out first.
    if FInputBuffer.Size > 0 then begin
      i := Min(FInputBuffer.Size, LWorkCount);
      FInputBuffer.ExtractToStream(AStream, i);
      Dec(LWorkCount, i);
    end;

    LBufSize := Min(LWorkCount, RecvBufferSize);
    while Connected and (LWorkCount > 0) do begin
      i := Min(LWorkCount, LBufSize);
                                                                       
      //DONE -oAPR: Dont use a string, use a memory buffer or better yet the buffer itself.
      try
        try
          SetLength(LBuf, 0); // clear the buffer
          ReadBytes(LBuf, i);
        except
          on E: Exception do begin
            i := FInputBuffer.Size;
            FInputBuffer.ExtractToBytes(LBuf, i);
            if (not (E is EIdConnClosedGracefully))
             or (AReadUntilDisconnect = False) then begin
              raise;
            end;
          end;
        end;
      finally
        if i > 0 then begin
          AStream.Write(LBuf, i);
          Dec(LWorkCount, i);
        end;
      end;
    end;
  finally
    EndWork(wmRead);
    if AStream.Stream.Size > AStream.Stream.Position then begin
      AStream.Stream.Size := AStream.Stream.Position;
    end;
    LBuf := NIL;
  end;
end;

procedure TIdIOHandler.RaiseConnClosedGracefully;
begin
  (* ************************************************************* //
  ------ If you receive an exception here, please read. ----------

  If this is a SERVER
  -------------------
  The client has disconnected the socket normally and this exception is used to notify the
  server handling code. This exception is normal and will only happen from within the IDE, not
  while your program is running as an EXE. If you do not want to see this, add this exception
  or EIdSilentException to the IDE options as exceptions not to break on.

  From the IDE just hit F9 again and Indy will catch and handle the exception.

  Please see the FAQ and help file for possible further information.
  The FAQ is at http://www.nevrona.com/Indy/FAQ.html

  If this is a CLIENT
  -------------------
  The server side of this connection has disconnected normaly but your client has attempted
  to read or write to the connection. You should trap this error using a try..except.
  Please see the help file for possible further information.

  // ************************************************************* *)
  raise EIdConnClosedGracefully.Create(RSConnectionClosedGracefully);
end;

function TIdIOHandler.InputBufferAsString: string;
begin
  Result := FInputBuffer.Extract(FInputBuffer.Size);
end;

function TIdIOHandler.AllData: string;
begin
  BeginWork(wmRead); try
    Result := '';
    while Connected do begin
      CheckForDataOnSource(250);
      Result := Result + InputBufferAsString;
    end;
  finally EndWork(wmRead); end;
end;

procedure TIdIOHandler.PerformCapture(ADest: TObject;
 out VLineCount: Integer; ADelim: string;
AIsRFCMessage: Boolean);
var
  s: string;
  LStream: TIdStream;
  LStrings: TStrings;
begin
  VLineCount := 0;

  LStream := nil;
  LStrings := nil;
  try
    if ADest is TStrings then begin
      LStrings := TStrings(ADest);
    end else if ADest is TStream then begin
      LStream := TIdStream.Create(TStream(ADest));
    end else begin
      EIdObjectTypeNotSupported.Toss(RSObjectTypeNotSupported);
    end;

    BeginWork(wmRead); try
      repeat
        s := ReadLn;
        if s = ADelim then begin
          Exit;
        end;
        // For RFC 822 retrieves
        // No length check necessary, if only one byte it will be byte x + #0.
        if AIsRFCMessage and (Copy(s, 1, 2) = '..') then begin
          Delete(s, 1, 1);
        end;
        // Write to output
        Inc(VLineCount);
        if LStrings <> nil then begin
          LStrings.Add(s);
        end else if LStream <> nil then begin
          LStream.WriteLn(s);
        end;
      until False;
    finally EndWork(wmRead); end;
  finally
    if LStream <> nil then begin
      FreeAndNil(LStream);
    end;
  end;
end;

function TIdIOHandler.InputLn(AMask: String; AEcho: Boolean;
  ATabWidth, AMaxLineLength: Integer): String;
var
  i: Integer;
  LChar: Char;
  LTmp: string;
begin
  if AMaxLineLength = -1 then begin
    AMaxLineLength := MaxLineLength;
  end;
  Result := '';
  repeat
    LChar := ReadChar;
    i := Length(Result);
    if i <= AMaxLineLength then begin
      case LChar of
        BACKSPACE:
          begin
            if i > 0 then begin
              SetLength(Result, i - 1);
              if AEcho then begin
                Write(BACKSPACE + ' ' + BACKSPACE);
              end;
            end;
          end;
        TAB:
          begin
            if ATabWidth > 0 then begin
              i := ATabWidth - (i mod ATabWidth);
              LTmp := StringOfChar(' ', i);
              Result := Result + LTmp;
              if AEcho then begin
                Write(LTmp);
              end;
            end else begin
              Result := Result + LChar;
              if AEcho then begin
                Write(LChar);
              end;
            end;
          end;
        LF: ;
        CR: ;
        #27: ; //ESC - currently not supported
      else
        Result := Result + LChar;
        if AEcho then begin
          if Length(AMask) = 0 then begin
            Write(LChar);
          end else begin
            Write(AMask);
          end;
        end;
      end;
    end;
  until LChar = LF;
  // Remove CR trail
  i := Length(Result);
  while (i > 0) and (Result[i] in [CR, LF]) do begin
    Dec(i);
  end;
  SetLength(Result, i);
  if AEcho then begin
    WriteLn;
  end;
end;

function TIdIOHandler.WaitFor(AString: string): string;
                                                                   
                                        
                                     
                                                                                             
//      and leave the rest in the buffer.
begin
  Result := '';
  // NOTE: AnsiPos should be used here, but AnsiPos has problems if result has
  // any #0 in it, which is often the case. So currently this function is not
  // MBCS compliant and should not be used in MBCS environments. However this
  // function should only be used on incoming TCP text data as it is 7 bit
  // anyways.
  while Pos(AString, Result) = 0 do begin
    CheckForDataOnSource;
    Result := Result + InputBufferAsString;
    CheckForDisconnect;
  end;
end;

procedure TIdIOHandler.Capture(ADest: TStream; out VLineCount: Integer;
  ADelim: string; AIsRFCMessage: Boolean);
begin
  PerformCapture(ADest, VLineCount, ADelim, AIsRFCMessage);
end;

procedure TIdIOHandler.Capture(ADest: TStream; ADelim: string;
  AIsRFCMessage: Boolean);
var
  LLineCount: Integer;
begin
  PerformCapture(ADest, LLineCount, ADelim, AIsRFCMessage);
end;

procedure TIdIOHandler.Capture(ADest: TStrings; out VLineCount: Integer;
  ADelim: string; AIsRFCMessage: Boolean);
begin
  PerformCapture(ADest, VLineCount, ADelim, AIsRFCMessage);
end;

procedure TIdIOHandler.Capture(ADest: TStrings; ADelim: string;
  AIsRFCMessage: Boolean);
var
  LLineCount: Integer;
begin
  PerformCapture(ADest, LLineCount, ADelim, AIsRFCMessage);
end;

procedure TIdIOHandler.InputBufferToStream(AStream: TIdStream; AByteCount: Integer = -1);
begin
  FInputBuffer.ExtractToStream(AStream, AByteCount);
end;

function TIdIOHandler.InputBufferIsEmpty: Boolean;
begin
  if FInputBuffer = nil then begin
    Result := True;
  end else begin
    Result := FInputBuffer.Size = 0;
  end;
end;

procedure TIdIOHandler.WriteBytes(ABuffer: TIdBytes;
 AWriteNow: Boolean);
begin
  if (FWriteBuffer = nil) or AWriteNow then begin
    WriteToDestination(ABuffer);
  // Write Buffering is enabled
  end else begin
    FWriteBuffer.Write(ABuffer);
    if (FWriteBuffer.Size >= FWriteBufferThreshhold)
     and (FWriteBufferThreshhold > 0) then begin
                                                                              
      // Threshold.
      // That is do at least one physical send.
      WriteBufferFlush(FWriteBufferThreshhold);
    end;
  end;
end;

function TIdIOHandler.WriteFile(AFile: String;
 AEnableTransferFile: Boolean): Cardinal;
var
                                                                           
  LStream: TIdStream;
begin
  EIdFileNotFound.IfFalse(FileExists(AFile), Format(RSFileNotFound, [AFile]));
  LStream := TIdStream.Create
   (TFileStream.Create(AFile, fmOpenRead or fmShareDenyWrite)
   , True); try
    Write(LStream, 0);
    Result := LStream.Stream.Size;
  finally FreeAndNil(LStream); end;
end;

function TIdIOHandler.WriteBufferingActive: Boolean;
begin
  Result := FWriteBuffer <> nil;
end;

procedure TIdIOHandler.CloseGracefully;
begin
  FClosedGracefully := True
end;

procedure TIdIOHandler.InterceptWrite(var ABuffer: TIdBytes);
begin
  if Intercept <> nil then begin
    Intercept.Send(ABuffer);
  end;
end;

procedure TIdIOHandler.InterceptReceive(var ABuffer: TIdBytes);
begin
  if Intercept <> nil then begin
    Intercept.Receive(ABuffer);
  end;
end;

initialization
finalization
  FreeAndNil(GIOHandlerClassList)
end.
