{
  $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.12    7/24/04 12:56:14 PM  RLebeau
  Compiler fix for Print(TIdBytes)

  Rev 1.11    7/23/04 7:15:16 PM  RLebeau
  Added extra exception handling to various Print...() methods

  Rev 1.10    2004.05.20 11:36:50 AM  czhower
  IdStreamVCL

  Rev 1.9    2004.03.03 11:54:32 AM  czhower
  IdStream change

  Rev 1.8    2004.02.03 5:43:56 PM  czhower
  Name changes

  Rev 1.7    1/21/2004 3:11:22 PM  JPMugaas
  InitComponent

  Rev 1.6    10/24/2003 02:54:52 PM  JPMugaas
  These should now work with the new code.

  Rev 1.5    2003.10.24 10:43:10 AM  czhower
  TIdSTream to dos

  Rev 1.4    2003.10.12 4:04:00 PM  czhower
  compile todos

  Rev 1.3    2/24/2003 09:07:26 PM  JPMugaas

  Rev 1.2    2/6/2003 03:18:08 AM  JPMugaas
  Updated components that compile with Indy 10.

  Rev 1.1    12/6/2002 05:30:18 PM  JPMugaas
  Now decend from TIdTCPClientCustom instead of TIdTCPClient.

  Rev 1.0    11/13/2002 07:56:22 AM  JPMugaas

  27.07. rewrite component for integration
   in Indy core library
}

unit IdLPR;

{
  Indy Line Print Remote TIdLPR
  Version 9.1.0
  Original author Mario Mueller
  home: www.hemasoft.de
  mail: babelfisch@daybyday.de
}

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdAssignedNumbers, IdGlobal, IdException, IdTCPClient,
  IdComponent, IdBaseComponent;

type
  TIdLPRFileFormat =
    (ffCIF, // CalTech Intermediate Form
     ffDVI, //   DVI (TeX output).
     ffFormattedText, //add formatting as needed to text file
     ffPlot, //   Berkeley Unix plot library
     ffControlCharText, //text file with control charactors
     ffDitroff, // ditroff output
     ffPostScript, //Postscript output file
     ffPR,//'pr' format    {Do not Localize}
     ffFORTRAM, // FORTRAN carriage control
     ffTroff, //Troff output
     ffSunRaster); //  Sun raster format file

const
  DEF_FILEFORMAT = ffControlCharText;
  DEF_INDENTCOUNT = 0;
  DEF_BANNERPAGE = False;
  DEF_OUTPUTWIDTH = 0;
  DEF_MAILWHENPRINTED = False;

type
  TIdLPRControlFile = class(TPersistent)
  protected
    FBannerClass: String;			// 'C'    {Do not Localize}
    FHostName: String;				// 'H'    {Do not Localize}
    FIndentCount: Integer;		// 'I'    {Do not Localize}
    FJobName: String;					// 'J'    {Do not Localize}
    FBannerPage: Boolean;			// 'L'    {Do not Localize}
    FUserName: String;					// 'P'    {Do not Localize}
    FOutputWidth: Integer;		// 'W'    {Do not Localize}

    FFileFormat : TIdLPRFileFormat;
    FTroffRomanFont : String; //substitue the Roman font with the font in file
    FTroffItalicFont : String;//substitue the Italic font with the font in file
    FTroffBoldFont : String;  //substitue the bold font with the font in file
    FTroffSpecialFont : String; //substitue the special font with the font
                                //in this file
    FMailWhenPrinted : Boolean; //mail me when you have printed the job
  public
    constructor Create;
    procedure Assign(Source: TPersistent); override;
    property HostName: String read FHostName write FHostName;
  published
    property BannerClass: String read FBannerClass write FBannerClass;
    property IndentCount: Integer read FIndentCount write FIndentCount default DEF_INDENTCOUNT;
    property JobName: String read FJobName write FJobName;
    property BannerPage: Boolean read FBannerPage write FBannerPage default DEF_BANNERPAGE;
    property UserName: String read FUserName write FUserName;
    property OutputWidth: Integer read FOutputWidth write FOutputWidth default DEF_OUTPUTWIDTH;
    property FileFormat: TIdLPRFileFormat read FFileFormat write FFileFormat default DEF_FILEFORMAT;
    {font data }
    property TroffRomanFont : String read FTroffRomanFont write FTroffRomanFont;
    property TroffItalicFont : String read FTroffItalicFont write FTroffItalicFont;
    property TroffBoldFont : String read FTroffBoldFont write FTroffBoldFont;
    property TroffSpecialFont : String read FTroffSpecialFont write FTroffSpecialFont;
    {misc}
    property MailWhenPrinted : Boolean read FMailWhenPrinted write FMailWhenPrinted default DEF_MAILWHENPRINTED;
  end;

type
  TIdLPRStatus = (psPrinting, psJobCompleted, psError, psGettingQueueState,
    psGotQueueState, psDeletingJobs, psJobsDeleted, psPrintingWaitingJobs,
    psPrintedWaitingJobs);

type
  TIdLPRStatusEvent = procedure(ASender: TObject;
    const AStatus: TIdLPRStatus;
    const AStatusText: String) of object;

type
  TIdLPR = class(TIdTCPClientCustom)
  protected
    FOnLPRStatus: TIdLPRStatusEvent;
    FQueue: String;
    FJobId: Integer;
    FControlFile: TIdLPRControlFile;
    procedure DoOnLPRStatus(const AStatus: TIdLPRStatus;
    const AStatusText: String);
    procedure SeTIdLPRControlFile(const Value: TIdLPRControlFile);
    procedure CheckReply;
    function GetJobId: String;
    procedure SetJobId(const Value: String);
    procedure InternalPrint(Data: TStream);
    function GetControlData: String;
    procedure InitComponent; override;
  public
    {$IFDEF WORKAROUND_INLINE_CONSTRUCTORS}
    constructor Create(AOwner: TComponent); reintroduce; overload;
    {$ENDIF}
    destructor Destroy; override;
    procedure Connect; override;
    procedure Print(const AText: String); overload;
    procedure Print(const ABuffer: TIdBytes); overload;
    procedure PrintFile(const AFileName: String);
    function GetQueueState(const AShortFormat: Boolean = False; const AList : String = '') : String;    {Do not Localize}
    procedure PrintWaitingJobs;
    procedure RemoveJobList(const AList: String; const AAsRoot: Boolean = False);
    property JobId: String read GetJobId write SetJobId;
  published
    property Queue: String read FQueue write FQueue;
    property ControlFile: TIdLPRControlFile read FControlFile write SeTIdLPRControlFile;
    property Host;
    property Port default IdPORT_LPD;
    property OnLPRStatus: TIdLPRStatusEvent read FOnLPRStatus write FOnLPRStatus;
  end;

type
  EIdLPRErrorException = class(EIdException);

implementation

uses
  {$IFDEF DOTNET}
  IdStreamNET,
  {$ELSE}
  IdStreamVCL,
  {$ENDIF}
  IdGlobalProtocols, IdResourceStringsProtocols, IdStack, IdStackConsts,
  SysUtils;

{ TIdLPR }

{$IFDEF WORKAROUND_INLINE_CONSTRUCTORS}
constructor TIdLPR.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
end;
{$ENDIF}

procedure TIdLPR.InitComponent;
begin
  inherited InitComponent;

  Port := IdPORT_LPD;
  Queue := 'pr1';    {Do not Localize}
  FJobId := 1;
  FControlFile := TIdLPRControlFile.Create;

  // Restriction in RFC 1179
  // The source port must be in the range 721 to 731, inclusive.

  BoundPortMin := 721;
  BoundPortMax := 731;
end;

procedure TIdLPR.Connect;
var
  LPort: TIdPort;
begin
  // RLebeau 3/7/2010: there is a problem on Windows where sometimes it will
  // not raise a WSAEADDRINUSE error in TIdSocketHandle.TryBind(), but will
  // delay it until TIdSocketHandle.Connect() instead.  So we will loop here
  // to force a Connect() on each port, rather than let TIdSocketHandle do
  // the looping in BindPortReserved().  If this logic proves useful in other
  // protocols, we can move it into TIdSocketHandle later on...

  // AWinkelsdorf 3/9/2010: Implemented, adjusted to use BoundPortMax and
  // BoundPortMin

  // looping backwards because that is what TIdSocketHandle.BindPortReserved() does
  for LPort := BoundPortMax downto BoundPortMin do
  begin
    BoundPort := LPort;
    try
      inherited Connect;
      Exit;
    except
      on E: EIdCouldNotBindSocket do begin end;
      on E: EIdSocketError do begin
        if E.LastError <> Id_WSAEADDRINUSE then begin
          raise;
        end;
        // Socket already in use, cleanup and try again with the next
        Disconnect;
      end;
    end;
  end;

  // no local ports could be bound successfully
  raise EIdCanNotBindPortInRange.CreateFmt(RSCannotBindRange, [BoundPortMin, BoundPortMax]);
end;

procedure TIdLPR.Print(const AText: String);
var
  LStream: TStream;
  LEncoding: IIdTextEncoding;
begin
  LStream := TMemoryStream.Create;
  try
    LEncoding := IndyTextEncoding_8Bit;
    WriteStringToStream(LStream, AText, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
    LEncoding := nil;
    LStream.Position := 0;
    InternalPrint(LStream);
  finally
    FreeAndNil(LStream);
  end;
end;

procedure TIdLPR.Print(const ABuffer: TIdBytes);
var
  LStream: TMemoryStream;
begin
  LStream := TMemoryStream.Create;
  try
    WriteTIdBytesToStream(LStream, ABuffer);
    LStream.Position := 0;
    InternalPrint(LStream);
  finally
    FreeAndNil(LStream);
  end;
end;

procedure TIdLPR.PrintFile(const AFileName: String);
var
  LStream: TIdReadFileExclusiveStream;
  p: Integer;
begin
  p := RPos(GPathDelim, AFileName);
  ControlFile.JobName := Copy(AFileName, p+1, Length(AFileName)-p);
  LStream := TIdReadFileExclusiveStream.Create(AFileName);
  try
    InternalPrint(LStream);
  finally
    FreeAndNil(LStream);
  end;
end;

function TIdLPR.GetJobId: String;
begin
  Result := IndyFormat('%.3d', [FJobId]);    {Do not Localize}
end;

procedure TIdLPR.SetJobId(const Value: String);
var
  I: Integer;
begin
  I := IndyStrToInt(Value);
  if I < 999 then begin
    FJobId := I;
  end;
end;

procedure TIdLPR.InternalPrint(Data: TStream);
begin
  try
    if not Connected then begin
      Exit;
    end;
    Inc(FJobID);
    if FJobID > 999 then begin
      FJobID := 1;
    end;
    DoOnLPRStatus(psPrinting, JobID);
    try
      ControlFile.HostName := GStack.HostName
    except
      ControlFile.HostName := 'localhost';    {Do not Localize}
    end;

    // Receive a printer job
    IOHandler.Write(#02 + Queue + LF);
    CheckReply;
    // Receive control file
    IOHandler.Write(#02 + IntToStr(Length(GetControlData)) + ' cfA' + JobId + ControlFile.HostName + LF);    {Do not Localize}
    CheckReply;
    // Send control file
    IOHandler.Write(GetControlData);
    IOHandler.Write(#0);
    CheckReply;
    // Send data file
    IOHandler.Write(#03 + IntToStr(Data.Size) +	' dfA'  + JobId + ControlFile.HostName + LF);   {Do not Localize}
    CheckReply;
    // Send data
    IOHandler.Write(Data);
    IOHandler.Write(#0);
    CheckReply;
    DoOnLPRStatus(psJobCompleted, JobID);
  except
    on E: Exception do begin
      DoOnLPRStatus(psError, E.Message);
    end;
  end;
end;

function TIdLPR.GetQueueState(const AShortFormat: Boolean = False; const AList : String = '') : String;    {Do not Localize}
begin
  DoOnLPRStatus(psGettingQueueState, AList);
  if AShortFormat then begin
    IOHandler.Write(#03 + Queue + ' ' + AList + LF)    {Do not Localize}
  end else begin
    IOHandler.Write(#04 + Queue + ' ' + AList + LF);    {Do not Localize}
  end;
//  This was the original code - problematic as this is more than one line
//  read until I close the connection
//  result:=ReadLn(LF);
  Result := IOHandler.AllData;
  DoOnLPRStatus(psGotQueueState, result);
end;

function TIdLPR.GetControlData: String;
var
  Data: String;
begin
  Data := '';    {Do not Localize}
  try
    // H - Host name
    Data := Data + 'H' + FControlFile.HostName + LF;    {Do not Localize}
    // P - User identification
    Data := Data + 'P' + FControlFile.UserName + LF;    {Do not Localize}
    // J - Job name for banner page
    if Length(FControlFile.JobName) > 0 then begin
      Data := Data + 'J' + FControlFile.JobName + LF;    {Do not Localize}
    end else begin
      Data := Data + 'JcfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
    end;
    //mail when printed
    if FControlFile.FMailWhenPrinted then begin
      Data := Data + 'M' + FControlFile.UserName + LF;    {Do not Localize}
    end;
    case FControlFile.FFileFormat of
       ffCIF : // CalTech Intermediate Form
       begin
         Data := Data + 'cdfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffDVI : //   DVI (TeX output).
       begin
         Data := Data + 'ddfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffFormattedText : //add formatting as needed to text file
       begin
         Data := Data + 'fdfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffPlot : //   Berkeley Unix plot library
       begin
         Data := Data + 'gdfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffControlCharText : //text file with control charactors
       begin
         Data := Data + 'ldfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffDitroff : // ditroff output
       begin
         Data := Data + 'ndfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffPostScript : //Postscript output file
       begin
         Data := Data + 'odfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffPR : //'pr' format    {Do not Localize}
       begin
         Data := Data + 'pdfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffFORTRAM : // FORTRAN carriage control
       begin
         Data := Data + 'rdfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffTroff : //Troff output
       begin
         Data := Data + 'ldfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}
       end;
       ffSunRaster : //  Sun raster format file
       begin
       end;
    end;
    // U - Unlink data file
    Data := Data + 'UdfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}

    // N - Name of source file
    Data := Data + 'NcfA' + JobId + FControlFile.HostName + LF;    {Do not Localize}

    if FControlFile.FFileFormat = ffFormattedText then begin
      if FControlFile.IndentCount > 0 then begin
        Data := Data + 'I' + IntToStr(FControlFile.IndentCount) + LF;    {Do not Localize}
      end;
      if FControlFile.OutputWidth > 0 then begin
        Data := Data + 'W' + IntToStr(FControlFile.OutputWidth) + LF;    {Do not Localize}
      end;
    end;
    if Length(FControlFile.BannerClass) > 0 then begin
      Data := Data + 'C' + FControlFile.BannerClass + LF;    {Do not Localize}
    end;
    if FControlFile.BannerPage then begin
      Data := Data + 'L' + FControlFile.UserName + LF;    {Do not Localize}
    end;
    if Length(FControlFile.TroffRomanFont) > 0 then begin
      Data := Data + '1' + FControlFile.TroffRomanFont + LF;    {Do not Localize}
    end;
    if Length(FControlFile.TroffItalicFont) > 0 then begin
      Data := Data + '2' + FControlFile.TroffItalicFont + LF;    {Do not Localize}
    end;
    if Length(FControlFile.TroffBoldFont) > 0 then begin
      Data := Data + '3' + FControlFile.TroffBoldFont + LF;    {Do not Localize}
    end;
    if Length(FControlFile.TroffSpecialFont) > 0 then begin
      Data := Data + '4' + FControlFile.TroffSpecialFont + LF;    {Do not Localize}
    end;
    Result := Data;
  except
    Result := 'error';    {Do not Localize}
  end;
end;

procedure TIdLPR.SeTIdLPRControlFile(const Value: TIdLPRControlFile);
begin
  FControlFile.Assign(Value);
end;

destructor TIdLPR.Destroy;
begin
  FreeAndNil(FControlFile);
  inherited Destroy;
end;

procedure TIdLPR.PrintWaitingJobs;
begin
  try
    DoOnLPRStatus(psPrintingWaitingJobs, '');    {Do not Localize}
    IOHandler.Write(#03 + Queue + LF);
    CheckReply;
    DoOnLPRStatus(psPrintedWaitingJobs, '');    {Do not Localize}
  except
    on E: Exception do begin
      DoOnLPRStatus(psError, E.Message);
    end;
  end;
end;

procedure TIdLPR.RemoveJobList(const AList: String; const AAsRoot: Boolean = False);
begin
  try
    DoOnLPRStatus(psDeletingJobs, JobID);
    if AAsRoot then begin
      {Only root can delete other people's print jobs}    {Do not Localize}
      IOHandler.Write(#05 + Queue + ' root ' + AList + LF);    {Do not Localize}
    end else begin
      IOHandler.Write(#05 + Queue + ' ' + ControlFile.UserName + ' ' + AList + LF);    {Do not Localize}
    end;
    CheckReply;
    DoOnLPRStatus(psJobsDeleted, JobID);
  except
    on E: Exception do begin
      DoOnLPRStatus(psError, E.Message);
    end;
  end;
end;

procedure TIdLPR.CheckReply;
var
  Ret : Byte;
begin
  Ret := IOHandler.ReadByte;
  if Ret <> $00 then begin
    raise EIdLPRErrorException.CreateFmt(RSLPRError, [Integer(Ret), JobID]);
  end;
end;

procedure TIdLPR.DoOnLPRStatus(const AStatus: TIdLPRStatus; const AStatusText: String);
begin
  if Assigned(FOnLPRStatus) then begin
    FOnLPRStatus(Self, AStatus, AStatusText);
  end;
end;

{ TIdLPRControlFile }
procedure TIdLPRControlFile.Assign(Source: TPersistent);
var
  cnt : TIdLPRControlFile;
begin
  if Source is TIdLPRControlFile then
  begin
    cnt := Source as TIdLPRControlFile;
    FBannerClass := cnt.BannerClass;
    FIndentCount := cnt.IndentCount;
    FJobName := cnt.JobName;
    FBannerPage := cnt.BannerPage;
    FUserName := cnt.UserName;
    FOutputWidth := cnt.OutputWidth;
    FFileFormat := cnt.FileFormat;
    FTroffRomanFont := cnt.TroffRomanFont;
    FTroffItalicFont := cnt.TroffItalicFont;
    FTroffBoldFont := cnt.TroffBoldFont;
    FTroffSpecialFont := cnt.TroffSpecialFont;
    FMailWhenPrinted := cnt.MailWhenPrinted;
  end else begin
    inherited Assign(Source);
  end;
end;

constructor TIdLPRControlFile.Create;
begin
  inherited Create;
  try
    HostName := GStack.HostName;
  except
    HostName := RSLPRUnknown;   
  end;
  FFileFormat := DEF_FILEFORMAT;
  FIndentCount := DEF_INDENTCOUNT;
  FBannerPage := DEF_BANNERPAGE;
  FOutputWidth := DEF_OUTPUTWIDTH;
end;

end.
