{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.4    6/11/2004 8:40:12 AM  DSiders
  Added "Do not Localize" comments.

  Rev 1.3    2004.05.06 1:47:28 PM  czhower
  Now uses IndexOf

  Rev 1.2    2004.04.22 11:45:18 PM  czhower
  Bug fixes

  Rev 1.1    2004.02.09 9:16:58 PM  czhower
  Updated to compile and match lib changes.

  Rev 1.0    2004.02.03 12:39:10 AM  czhower
  Move

  Rev 1.14    2003.10.19 2:50:42 PM  czhower
  Fiber cleanup

  Rev 1.13    2003.10.11 5:44:20 PM  czhower
  Chained servers now functional.

  Rev 1.12    2003.07.17 4:42:08 PM  czhower
  More IOCP improvements.

  Rev 1.11    2003.07.17 3:55:18 PM  czhower
  Removed IdIOChainEngineIOCP and merged it into TIdChaingEngine in
  IdIOHandlerChain.pas.

  Rev 1.7    2003.07.14 11:00:52 PM  czhower
  More IOCP fixes.

  Rev 1.6    2003.07.14 12:54:34 AM  czhower
  Fixed graceful close detection if it occurs after connect.

  Rev 1.5    7/7/2003 1:25:26 PM  BGooijen
  Added BytesSent property to TIdWorkOpUnitWriteFile

  Rev 1.4    7/5/2003 11:47:14 PM  BGooijen
  Added TIdWorkOpUnitCheckForDisconnect and TIdWorkOpUnitWriteFile

  Rev 1.3    3/27/2003 2:43:06 PM  BGooijen
  Added woWriteStream and woWriteBuffer

  Rev 1.2    3/22/2003 09:45:30 PM  JPMugaas
  Now should compile under D4.

  Rev 1.1    3/2/2003 12:36:26 AM  BGooijen
  Added woReadBuffer and TIdWorkOpUnitReadBuffer to read a buffer. Now
  ReadBuffer doesn't use ReadStream any more.
  TIdIOHandlerChain.ReadLn now supports MaxLineLength (splitting, and
  exceptions).
  woReadLn doesn't check the intire buffer any more, but continued where it
  stopped the last time.
  Added basic support for timeouts (probably only on read operations, and maybe
  connect), accuratie of timeout is currently 500msec.

  Rev 1.0    2/27/2003 10:11:50 PM  BGooijen
  WorkOpUnits combined in one file
}

unit IdWorkOpUnits;

interface

uses
  Classes,
  IdWorkOpUnit, IdGlobal,
  SysUtils;

type
  TIdWorkOpUnitStreamBaseRead = class(TIdWorkOpUnitRead)
  protected
    FStream: TStream;
  public
    constructor Create(AStream: TStream); reintroduce; virtual;
  end;

  TIdWorkOpUnitStreamBaseWrite = class(TIdWorkOpUnitWrite)
  protected
    FFreeStream: Boolean;
    FStream: TStream;
  public
    constructor Create(
      AStream: TStream;
      AFreeStream: Boolean = True
      ); reintroduce; virtual;
    destructor Destroy; override;
  end;

  TIdWorkOpUnitWriteBuffer = class(TIdWorkOpUnitWrite)
  protected
    FBuffer: Pointer;
    FFreeBuffer: Boolean;
    FSize: Integer;
    //
    procedure Processing(ABytes: Integer); override;
    procedure Starting; override;
  public
    constructor Create(ABuffer: Pointer; ASize: Integer;
      AFreeBuffer: Boolean = True); reintroduce; virtual;
    destructor Destroy; override;
  end;

  TIdWorkOpUnitWriteFile = class(TIdWorkOpUnitWrite)
  protected
    FFilename: String;
    FBytesSent: Integer;
    //
    procedure Processing(ABytes: Integer); override;
    procedure Starting; override;
  public
    constructor Create(AFileName: string); reintroduce;
  end;

  TIdWorkOpUnitWriteStream = class(TIdWorkOpUnitStreamBaseWrite)
  protected
    FCount: Integer;
    FStartPos: Integer;
    //
    procedure Processing(ABytes: Integer); override;
    procedure Starting; override;
  public
    constructor Create(AStream: TStream; AStartPos, ACount: Integer;
     AFreeStream: Boolean); reintroduce; virtual;
  end;

  TIdWorkOpUnitWaitConnected = class(TIdWorkOpUnit)
  protected
    procedure Starting; override;
  public
    procedure Process(
      AOverlapped: PIdOverlapped;
      AByteCount: Integer
      ); override;
  end;

  TIdWorkOpUnitReadSized = class(TIdWorkOpUnitRead)
  protected
    FSize: Integer;
    //
    procedure Processing(
      ABuffer: TIdBytes
      ); override;
  public
    constructor Create(ASize: Integer); reintroduce;
  end;

  TIdWorkOpUnitReadSizedStream = class(TIdWorkOpUnitStreamBaseRead)
  protected
    FSize: Integer;
    //
    procedure Processing(
      ABuffer: TIdBytes
      ); override;
  public
    constructor Create(AStream: TStream; ASize: Integer);
     reintroduce;
  end;

  TIdWorkOpUnitReadLn = class(TIdWorkOpUnitRead)
  protected
    FLastPos: Integer;
    FMaxLength: Integer;
    FTerminator: string;
    //
    procedure Processing(
      ABuffer: TIdBytes
      ); override;
  public
    constructor Create(
      ATerminator: string;
      AMaxLength: Integer
      ); reintroduce;
  end;

  TIdWorkOpUnitReadUntilDisconnect = class(TIdWorkOpUnitStreamBaseRead)
  protected
    procedure Processing(
      ABuffer: TIdBytes
      ); override;
  end;

  TIdWorkOpUnitReadAvailable = class(TIdWorkOpUnitRead)
  protected
    procedure Processing(
      ABuffer: TIdBytes
      ); override;
  end;

implementation

{ TIdWorkOpUnitWriteStream }

constructor TIdWorkOpUnitWriteStream.Create(AStream: TStream; AStartPos,ACount:integer; AFreeStream: Boolean);
begin
  inherited Create(AStream, AFreeStream);
  FStream.Position := AStartPos;
  FCount := ACount;
end;

procedure TIdWorkOpUnitWriteStream.Processing(ABytes: Integer);
//TODO: This used to use pages from IdBuffer, which because of .Net do not exist
// anymore. We need to maybe keep a local persistent buffer instead then for
// storage reasons.
var
  LBuffer: TIdBytes;
  LSize: Integer;
begin
  FCount := FCount - ABytes;
  if FCount = 0 then begin
    Complete;
  end else begin
    FStream.Position := ABytes;
    //
    //TODO: Dont hard code this value. Also find an optimal size for IOCP
    LSize := Min(FCount, WOPageSize);
    SetLength(LBuffer, LSize);
    //
    FStream.ReadBuffer(LBuffer[0], LSize);
    Write(@LBuffer[0], LSize);
  end;
end;

procedure TIdWorkOpUnitWriteStream.Starting;
begin
  Processing(0);
end;

{ TIdWorkOpUnitWriteBuffer }

constructor TIdWorkOpUnitWriteBuffer.Create(ABuffer: pointer; ASize: integer; AFreeBuffer: Boolean = True);
begin
  inherited Create;
  FSize := ASize;
  FBuffer := ABuffer;
  FFreeBuffer := AFreeBuffer;
end;

destructor TIdWorkOpUnitWriteBuffer.Destroy;
begin
  if FFreeBuffer then begin
    FreeMem(FBuffer);
    FBuffer := nil;
  end;
  inherited;
end;

procedure TIdWorkOpUnitWriteBuffer.Processing(ABytes: Integer);
begin
  //TODO: Change the pointer to a type that points to bytes
  FBuffer := Pointer(Cardinal(FBuffer) + Cardinal(ABytes));
  FSize := FSize - ABytes;
  if FSize = 0 then begin
    Complete;
  end else begin
    //TODO: Reduce this down so it never sends more than a page
    Write(FBuffer, Min(FSize, WOPageSize));
  end;
end;

procedure TIdWorkOpUnitWriteBuffer.Starting;
begin
  Processing(0);
end;

{ TIdWorkOpUnitWriteFile }

constructor TIdWorkOpUnitWriteFile.Create(AFileName:string);
begin
  inherited Create;
  FFilename := AFileName;
end;

procedure TIdWorkOpUnitWriteFile.Processing(ABytes: Integer);
begin
  Assert(False, 'Need to implement WriteFile, also add to a bubble'); {do not localize}
end;

procedure TIdWorkOpUnitWriteFile.Starting;
begin
end;

{ TIdWorkOpUnitSizedStream }

constructor TIdWorkOpUnitReadSizedStream.Create(AStream: TStream; ASize:integer);
begin
  inherited Create(AStream);
  FSize := ASize;
end;

procedure TIdWorkOpUnitWaitConnected.Process(
  AOverlapped: PIdOverlapped;
  AByteCount: Integer
  );
begin
end;

procedure TIdWorkOpUnitWaitConnected.Starting;
begin
end;

{ TIdWorkOpUnitReadLn }

constructor TIdWorkOpUnitReadLn.Create(
  ATerminator: string;
  AMaxLength: Integer);
begin
  inherited Create;
  FLastPos := 1;
  FTerminator := ATerminator;
  FMaxLength := AMaxLength;
end;

procedure TIdWorkOpUnitReadLn.Processing(
  ABuffer: TIdBytes
  );
begin
  //TODO: ReadLn is very common. Need to optimize this class and maybe
  // even pass pack the result directly so we dont search twice.
  //Also allow for hinting from the user.
  IOHandler.InputBuffer.Write(ABuffer);
  if not IOHandler.Connected then begin
    Complete;
  end else if IOHandler.InputBuffer.IndexOf(FTerminator, FLastPos) = -1 then begin
    Read;
  end else begin
    Complete;
  end;
end;

procedure TIdWorkOpUnitReadUntilDisconnect.Processing(
  ABuffer: TIdBytes
  );
begin
  // 0 is disconnected, so keep requesting til 0
  if Length(ABuffer) = 0 then begin
    Complete;
  end else begin
    FStream.WriteBuffer(ABuffer[0], Length(ABuffer));
    Read;
  end;
end;

{ TIdWorkOpUnitReadAvailable }

procedure TIdWorkOpUnitReadAvailable.Processing(
  ABuffer: TIdBytes
  );
begin
  Complete;
end;

{ TIdWorkOpUnitReadSized }

constructor TIdWorkOpUnitReadSized.Create(ASize: Integer);
begin
  inherited Create;
  FSize := ASize;
end;

procedure TIdWorkOpUnitReadSized.Processing(
  ABuffer: TIdBytes
  );
begin
  IOHandler.InputBuffer.Write(ABuffer);
  FSize := FSize - Length(ABuffer);
  if FSize = 0 then begin
    Complete;
  end else begin
    Read;
  end;
end;

{ TIdWorkOpUnitStreamBaseRead }

constructor TIdWorkOpUnitStreamBaseRead.Create(AStream: TStream);
begin
  inherited Create;
  FStream := AStream;
end;

{ TIdWorkOpUnitStreamBaseWrite }

constructor TIdWorkOpUnitStreamBaseWrite.Create(AStream: TStream;
  AFreeStream: Boolean);
begin
  inherited Create;
  FStream := AStream;
  FFreeStream := AFreeStream;
end;

destructor TIdWorkOpUnitStreamBaseWrite.Destroy;
begin
  if FFreeStream then begin
    FreeAndNil(FStream);
  end;
  inherited;
end;

procedure TIdWorkOpUnitReadSizedStream.Processing(
  ABuffer: TIdBytes
  );
begin
  FStream.WriteBuffer(ABuffer[0], Length(ABuffer));
  FSize := FSize - Length(ABuffer);
  if FSize = 0 then begin
    Complete;
  end else begin
    Read;
  end;
end;

end.
