//lpdclient.h

#if !defined(__LPDCLIENT_H__)
#define __LPDCLIENT_H__

#include "lpdsystem.h"
#include "lpdsockets.h"
#include "lpdfilesystem.h"

#define MAX_BUF 1024

class Lpdclient {

public
  //public interface methods
  TInt Initialise();
  TInt Paused() const;
  TInt Resume();
  TInt ResultQueued();
  TInt Result() const;
  //public data - never read by this class 
  TBuf<64> resultString;
private
  //State machine methods
  TInt ProcessResult(TInt resultCode);
  TInt DoNext();
  
  //LPD protocol methods
  void SendQueueStateRequestA();
  void GetQueueStateResponseA();
  void SendSelectQueueA();
  void SendDataFileHeaderA();
  void SendDataA();
  void SendFileTerminatorA();
  void SendControlFileHeaderA();
  void SendControlFileA();
  void GetLpdResponseA();

  //File methods
  TInt FindPrintFile();
  TInt OpenPrintFile();
  void ReadPrintFileA();
  void ClosePrintFile();
  TInt DeletePrintFile();

  //member objetcs
  Lpdfilesystem filesystem;
  Lpdsockets sockets;   
  Lpdtimer timer;   
  Lpdsemaphore semaphore;

  //application data
  TBuf<MAX_BUF> buf;
  TBuf<1> resp; 
  TBuf<255> printFileName;
  TBuf<255> hostName;
  TBuf<255> queueName;
  TInt job;
  TInt fileSize;
  TInt socketNumber;

  //state machine data
  TBuf<64> successString;
  TBuf<64> pendingString;
  TInt successResult;
  TInt pendingResult;
  TInt paused;
  TInt result;  
  enum States {
    EFindFile,
    EFindFilePending,
    EWait,
    EFileOpen,
    EFileOpenPending,
    ENewJob,
    EGetRemoteAddr,
    EOpenSocketForJob,
    EOpenSocketForJobPending,
    EHostConnectForJob,
    ESelectQueue,
    EGetLPDResponse,
    ECheckLPDResponse,
    EDFHSend,
    EDFSend,
    EDFReadData,
    EDFSendData,
    EDFSendTerm,
    ECFHSend,
    ECFSend,
    EDisconnectAndRetry,
    EDisconnect,
    EDeleteFile,     
    EOpenSocketForQueue,
    EOpenSocketForQueuePending,
    EHostConnectForQueue,
    ESendQueueStateRequest,
    EGetQueueStateResponse,
    EGetQueueStateResponsePending,
    EExit
  };
  States state;
  States successState;
  States pendingState;
  States followonState;
  States errorState;

};

#endif

 //lpdclient.cpp v1.2

#include "lpdclient.h"

#define LPD_PORT 515
#define LPD_LHOST "psion"
#define LPD_RUSER "psion"
#define LPD_OK 0


TInt Lpdclient::Initialise() {
  paused=FALSE;
  job=0;
  resultString.Format(_L("Failed to Initialise\n"));
  if (timer.Initialise()!=0return KErrGeneral;
  if (semaphore.Initialise()!=0return KErrGeneral;
  if (sockets.Initialise()!=0return KErrGeneral;
  if (filesystem.Initialise()!=0return KErrGeneral;
  
  state=EExit;
  resultString=_L("Initialised OK\n");
   
   socketNumber=1023;
   
   successString=_L("");
   successState=EFindFile;
   successResult=KErrNone;
   
   pendingString=_L("");
   pendingResult=1;
   pendingState=EExit;
   
   errorState=EExit;
   semaphore.SignalA(KErrNone);
   return 0;
}

TInt Lpdclient::Paused() const {
  return paused;
}

TInt Lpdclient::Resume() {
  if (paused) {
    paused=FALSE;
    resultString=_L("");
    return DoNext();
  }
  return 0;
}

TInt Lpdclient::ResultQueued() {
  //Handle events
  
  if (sockets.ResultQueued()) {
    result = ProcessResult(sockets.Result());
    return TRUE;
  }
  
  if (timer.ResultQueued()) {
    result = ProcessResult(timer.Result());
    return TRUE;
  }
  
  if (semaphore.ResultQueued()) {
    result = ProcessResult(semaphore.Result());
    return TRUE;
  }
  
  return FALSE;
}

TInt Lpdclient::Result() const{
  return result;
}

TInt Lpdclient::ProcessResult(TInt resultCode) {
  
  if(resultCode==pendingResult) {
    //go on to pending state
    state=pendingState;   
    resultString=pendingString;
    if (!paused) return DoNext();
    return 0;
  }
  
  if(resultCode==successResult) {
    //go on to next state
    state=successState;
    resultString=successString;
    if (!paused) return DoNext();
    return 0;
  }
    
  //Only gets here if theres an error
  switch (resultCode) { //an error code
    case KErrEof:
      resultString=_L("EOF\n");
      break;
    case KErrCancel:
      resultString=_L("cancelled\n");
      break;
    case KErrTimedOut:
      resultString=_L("timed out\n");
      break;
    case KErrCouldNotConnect:
      resultString=_L("could not connect\n");
      break;
    default:
      resultString.Format(_L("error %d\n"),resultCode);
    break;
  }
  
  resultString+=_L("PAUSED\n");
  paused=TRUE;
  state=errorState;
  return 0;
}

TInt Lpdclient::DoNext() {
  successResult=KErrNone; //Can be overriden in certain steps
  successString=_L(""); //Only active in states where overridden
  pendingResult=1//Only active in states where overridden
  switch (state) {
    case EFindFile:
      resultString+=_L("Looking for print file...");
      pendingString=_L("");
    case EFindFilePending:
      successString=_L("found\n");
      successState=ENewJob;
      pendingResult=KErrNotFound;
      pendingState=EWait;
      followonState=EFindFilePending;
      errorState=EExit;
      semaphore.SignalA(FindPrintFile());
      break;
    case EWait: //Used multiple times in process
      successState=followonState;
      timer.WaitA(20);
      break;
    case ENewJob:
      job=job+1;
      resultString.AppendFormat(_L("Submitting job #%d\n"),job);
      successState=EFileOpen;;
      semaphore.SignalA(KErrNone);
      break;
    case EFileOpen:
      resultString.AppendFormat(_L("Opening %s\n"),printFileName.PtrZ());
      resultString+=_L("Waiting for spooling to stop...");
    case EFileOpenPending:
      successState=EGetRemoteAddr;  successString=_L("spooling stopped\n");
      pendingResult=KErrInUse; pendingState=EWait;  followonState=EFileOpenPending;
      errorState=EFindFile;
      semaphore.SignalA(OpenPrintFile());
      break;
    case EGetRemoteAddr:
      resultString.AppendFormat(_L("Looking up address for %s..."),hostName.PtrZ());
      successString=_L("OK\n");
      sockets.GetRemoteAddressA(hostName);
      successState=EOpenSocketForJob;
      errorState=EGetRemoteAddr;
      break;
    case EOpenSocketForJob: 
      resultString+=_L("Opening socket...");
    case EOpenSocketForJobPending:
      if (socketNumber--< 600) socketNumber=1023
      successString+=(_L("OK\n"));
      successState=EHostConnectForJob;
      pendingResult=KErrInUse;
      pendingState=EOpenSocketForJobPending;
      semaphore.SignalA(sockets.Open(socketNumber));
      break;
    case EHostConnectForJob:
      resultString.AppendFormat(_L("Conecting to host %s..."),hostName.PtrZ());
      successString=_L("OK\n");
      sockets.ConnectToHostA(LPD_PORT);
      successState=ESelectQueue;
      break;
    case ESelectQueue:
      resultString.AppendFormat(_L("Selecting queue %s..."),queueName.PtrZ());
      successString=_L("OK\n");
      SendSelectQueueA();
      successState=EGetLPDResponse;
      followonState=EDFHSend;
      errorState=EDisconnectAndRetry;
      break;
    case EGetLPDResponse: //used multiple times in one sequence
      resultString+=_L("Waiting for LPD response...");
      successString=_L("OK\n");
      successState=ECheckLPDResponse;
      GetLpdResponseA();
      break;
    case ECheckLPDResponse: //always follows GetLPDresponse
      if (resp[0]==0) {
        successState=followonState;
      }
      else { 
        resultString.Format(_L("LPD error %d\n"),resp[0]);
        successState=EDisconnectAndRetry;
      resp[0]=0;
      }
      semaphore.SignalA(KErrNone);
    break;   
    case EDFHSend:
      resultString+=_L("Sending DFH...");
      successString=_L("OK\n");
      successState=EGetLPDResponse;
      followonState=EDFSend;
      SendDataFileHeaderA();
      break;
    case EDFSend:
      resultString+=_L("Sending DF...");
      pendingString=_L(".");
      followonState=ECFHSend;
    case EDFReadData:
      successResult=KErrEof;
      successState=EDFSendTerm;
      pendingResult=KErrNone;
      pendingState=EDFSendData;
      ReadPrintFileA();
      break;
    case EDFSendData: //only used coupled to EDFReadData
      successState=EDFReadData;
      SendDataA();
      break;
    case EDFSendTerm:
      successString=_L("OK\n");
      successState=EGetLPDResponse;
      SendFileTerminatorA();
      break;
    case ECFHSend:
      resultString+=_L("Sending CFH...");
      successString=_L("OK\n");
      successState=EGetLPDResponse;
      followonState=ECFSend;
      SendControlFileHeaderA();
      break;
    case ECFSend:
      resultString+=_L("Sending CF...");
      successString=_L("OK\n");
      successState=EGetLPDResponse;
      followonState=EDisconnect;
      SendControlFileA();      
      break;   
    case EDisconnect: 
      resultString+=_L("Disconnecting...");
      resultString+=_L("OK\n");
      successState=EDeleteFile;
      ClosePrintFile(); 
      sockets.Close();
      semaphore.SignalA(KErrNone);
      break;
    case EDisconnectAndRetry: 
      resultString+=_L("Retrying Job\n");
      successState=ENewJob;
      ClosePrintFile(); 
      sockets.Close();
      timer.WaitA(20);
      break;
    case EDeleteFile:
      resultString.AppendFormat(_L("Deleting %s..."),printFileName.PtrZ());
      successString=_L("OK\n");       
      successState=EOpenSocketForQueue;
      errorState=EExit;
      semaphore.SignalA(DeletePrintFile());
      break;
    case EOpenSocketForQueue:
      resultString=_L("Opening socket...");
    case EOpenSocketForQueuePending:
      if (socketNumber--< 600) socketNumber=1023;
      successState=EHostConnectForQueue;
      successString=_L("OK\n"); 
      pendingResult=KErrInUse;
      pendingState=EOpenSocketForQueuePending;
      semaphore.SignalA(sockets.Open(socketNumber));
      break;
    case EHostConnectForQueue:
      resultString.AppendFormat(_L("Conecting to host %s..."),hostName.PtrZ());
      successString=_L("OK\n");
      successState=ESendQueueStateRequest;
      errorState=EOpenSocketForQueue;
      sockets.ConnectToHostA(LPD_PORT);
      break;
    case ESendQueueStateRequest:
      resultString+=_L("Sending queue state request...");
      successString=_L("OK\n");
      successState=EGetQueueStateResponse;
      SendQueueStateRequestA();
      break;
    case EGetQueueStateResponse:
      resultString+=_L("Queue state response:\n");
    case EGetQueueStateResponsePending:
      successResult=KErrEof;
      successState=EFindFile;
      pendingResult=KErrNone;
      pendingState=EGetQueueStateResponsePending;
      GetQueueStateResponseA();
      break;
    case EExit:
      resultString+=_L("Fatal Error - exiting\n");
      return KErrGeneral;
      break;
    default:
      resultString+=_L("Bad state - exiting\n");
      return KErrGeneral;
      break;
  }
  return KErrNone;
}


//LPD protocol methods

void Lpdclient::SendSelectQueueA() {
  buf.Format(_L("\2%s\12"),queueName.PtrZ());
  sockets.SendA(buf);
}

void Lpdclient::SendDataFileHeaderA() {
  buf.Format(_L("\3%d dfA%03d%s\12"),fileSize,job,LPD_LHOST);
  sockets.SendA(buf);
}

void Lpdclient::SendDataA() {
  sockets.SendA(buf);
}

void Lpdclient::SendFileTerminatorA() {
  buf.Fill(0,1); // file terminator
  sockets.SendA(buf);
}

void Lpdclient::SendControlFileHeaderA() {
  TInt controlFileBytes;// length of control file
  buf.Format(_L("H%s\nP%s\nfdfA%03d%s\n"),LPD_LHOST,LPD_RUSER,job,LPD_LHOST);
  controlFileBytes = buf.Length();
  buf.Format(_L("\2%d cfA%03d%s\12"),controlFileBytes,job,LPD_LHOST);
  sockets.SendA(buf);
}

void Lpdclient::SendControlFileA() {
  buf.Format(_L("H%s\nP%s\nfdfA%03d%s\n"),LPD_LHOST,LPD_RUSER,job,LPD_LHOST);
  buf.Append(0);
  sockets.SendA(buf);
}

void Lpdclient::GetLpdResponseA() {
  sockets.RecvA(resp);
}

void Lpdclient::SendQueueStateRequestA() {
  buf.Format(_L("\3%s\12"),queueName.PtrZ());
  sockets.SendA(buf);
}

void Lpdclient::GetQueueStateResponseA() {
  sockets.RecvOneOrMoreA(pendingString);
}

//File methods
TInt Lpdclient::FindPrintFile()  {
  // scans directory once for a valid printile name,
  // returns the name of the first valid PRINT file found

  TBufC<16> fileSpec = _L("*.lpd");
  TBufC<16> pathSpec = _L("C:\\");
  
  printFileName=_L("");
    
  TInt rc=filesystem.Find(fileSpec,pathSpec,printFileName);
  
  if (rc==0) {              
    
    if (printFileName.Length() < 12return KErrBadName;
     
    if (printFileName.Locate('@') < 5)      //012345678901
      return KErrBadName;                     //c:\ab@cd.lpd
    else
      queueName=printFileName.Mid(3,printFileName.Locate('@')-3);

    if (printFileName.Locate('.') < (printFileName.Locate('@')+2))
      return KErrBadName;
    else
      hostName=printFileName.Mid(printFileName.Locate('@')+1,printFileName.Locate('.')-printFileName.Locate('@')-1);
  }
  return rc;
}

TInt Lpdclient::OpenPrintFile() { 
   fileSize=0;
  TInt rc=filesystem.Open(printFileName);
    if (rc==0){
    fileSize=filesystem.Size();
  }
  return rc;
}

void Lpdclient::ReadPrintFileA() {
  TInt rc = filesystem.Read(buf,MAX_BUF);
  if (rc==KErrNone) {
    if (buf.Length()==0) { 
      semaphore.SignalA(KErrEof);
      return;
    }
  }
  semaphore.SignalA(rc);
}

void Lpdclient::ClosePrintFile()  {
  filesystem.Close();   
}

TInt Lpdclient::DeletePrintFile() {
  return filesystem.Delete(printFileName);
}