Fax Server Progress Notification

The fax server components in Async Professional 3.0 let you create a distributed fax server/client system, but there's something missing.  When a client submits a fax job, the server handles the job and keeps all of the status reporting to itself.  The client will never know whether the fax was sent, or the error if there was one. Even though the ability to send status notification messages is not built into the components, you can still do it with just a few lines of code.

To implement progress notification we will need to find a way to send contact information about the client to the server, and find a way to transmit the notification from the server back to the client. Fortunately, both of these tasks are relatively easy to do with Async Pro. We can add the contact information to the header of the fax job file, and transmit progress notification messages using the ApdWinsockPort.

The general idea is for the client to send it's own IP address to the server; the server creates a Winsock connection to the client and sends progress messages.  Pretty simple in theory; and, surprisingly, fairly simple in implementation.  The client will need to send it's IP address in the fax job file and the server will need to extract the client's IP address.  (Here's where it gets a little confusing, but stay with me)  To transmit the notification messages from the fax server to the fax client, the fax client will need a Winsock server and the fax server will need a Winsock client.  We'll do it this way so the fax server can connect, transmit messages and disconnect when it needs to.

We'll be modifying the FxClient and FxSrvr example projects to illustrate what we're talking about.  This will let us concentrate on the applicable code and leave the regular fax client/server setup alone.

We'll start off with the client side of things, so open up the FxClient example project. We will be sending contact information to the server so the server can send us notification.  Since we are going to use the ApdWinsockPort for progress notification, we will need to send the network address of the client to the server. To add the network address to the fax job file, we'll need to modify one of the headers in the file.  There are two types of headers in the APJ file, the TFaxJobHeaderRec which is used to define the entire fax job file, and the TFaxRecipientRec which is used to define each fax in the file. The TFaxJobHeaderRec is not easily accessible from the fax server components, but the TFaxRecipientRec is, so we will use that record for our contact information.  Here's what the TFaxRecipientRec looks like:

  TFaxRecipientRec = packed record
    Status         : Byte;             {0=not sent, 1=sending, 2=sent, 3=paused}
    JobID          : Byte;             {Unique ID for this job}
    SchedDT        : TDateTime;        {TDateTime this job should be sent}
    AttemptNum     : Byte;             {Retry number for this recipient}
    LastResult     : Word;             {Last ErrorCode for this fax}
    PhoneNumber    : String[50];       {Phone number to dial for this job}
    HeaderLine     : String[100];      {Header line}
    HeaderRecipient: String[30];       {Recipient's name}
    HeaderTitle    : String[30];       {Title of fax}
    Padding        : Array[228..256] of Byte;{Expansion room}
  end;

There are a few places we can put the client's network address, we could use the PhoneNumber, HeaderLine, HeaderRecipient, or HeaderTitle fields, but those are used elsewhere in the fax server components. We'll use the often-overlooked Padding field. The Padding field is not used by anything else in the fax server components, so we are pretty safe in using it for our purposes.  We also have 28 characters to play around with, which is more than adequate for a network address in dot-notation.

OK, so we've decided where to put the information, now how do we get the information in there?  When you create a fax job (using the ApdFaxJobHandler.MakeJob method) you pass in a TFaxRecipientRec, so you can set the Padding field to the network address from there.  If you add additional recipients, you pass in another TFaxRecipientRec in the AddRecipient method.  You can get the client's IP address with the ApdSocket.LocalAddress property.

In the FxClient example project, drop a TApdWinsockPort on the form, and set the DeviceLayer property to dlWinsock and the WsMode property to wsServer.  Go to the btnMakeJobClick event handler and add the lines marked by {!!.TT}:

procedure TfrmFaxClient0.btnMakeJobClick(Sender: TObject);
var                                                                                                                   {!!.TT}
  LocalAddress : string;                                                                         {!!.TT}
begin
  ApdFaxClient1.CoverFileName := edtCoverFileName.Text;
  ApdFaxClient1.FaxFileName := edtFaxFileName.Text;
  ApdFaxClient1.HeaderLine := edtHeaderLine.Text;
  ApdFaxClient1.HeaderRecipient := edtHeaderRecipient.Text;
  ApdFaxClient1.HeaderTitle := edtHeaderTitle.Text;
  ApdFaxClient1.JobName := edtJobName.Text;
  ApdFaxClient1.PhoneNumber := edtPhoneNumber.Text;
  ApdFaxClient1.ScheduleDateTime := StrToDate(edtSchedDate.Text) +
    StrToTime(edtSchedTime.Text);
  ApdFaxClient1.Sender := edtSender.Text;
  ApdFaxClient1.JobFileName := edtJobFileName.Text;
  LocalAddress := ApdSocket.LocalAddress;                                                   {!!.TT}
  Move(LocalAddress[1], ApdFaxClient1.Recipient.Padding[228],                   {!!.TT}
    Length(LocalAddress));                                                                                {!!.TT}
  ApdFaxClient1.MakeFaxJob;
end;

Since we are using the ApdSocket class, you will need to ad AwWnsock to the uses clause.  LocalAddress will hold the IP address of the local machine.  The Move method moves the LocalAddress into the Padding field.  Since the Padding field is defined as an Array [228..256], we need to start the string at the 228th index of the array, which is actually the 1st index . You can do the same thing to the btnAddRecipientClick event so the local IP address is added to the TFaxRecipientRec when a recipient is added.

We'll deal with handling the notification messages at the client shortly, but let's go to the server side now.  Save the changes you made to the FxClient project and open the FxSrvr example.  Let's go over what needs to happen on the server side, then dive into some code changes. There are several notification messages that we might want to send to the client, we can tell them when we start working with their fax, we can send them detailed status messages showing them exactly what is happening with their fax every step of the way, or we can just send them notification when their fax is finished sending.  For the sake of clarity, we'll be passing along all of the status messages provided in the OnFaxServerStatus event handler that are generated while sending a fax.

Drop a TApdWinsockPort on the form and set the DeviceLayer to dlWinsock.  When we get an OnFaxServerStatus event, we are going to pass the information to the client using the ApdWinsockPort. To make it easy for the fax client to tell when a status message starts and ends (we'll be using a TApdDataPacket on the fax client to capture it, but more on that later), we will bracket the status message between an STX character (#2) and an ETX character (#3). (STX stands for "Start TeXt", ETX stands for "End TeXt").

procedure TfrmFxSrv0.ApdFaxServer1FaxServerStatus(CP: TObject;
  FaxMode: TFaxServerMode; First, Last: Boolean; Status: Word);
var
  S: String;
begin
  if FaxMode = fsmSend then
    S := 'Send'
  else
    S := 'Receive';
  Add('Fax status (' + S + '): ' + ApdFaxServer1.StatusMsg(Status));
  { add this section }
  if FaxMode = fsmSend then begin
    if First then begin
      Move(ApdFaxServer1.CurrentRecipient.Padding[228], S[1], 28);
      ApdWinsockPort1.WsAddress := S;
      ApdWinsockPort1.Open := True;
    end;
    ApdWinsockPort1.Output :=  #2 + ApdFaxServer1.StatusMsg(Status) + #3;
    if Last then
      ApdWinsockPort1.Open := False;
  end;
end;

The First parameter of the event handler is True when we start sending the fax, so we extract the client's IP address from the Padding field and open the Winsock connection to the client.  The Last parameter of the event handler is True when the fax is complete (either successfully or unsuccessfully), so we close the Winsock connection.  For all other times that the OnFaxServerStatus event fires, we just send the textual status message to the client.

Believe it or not, but we are done with the server side, and can jump back into the client to handle the status messages as they arrive.  Save your changes to the FxSrvr example project and re-open the FxClient example project.

We want the client's ApdWinsockPort to listen for a connection from the fax server's ApdWinsockPort only when a fax job has been submitted, so we should set the ApdWinsockPort's Open property to True when we copy our APJ file to the directory being monitored by the ApdFaxServerManager component.  Let's assume that the user of the FxClient example will enter the monitored directory in the JobFileName edit control so the fax job file is automatically submitted.  Add "ApdWinsockPort1.Open := True;" to the end of the btnMakeJobClick event handler like this:

procedure TfrmFaxClient0.btnMakeJobClick(Sender: TObject);
var
  LocalAddress : string;
begin
  .
  ApdFaxClient1.MakeFaxJob;
  ApdWinsockPort1.Open := True;
end;

So, now we will listen for a connection as soon as we submit a fax job.  When the fax server starts sending our fax, it will connect to the client's ApdWinsockPort, causing the OnWsAccept event to be generated.  When the fax is complete, the fax server will close the Winsock connection and the client's OnWsDisconnect event will be generated.  Anything received between those two events are status messages that we can display to the user.

Remember back a few paragraphs when we talked about bracketing the status messages so the fax client can find them easily?  We are now at the point to capture the messages.  Drop a TApdDataPacket on the form and set the EndCond property to [ecString], EndString property to "^C" (for ETX, #3), IncludeStrings property to False, StartCond to scString, and StartString to "^B" (for STX, #2).  Create the ApdDataPacket's OnStringPacket event handler, which will be generated when we receive the status messages from the fax server.

For our purposes, we'll just put the status messages into a TLabel, so drop a TLabel on the bottom of the form and name it lblStatus or something like that.  When the ApdDataPacket's OnStringPacket event fires, we want to update the label with the text of the status message, so add something like this:

procedure TfrmFaxClient0.ApdDataPacket1StringPacket(Sender: TObject;
  Data: String);
begin
  lblStatus.Caption := Data;
end;

Save the changes that you have made to the FxClient example, and you're done. Here's what we did and why we did it:

The purpose of this Tech Tip is to show how to pass progress notification messages from a fax server back to the fax client.  To do this, we have to tell the fax server who created the fax job, and we have to send messages from the server to the client. We used the TFaxRecipientRec record to pass the client's IP address, and we used ApdWinsockPorts to send the status messages from the fax server to the fax client. To detect the status messages at the fax client, we used a TApdDataPacket to capture messages bracketed by the STX and ETX characters.

This site is not affiliated, endorsed, or otherwise associated with the entity formerly known as TurboPower Software. The owners and maintainers of Aprozilla.com were merely meager employees of the aforementioned organization, providing this site out of the pure goodness of their collective hearts. SourceForge.net Logo

Last updated: July 22, 2003.