Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

A Database System for Automating E-Mail


DEC90: A DATABASE SYSTEM FOR AUTOMATING E-MAIL

A DATABASE SYSTEM FOR AUTOMATING E-MAIL

Storing, receiving, and copying electronic messages

Chris Olsen

Chris is an engineer at Borland Intl., specializing in Turbo C++ and the Paradox Engine. He coauthored the book Turbo Pascal Advanced Techniques (Que, 1989) and can be reached at Borland Intl., P.O. Box 660001, Scotts Valley, CA 95066-0001.


The proliferation of electronic mail has become an accepted part of today's workplace. That's the good news. The bad news is that in many cases the sheer volume of electronic messages received demands some means of managing them. This article presents a message storage and retrieval system just for this purpose. For the sake of example, the system focuses on MCI Mail, a widely used electronic mail service, and is built around an off-the-shelf database and engine, Paradox from Borland.

For those unfamiliar with MCI Mail, it is an electronic mail service that lets you send messages to individual users (TO:) as well as to a long list of users (CC:). There is an "inbox" for messages sent to you and an "outbox" to save copies of messages you send. MCI also provides a number of advanced features, including those which let you customize your electronic "space" and gain access to other services.

For its part, the Paradox Engine is a database engine with a library of 70 functions callable from Turbo C or Microsoft C. Paradox is a relational database that includes a scripting language called "PAL" (Paradox Application Language) that lets you write within Paradox to manipulate the environment and perform Queries By Example (QBE), fast I/O, and quick prototypes. The inherent flexibility of Paradox and the Paradox Engine makes them ideal for creating an electronic mail storage and retrieval database system.

Before delving too deeply into the system presented here, a quick overview might be useful. As illustrated in Figure 1, your telecommunications program logs on to MCI and prints (and stores on disk) all messages in your MCI "inbox." The telecomm program then logs off MCI. The resulting text file is then parsed by the database application into several Paradox tables. The PAL script presents this information to you, allowing you to respond to a message or create a new one. Next, the database application parses any responses in another table back into a text file. Finally, the telecomm program uploads the responses and new messages to MCI.

Table Structures

To store MCI messages in a Paradox database, you need to develop a database structure. This can be done with four tables: the Header, Route, Message, and Pending tables.

The Header table, shown in Table 1(a), includes subject, date received, and name of sender information. With MCI, there are two ways to contact another user. You can use their "user name" (for example, John Smith or JSMITH) or their "MCI address" (for example, 555-1234). The MCI address is always unique but the user name may not be; there may be many MCI users named John Smith. (Therefore, if you send mail to a user name, MCI prompts you with a list of all users who have that name and their corresponding unique addresses; you then select the correct John Smith.) However, because the goal here is to develop an automatic system, you should use the MCI address.

Table 1: Table types: (a) defines the Header table, (b) Routing table, (c) Message table, and (d) Pending table

  Field Name        Field Type
  ----------------------------

  (a)  DateReceived  D*
       Message#      N*
       Subject       A40
       From User     A20
       From ID       A8

  (b)  Message#      N*
       TOorCC        A5*
       User Name     A20*
       User ID       A8

  (c)  Message#      N*
       Line#         N*
       Text          A80

  (d)  Pending#      N*
       Action        A8*
       Text          A40*

Next is the Route table, shown in Table 1(b), which contains the routing information for the message, with a complete list of all the TO: and CC: recipients. The program needs to include all the names if you respond to the message. The Route table lists whether the addressee was included as a TO: or CC: and includes both the user's name and ID.

The Message table, Table 1(c), is the message itself and uses a separate record for each line in the message. The Message# field links the previous two tables with this table. The Line# field allows you to keep the fields in the order that messages are received. The table is displayed in a sorted order based on the Message# field and the Line# field.

Finally, the Pending table, shown in Table 1(d), contains responses to be processed and sent to MCI. The first field is the Pending# field, which links together all of the components of the response. The Action field contains information such as TO:, CC:, Subject, and Filename. The first three strings are associated with the routing and subject matter of the message. The Filename string, however, needs a bit more discussion. Since neither PAL nor C provides the means to edit a message easily, the PAL script runs an external editor. The Filename record contains the name of the ASCII file to be transferred as the body of the message.

The Paradox Engine Application

The storage/retrieval application processes incoming text files and puts the information into the Paradox tables for later use by the PAL script. After the PAL script has run, this application takes the pending message information and translates it into a file that can be uploaded to MCI. However, you must first initialize the database engine, open the tables, and allocate the record buffers. Refer to the C program in Listing One, page 104. (Listing Two, page 104, is the accompanying header file.) start-Engine( ) performs the initialization. A call to openTables( ) ensures the existence of a table by using the Engine function PXTblExist( ). If such a table does not exist, then calls to PXTblCreate( ) and PXKeyAdd( ) create the table and indexes. Once these calls are completed, the tables are opened and record buffers allocated.

At this point, note that the PXCheck( ) function defined in this module takes two parameters: a location and an error code. The location is your current place in the program, and the error code is the return value from the function. This method is a quick and dirty way to trap errors. In cases where an engine function returns a value that we do not want to stop the program for, add the location to a switch statement that returns to the caller. The calling code can then check PXLastErr( ) to determine if there was an unacceptable error in the last call. An example of wanting to know the function's return value is PXSrchKey( ). If the key you are searching for does not exist, you do not want to abort the program but continue. The location values are determined as follows: The first digit is the module containing the code; the second is the function containing the code; and the third is the specific call itself.

Parsing Incoming Messages

The program checks the command line for a filename to determine whether there are any incoming files. If there is nothing on the command line, it does not need to process incoming messages. The program will process an outgoing message only if the Pending table exists on the disk. The PAL script creates this table and places all of the outgoing message information in it.

To understand how the program parses incoming messages, study the layout of a typical MCI message in Figure 2 and the parser listed in Listing Three, page 104. The first line of an MCI message contains the Date: string. You can ignore anything that appears before this string in the text file. The program reads the Date: string and parses it using strtok( ) to remove the components of the date. It then converts these components into data the engine can use, using PXDateEncode( ), and adds it to the record for the Header table. The next line of text is the sender information. The program parses both the user's name and MCI ID and adds them to the record.

Figure 2: Layout of a typical MCI Message

  Date: Sun Apr 22, 1990 12:52 pm EST
  From: Joe Q. Public /MCI ID: 111-1111

  TO:   *Chris Ohlsen /MCI ID: 222-2222
  TO:   John Smith /MCI ID:333-3333
  TO:   Jane Doe /MCI ID: 444-4444
  CC:   Frank Friend /MCI ID: 555-5555
  Subject:   Testing MCI program
  Hey everybody!  This is just a sample
  message to see what the layout of
  an MCI message looks like.
  Joe

The next block of information goes to the Routing table. The program uses getTo( ) and getCC( ) to add the TO: and CC: lines to the Route table. Every TO: and CC: line is read and parsed to remove the user's name and MCI ID, and the information is appended to the end of the Route table. Finally, the program reads the Subject line, parsing it and adding it to the record in the Header table.

Once you have all the information for the Header and Route tables, you need to work with the message itself. The program reads the message one line at a time and posts it to the Message table with a unique line number and the current message number. If the line starts with a Date: string, there is another message to process. If the line starts with the Command: string, there are no more messages.

Creating Outgoing Messages

The send( ) function (see Listing Four, page 106) checks for and processes any outgoing messages. The existence of the Pending table determines if there are any messages to process. The PAL script will create this table only if there is a reply to a message or the user composes a new message. If there are no messages, the function returns. If there is a message, send( ) continues by opening the table, getting the handles to the fields, and creating an index on the table.

The engine application now enters the main loop of the function, which creates the message script for the communications program. The first line, CR, creates a new message. Then the program addresses the message for both the TO: and CC: fields. Next it adds the Subject: line. The application uses processMany( ) for each of these fields. It uses a string and the current pending message number as parameters. The program adds these two fields to a temporary record and searches for them in the table. If it finds a match, it reads the record into the Engine's internal buffers, which pull out the desired field and append it to the text file. Then it searches for another match. This continues until there are no matches, at which point the function returns.

The message itself is stored in an external file. processFile( ) gets the name of the file from the Pending table, opens the file, and reads it line by line. As it reads each line into the program, processFile( ) writes out to the message script being built. When the end of the file is reached, the message file is closed and erased. send( ) again gains control and appends a "/" to the end of the message, which tells MCI Mail that the text of the message is ended. send( ) then adds a blank line to skip through MCI's Handling: prompt, and the "Yes" string is in response to MCI's Send: prompt.

Finally, nextPendNum( ) gets the number of the next pending function. If there are no more messages pending, it sets the pendNum variable to 0. When the loop sees there are no more messages to process, it adds the string "exit\n" to the end of the file, which logs the script off of MCI Mail. The Pending table (and all associated files) is removed from the disk.

At this point, main( ) takes over again and closes all the open tables, and a call to PXExit( ) turns the engine off.

The PAL Script

Because PAL cannot compete against your favorite word processor, the MCI PAL script can call an external editor to process the composed messages. The first line of the PAL script (see Listing Five, page 107) defines which editor (ED) is called. Make sure the word processor you select is working with an ASCII file without special control characters.

The script then confirms that the tables the engine application has created actually exist. If one of the tables does not exist, the script displays a five-second message on the screen, stating that you must run the MCI.EXE program before running this script.

Next, the script checks the forms to display the Header and Message table information. If the forms do not exist, it will build them. (The Paradox script recording capability allows you to create these functions: Just lay out the fields in the order you want and write down the layout. Then, select Scripts/Begin Record to record the steps.)

The Message table has MultiRecord views so you can display and scroll through the entire message. This form is linked to the Header form. The combined information comprises the view of the individual fields. Notice that the Line# field is decreased to one character width and blocked out by selecting a color because the field is necessary for internal use only. You can change the color selection to match your form display; this block of code is commented in BuildMsgForms( ). The third field lets you view the headers of the messages currently stored and select the messages you want to see in full.

Next, an opening screen displays the name of the script and instructions for working with the menus, and the main menu is displayed: Inbox lets you view a message, Compose lets you create a new one, and Quit exits the script. You are prompted for verification if you select Quit.

Viewing the Inbox Messages

The Inbox( ) function uses the form that was created to display the Header table and lets you choose the message with which you wish to work. If there are no messages to view, "No messages to be viewed" is displayed on the main screen. If you select Escape, you return to the main menu. If you choose Enter, ViewMessage( ) selects the message the cursor is currently on.

ViewMessage( ) also uses one of the forms created earlier -- the MultiTable form, which was developed with both the Header and Message table. The Message table form has a MultiRecord region that displays the message body. You can use the arrow keys, PgUp, and PgDn to scan through the message. If you want to view the previous or next message in the Header table, press F3 or F4, respectively. These keys correspond with the Up Image and Down Image keys in Paradox. You can also press Escape at this point to return to the main menu.

If you want to respond to the message, press Insert and the script calls Respond( ), which grabs the current header information about the subject, message number, and sender. QBE performs a query on the Route table to get all those who received the message, so they can be included in the reply.

Several other fields are set up before pulling the information returned from the query. The Subject is added to the table with the RE: string appended to it to denote a reply. The filename added to the table contains the body of the message. The filename is a combination of the Pending string and an extension of the Pending number in the table.

When these fields are set up, the script scans the answer table built by the previous QBE. It adds all of the fields within the answer table to the Pending table with the appropriate action, indicating a TO: or CC:. DO_IT! saves all of the additions to the Pending table. Finally, the RUN BIG command calls the editor, thus providing the maximum amount of memory.

Composing a New Message

Composing a new message is similar to responding to a prior message except that the information pulled from the Header and Route tables is provided by the user.

At least one TO: field is required to send a message. Therefore, the script forces you to input at least one field. After receiving the TO: fields, it prompts you for the CC: fields. There can be any number of CC:s on a message. Finally, it prompts you for the Subject field. There can be only one of these, too. When all of the information is input, the modified table is saved, and the editor is called with the RUN BIG command.

Pseudo Communication Script and Batch File

Now that you have the major players in the application, the PAL script and the Paradox Engine application, you need something to tie them together. You can use a simple batch file (see Example 1) to call everything, including the pseudo telecommunications package.

Example 1: Sample scripts: (a) MCI.BAT, (b) GETMCI.SCR, and (c)

SENDMCI.SCR
(a)

Echo Off
comm -sGETMCI.SCR
mci inbox.txt
paradox3 mci
mci
comm -sSENDMCI.SCR
del MCI-SEND.TXT

(b)

DIAL #-###-###-####    ; Dial your MCI Mail phone number.
WAIT "user name:"
SEND "my mci name"     ; Type in your MCI Account name.
WAIT "Password:"
SEND "my mci password" ; Type in your MCI Password.
WAIT "Command:"
CAPTURE "inbox.txt"    ; Start capturing everything to the file INBOX.TXT.
SEND "pr inbox"        ; Tell MCI to display all message in your Inbox.
WAIT "Command:"
CAPTURE OFF
SEND "exit"            ; Log off of MCI Mail.
EXIT

(c)

DIAL #-###-###-####         ; Dial your MCI Mail phone number.
WAIT "user name:"
SEND "my mci name"          ; Type in your MCI Account name.
WAIT "Password:"
SEND "my mci password"      ; Type in your MCI password.
WAIT 'Command:"
ASCII XFER NOLF             ; Turn off line feeds during ASCII upload.
UPLOAD ASCII "mci-send.txt" ; Upload all new messages.

The batch file simply needs to call the communications program with a script that will grab all of the available messages on MCI Mail. After it does this, it calls the MCI.EXE program with the name of the capture file to parse and build the tables. Next, it calls Paradox to execute the MCI script. It calls the MCI.EXE application again to parse any responses the script may generate. Finally, it calls the communications script again to upload the message that was created, and the upload file is deleted.

The script files are pseudo scripts that you can translate into whatever communications package you are using. The GETMCI.SCR script in Example 1(b) logs on to MCI Mail and down-loads all new messages, capturing the entire session to an Inbox.Txt file. The SENDMCI.SCR script, in Example 1(c) logs on to the service and performs an ASCII upload of the file generated by the MCI.EXE program, MCI-SEND.TXT. The ASCII file creates the message, sends it, and logs off the service.

Conclusions

Many enhancements could be built into the basic system presented here. For one thing, the PAL script could be enhanced to support an address book that keeps track of users' MCI addresses, and the PAL script could add the Route table information to the Address table.

The Paradox Engine application could also be enhanced to do communications internally. It could dial up MCI, post the message, and receive new mail. The program could be set to dial MCI early in the morning before work, and the PAL script could be called so your messages are waiting when you get there. Another possibility is a gateway that would allow many people to use a single MCI Mail account via private "mailboxes." To send a message to one of these people, you could place additional addressing information (such as MBXTO: and MBXCC:) in the body of the message. This extends the application to a network.

_A DATABASE SYSTEM FOR AUTOMATING E-MAIL_ by Chris Ohlsen

[LISTING ONE]

<a name="0275_0011">

/* You will need the Paradox Engine to compile this program. If you have   */
/* placed directory for Engine in your INCLUDE path and LIBRARY path, the  */
/* modules should compile fine with: tcc -ml mci parse send pxengtcl.lib   */
/* Otherwise, specify where Engine header file and library files are       */
/* using the -I and -L compiler switches.               */

#define MCIMAIN

#include <stdio.h>
#include <stdlib.h>
#include <pxengine.h>
#include "mci.h"

void closeTables(void);
void getFldNames(void);
void makeTblExist(char *tblName,int nFlds,char **fld,char **types,
                                                                int keyFields);
void openTables(void);
void shutdownEngine(void);
void startEngine(void);

/**** DEFINE HEADER TABLE INFORMATION ****/
char *headerFields[] =
{
  "Date Received","Message#","Subject","From User","From ID"
};
char *headerTypes[] =
{
  "D","N","A40","A20","A8"
};
int headerKeyFields = 2;
#define headerNFields sizeof(headerTypes)/sizeof(char*)

/**** DEFINE MESSAGE TABLE INFORMATION ****/
char *msgFields[] =
{
  "Message#","Line#","Text"
};
char *msgTypes[] =
{
  "N","N","A80"
};
int msgKeyFields = 2;
#define msgNFields sizeof(msgTypes)/sizeof(char*)

/**** DEFINE ROUTE TABLE INFORMATION ****/
char *routeFields[] =
{
  "Message#","TOorCC","User Name","User ID"
};
char *routeTypes[] =
{
  "N","A5","A20","A8"
};
int routeKeyFields = 3;
#define routeNFields sizeof(routeTypes)/sizeof(char*)

int lastPXErr;
FIELDHANDLE fh[] = {1};
TABLEHANDLE headerTbl,msgTbl,routeTbl;

int main(int argc,char *argv[])
{
   startEngine();
   openTables();
   getFldNames();
   if (argc > 1)
        parse(argv[1]);    /* only add to tables if filename passed in */
   send();
   closeTables();
   shutdownEngine();
   return 0;
}

void startEngine(void)
{
   PXCheck(0x101,PXInit());
}

void shutdownEngine(void)
{
   PXCheck(0x110,PXExit());
}

void openTables(void)
{
 makeTblExist("Header",headerNFields,headerFields,headerTypes, headerKeyFields);
 makeTblExist("Message",msgNFields,msgFields,msgTypes,msgKeyFields);
 makeTblExist("Route",routeNFields,routeFields,routeTypes,routeKeyFields);
   PXCheck(0x120,PXTblOpen("Header",&headerTbl,0,0));
   PXCheck(0x121,PXTblOpen("Message",&msgTbl,0,0));
   PXCheck(0x122,PXTblOpen("Route",&routeTbl,0,0));
   PXCheck(0x123,PXRecBufOpen(headerTbl,&headerRec));
   PXCheck(0x125,PXRecBufOpen(msgTbl,&msgRec));
   PXCheck(0x126,PXRecBufOpen(routeTbl,&routeRec));
}

void closeTables(void)
{
   PXCheck(0x130,PXTblClose(headerTbl));
   PXCheck(0x131,PXTblClose(msgTbl));
   PXCheck(0x132,PXTblClose(routeTbl));
}

void getFldNames(void)
{
   PXCheck(0x140,PXFldHandle(headerTbl,"Date Received",&hdrDateFld));
   PXCheck(0x141,PXFldHandle(headerTbl,"Message#",&hdrMsgFld));
   PXCheck(0x142,PXFldHandle(headerTbl,"Subject",&hdrSubjectFld));
   PXCheck(0x143,PXFldHandle(headerTbl,"From User",&hdrUserFld));
   PXCheck(0x144,PXFldHandle(headerTbl,"From ID",&hdrIDFld));
   PXCheck(0x145,PXFldHandle(msgTbl,"Message#",&msgMsgNumFld));
   PXCheck(0x146,PXFldHandle(msgTbl,"Line#",&msgLineFld));
   PXCheck(0x147,PXFldHandle(msgTbl,"Text",&msgTextFld));
   PXCheck(0x148,PXFldHandle(routeTbl,"Message#",&routeMsgFld));
   PXCheck(0x149,PXFldHandle(routeTbl,"ToOrCC",&routeToFld));
   PXCheck(0x14A,PXFldHandle(routeTbl,"User Name",&routeUserFld));
   PXCheck(0x14B,PXFldHandle(routeTbl,"User ID",&routeIDFld));
   return;
}

void makeTblExist(char *tblName,int nFlds,char **fld,char **types,
                                                                int keyFields)
{
   int exist;
   PXCheck(0x150,PXTblExist(tblName,&exist));
   if (!exist)
   {
      PXCheck(0x151,PXTblCreate(tblName,nFlds,fld,types));
      if (keyFields)
         PXCheck(0x152,PXKeyAdd(tblName,keyFields,fh,PRIMARY));
   }
   return;
}

void PXCheck(int loc,int errCode)
{
   lastPXErr=errCode;
   if(errCode==PXSUCCESS)
      return;
   switch (loc)
   {
                case 0x234:
      case 0x237:
      case 0x247:
      case 0x253:
      case 0x254:   return;
      default:  printf("Error '%s' at %04x\n",PXErrMsg(errCode),loc);
               exit (1);
   }
}

int PXLastErr(void)
{
   return lastPXErr;
}



<a name="0275_0012"><a name="0275_0012">
<a name="0275_0013">
[LISTING TWO]
<a name="0275_0013">

#ifdef MCIMAIN
#define extern
#endif

#define MESSAGELENGTH 90
#define STARTOFNEWMESSAGE "Date:"
#define ENDOFSCRIPT "Command:"

int PXLastErr(void);
void PXCheck(int loc,int errCode);
void send(void);
void parse(char *filename);

extern short MessageID;
extern TABLEHANDLE headerTbl,msgTbl,routeTbl;
extern RECORDHANDLE headerRec,msgRec,routeRec;

extern FIELDHANDLE hdrDateFld;
extern FIELDHANDLE hdrMsgFld;
extern FIELDHANDLE hdrSubjectFld;
extern FIELDHANDLE hdrUserFld;
extern FIELDHANDLE hdrIDFld;

extern FIELDHANDLE msgMsgNumFld;
extern FIELDHANDLE msgLineFld;
extern FIELDHANDLE msgTextFld;

extern FIELDHANDLE routeMsgFld;
extern FIELDHANDLE routeToFld;
extern FIELDHANDLE routeUserFld;
extern FIELDHANDLE routeIDFld;



<a name="0275_0014"><a name="0275_0014">
<a name="0275_0015">
[LISTING THREE]
<a name="0275_0015">

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pxengine.h>
#include "mci.h"

void getCC(FILE *inMsgFp,char inBuffer[],int msgID);
void getHeaderInfo(FILE *inMsgFp,char inBuffer[],int msgID);
short getNextMsgID(short *MessageID);
void getTo(FILE *inMsgFp,char inBuffer[],int msgID);
void parseAddr(char inBuffer[],char Person[],char ID[]);
void parseDate(char *dateStr);
void whoFrom(FILE *inMsgFp,char inBuffer[],int msgID);
int convertMonth(char *str);

void parse(char *filename)
{
   FILE *inMsgFp;
   char inBuffer[MESSAGELENGTH+1];
   short LineNumber;

   if ((inMsgFp = fopen(filename,"r")) == NULL)
   {
      perror(filename);
      PXExit();
      return;
   }

   /* Parse through text file searching for first MCI message. */
   while (fgets(inBuffer,MESSAGELENGTH,inMsgFp))
      if(strstr(inBuffer,STARTOFNEWMESSAGE) == inBuffer)
         break;
   do
   {
   /* If STARTOFNEWMESSAGE is at beginning of current line, save last  */
        /* message and get remainder of header information for the message. */
   /* Otherwise, keep getting text.                                    */
      if (strstr(inBuffer,ENDOFSCRIPT) == inBuffer)
         break;                   /* Reached end of messages */
      if (strstr(inBuffer,STARTOFNEWMESSAGE) == inBuffer)
      {
         LineNumber = 1;
         getNextMsgID(&MessageID);
         getHeaderInfo(inMsgFp,inBuffer,MessageID);
      }
        if (strlen(inBuffer) > 0 && inBuffer[strlen(inBuffer)-1] == '\n')
         inBuffer[strlen(inBuffer)-1] = '\0';
        PXCheck(0x300,PXPutAlpha(msgRec,msgTextFld,inBuffer));
        PXCheck(0x301,PXPutShort(msgRec,msgLineFld,LineNumber++));
        PXCheck(0x302,PXPutShort(msgRec,msgMsgNumFld,MessageID));
        PXCheck(0x303,PXRecAppend(msgTbl,msgRec));
   }while (fgets(inBuffer,MESSAGELENGTH,inMsgFp));
   fclose(inMsgFp);
   return;
}

void getHeaderInfo(FILE *inMsgFp,char inBuffer[],int msgID)
{
   char subject[41],dummy[1];
   parseDate(inBuffer);
   whoFrom(inMsgFp,inBuffer,msgID);

   /*Skip the Blank Line*/
   fgets(inBuffer,MESSAGELENGTH,inMsgFp);

   /*Skip the First TO: line (ignore address to self) */
   fgets(inBuffer,MESSAGELENGTH,inMsgFp);

   getTo(inMsgFp,inBuffer,msgID);
   getCC(inMsgFp,inBuffer,msgID);

   parseAddr(inBuffer,subject,dummy);
   PXCheck(0x330,PXPutAlpha(headerRec,hdrSubjectFld,subject));
   PXCheck(0x331,PXRecAppend(headerTbl,headerRec));

   /* Skip blank lines */
   fgets(inBuffer,MESSAGELENGTH,inMsgFp);
   fgets(inBuffer,MESSAGELENGTH,inMsgFp);
}

void getTo(FILE *inMsgFp,char inBuffer[],int msgID)
{
   char person[MESSAGELENGTH+1];
   char id[MESSAGELENGTH+1];
   while (fgets(inBuffer,MESSAGELENGTH,inMsgFp) &&
         strstr(inBuffer,"TO") || strstr(inBuffer,"EMS"))
   {
      parseAddr(inBuffer,person,id);
      PXCheck(0x310,PXPutAlpha(routeRec,routeUserFld,person));
      PXCheck(0x311,PXPutAlpha(routeRec,routeIDFld,id));
      PXCheck(0x312,PXPutShort(routeRec,msgMsgNumFld,msgID));
      PXCheck(0x313,PXPutAlpha(routeRec,routeToFld,"TO"));
      PXCheck(0x314,PXRecAppend(routeTbl,routeRec));
   }
}

void getCC(FILE *inMsgFp,char inBuffer[],int msgID)
{
   char person[MESSAGELENGTH+1];
   char id[MESSAGELENGTH+1];
   while (strstr(inBuffer,"CC"))
   {
      parseAddr(inBuffer,person,id);
      PXCheck(0x320,PXPutAlpha(routeRec,routeUserFld,person));
      PXCheck(0x321,PXPutAlpha(routeRec,routeIDFld,id));
      PXCheck(0x322,PXPutShort(routeRec,msgMsgNumFld,msgID));
      PXCheck(0x323,PXPutAlpha(routeRec,routeToFld,"CC"));
      PXCheck(0x324,PXRecAppend(routeTbl,routeRec));
      fgets(inBuffer,MESSAGELENGTH,inMsgFp);
   }
}

void whoFrom(FILE *inMsgFp,char inBuffer[],int msgID)
{
   char person[MESSAGELENGTH+1];
   char id[MESSAGELENGTH+1];

   fgets(inBuffer,MESSAGELENGTH,inMsgFp);
   parseAddr(inBuffer,person,id);
   printf("From: %s\n",person);
   printf("MCI ID: %s\n",id);
   PXCheck(0x350,PXPutAlpha(headerRec,hdrIDFld,id));
   PXCheck(0x351,PXPutAlpha(headerRec,hdrUserFld,person));
   PXCheck(0x352,PXPutShort(headerRec,hdrMsgFld,msgID));
}

void parseAddr(char *inBuffer,char *person,char *id)
{
   char *p;
   char *s = person;
   if ((p = strchr(inBuffer,':'))!=NULL)
   {
      while (*(++p) == ' ' || *p == '*')
         ;
      while ( (*s++ = *p++) != '/' && *p)
         ;
      *(--s) = '\0';
   }
   s = id;
   if ((p = strchr(p,':'))!=NULL)
   {
      while ( *(++p) == ' ')
         ;
      while ( (*s++ = *p++) != '\n')
         ;
      *(--s) = '\0';
   }
}

short getNextMsgID(short *msgID)
{
   RECORDNUMBER nRecs;
   PXCheck(0x360,PXTblNRecs(msgTbl,&nRecs));
   if (nRecs == 0)
      *msgID = 1;
   else
   {
      PXCheck(0x370,PXRecLast(msgTbl));
      PXCheck(0x371,PXRecGet(msgTbl,msgRec));
      PXCheck(0x372,PXGetShort(msgRec,msgMsgNumFld,msgID));
      ++(*msgID);
   }

   return(*msgID);
}

void parseDate(char *dateStr)
{
   char *mon,*day,*year;
   int iMon,iDay,iYear;
   long engDate;

   strtok(dateStr," ");
   strtok(NULL," ");
   mon=strtok(NULL," ");
   day=strtok(NULL," ");
   year=strtok(NULL," ");
   if((iMon=convertMonth(mon))==0)
      PXCheck(0x380,PXERR_INVDATE);
   iDay=atoi(day);
   iYear=atoi(year);
   PXCheck(381,PXRecBufEmpty(headerRec));
   PXCheck(382,PXDateEncode(iMon,iDay,iYear,&engDate));
   PXCheck(383,PXPutDate(headerRec,hdrDateFld,engDate));
}

int convertMonth(char *str)
{
   switch (str[0])
   {
      case 'A': switch (str[1])
            {
               case 'p': return 4;  /* April */
               case 'u': return 8;  /* August */
            }
      case 'D': return 12;                        /* December */
      case 'F': return 2;                         /* February */
      case 'J': switch (str[1])
            {
               case 'a': return 1; /* January */
               case 'u': switch (str[2])
                     {
               case 'l': return 7; /* July */
               case 'n': return 6; /* June */
                     }
            }
      case 'M': switch (str[2])
            {
               case 'r': return 3; /* March */
               case 'y': return 5; /* May */
            }
      case 'N': return 11;                        /* November */
      case 'S': return 9;                         /* September */
      case 'O': return 10;                        /* October */
   }
   return 0;
}




<a name="0275_0016"><a name="0275_0016">
<a name="0275_0017">
[LISTING FOUR]
<a name="0275_0017">

#include <stdio.h>
#include <stdlib.h>
#include <pxengine.h>
#include "mci.h"

void createIndex(TABLEHANDLE *tbl,FIELDHANDLE fld[3]);
void processMany(FILE *fp,FIELDHANDLE f[3],TABLEHANDLE t,int pn,char *st);
void processFile(FILE *fp,FIELDHANDLE f[3],TABLEHANDLE t,int pn);
void nextPendNum(TABLEHANDLE t,FIELDHANDLE f,short *pn);

#define PENDFIELD 0
#define ACTFIELD  1
#define TEXTFIELD 2

void send(void)
{
   TABLEHANDLE tbl;
   FIELDHANDLE flds[3];
   FILE *output;
   int exists;
   short pendNum;
   PXCheck(0x200,PXTblExist("Pending",&exists));
   if(!exists)
      return;
   createIndex(&tbl,flds);
   PXCheck(0x201,PXTblOpen("Pending",&tbl,0,0));
   pendNum=1;
   output=fopen("mci-send.txt","wt");
   if(output==NULL)
   {
      perror("mci-send.txt");
      exit(1);
   }
   do
   {
      fprintf(output,"\ncr\n");
      processMany(output,flds,tbl,pendNum,"TO");
      fputs("\n",output);
      processMany(output,flds,tbl,pendNum,"CC");
      fputs("\n",output);
      processMany(output,flds,tbl,pendNum,"Subject");
      processFile(output,flds,tbl,pendNum);
      fputs("\n/\n\n\Yes\n",output);
      nextPendNum(tbl,flds[PENDFIELD],&pendNum);
   }while(pendNum>0);

   fputs("exit\n",output);
   fclose(output);
   PXCheck(0x202,PXTblClose(tbl));
   PXCheck(0x203,PXTblDelete("Pending"));
   return;
}
void createIndex(TABLEHANDLE *tbl,FIELDHANDLE fld[3])
{
   PXCheck(0x210,PXTblOpen("Pending",tbl,0,0));
   PXCheck(0x211,PXFldHandle(*tbl,"Pending#",&fld[PENDFIELD]));
   PXCheck(0x212,PXFldHandle(*tbl,"Action",&fld[ACTFIELD]));
   PXCheck(0x213,PXFldHandle(*tbl,"Text",&fld[TEXTFIELD]));
   PXCheck(0x214,PXTblClose(*tbl));
   PXCheck(0x215,PXKeyAdd("Pending",1,&fld[ACTFIELD],SECONDARY));
}
void processMany(FILE *fp,FIELDHANDLE f[3],TABLEHANDLE t,int pn,char *st)
{
   char txtSt[81];
   RECORDHANDLE srchRec,rec;

   PXCheck(0x230,PXRecBufOpen(t,&rec));
   PXCheck(0x231,PXRecBufOpen(t,&srchRec));
   PXCheck(0x232,PXPutShort(srchRec,f[PENDFIELD],pn));
   PXCheck(0x233,PXPutAlpha(srchRec,f[ACTFIELD],st));
   PXCheck(0x234,PXSrchKey(t,srchRec,2,SEARCHFIRST));

   while(PXLastErr()==PXSUCCESS)
   {
      PXCheck(0x235,PXRecGet(t,rec));
      PXCheck(0x236,PXGetAlpha(rec,f[TEXTFIELD],sizeof(txtSt),txtSt));
      fprintf(fp,"%s\n",txtSt);
      PXCheck(0x237,PXSrchKey(t,srchRec,2,SEARCHNEXT));
   }
   PXCheck(0x238,PXRecBufClose(rec));
   PXCheck(0x239,PXRecBufClose(srchRec));
}
void processFile(FILE *fp,FIELDHANDLE f[3],TABLEHANDLE t,int pn)
{
   char txtSt[81],fname[21];
   FILE *inFp;
   RECORDHANDLE srchRec,rec;

   PXCheck(0x240,PXRecBufOpen(t,&rec));
   PXCheck(0x241,PXRecBufOpen(t,&srchRec));
   PXCheck(0x242,PXPutShort(srchRec,f[PENDFIELD],pn));
   PXCheck(0x243,PXPutAlpha(srchRec,f[ACTFIELD],"Filename"));
   PXCheck(0x244,PXSrchKey(t,srchRec,2,SEARCHFIRST));

   while(PXLastErr()==PXSUCCESS)
   {
          PXCheck(0x245,PXRecGet(t,rec));
               PXCheck(0x246,PXGetAlpha(rec,f[TEXTFIELD],sizeof(fname),fname));
      if((inFp=fopen(fname,"rt"))==NULL)
      {
         perror(fname);
         exit(1);
      }
      while(fgets(txtSt,80,inFp)!=NULL)
         fputs(txtSt,fp);
      fclose(inFp);
      unlink(fname);

      PXCheck(0x247,PXSrchKey(t,srchRec,2,SEARCHNEXT));
   }
   PXCheck(0x248,PXRecBufClose(rec));
   PXCheck(0x249,PXRecBufClose(srchRec));
}
void nextPendNum(TABLEHANDLE t,FIELDHANDLE f,short *pn)
{
   RECORDHANDLE r;

   PXCheck(0x250,PXRecBufOpen(t,&r));
   PXCheck(0x251,PXPutShort(r,f,*pn));
   PXCheck(0x252,PXSrchKey(t,r,1,SEARCHFIRST));
   while(PXLastErr()==PXSUCCESS)
      PXCheck(0x253,PXSrchKey(t,r,1,SEARCHNEXT));
   PXCheck(0x254,PXRecNext(t));
   if(PXLastErr()==PXSUCCESS)
   {
      PXCheck(0x255,PXRecGet(t,r));
      PXCheck(0x256,PXGetShort(r,f,pn));
      PXCheck(0x257,PXRecBufClose(r));
   }
   else
      *pn=0;
}




<a name="0275_0018"><a name="0275_0018">
<a name="0275_0019">
[LISTING FIVE]
<a name="0275_0019">

editor = "ED"  ; Change this to your word processor

PROC BuildHdrForm()
  {Forms} {Design} {Header} {2} {Brief Header}     ; Design form for HEADER
  " Date Recvd      From                  Subject" ; Place text...
  Enter
  " ----------- -------------------- ----------------------------------------"
  Enter
  " "
  Menu {Field} {Place} {Regular} {Date Received}   ; Place DATE field
  Enter Enter Right
  Menu {Field} {Place} {Regular} {From User}       ; Place FROM USER field
  Enter Enter Right
  Menu {Field} {Place} {Regular} {Subject}         ; Place SUBJECT field
  Enter Enter CtrlHome
  Menu {Multi} {Records} {Define}                  ; Define as MultiRecord
  Enter Left Enter Down Down Down Down Down Down Down Enter
  Do_It!                                           ; Save this form
ENDPROC ; BuildHdrForm

PROC BuildMsgForms()
  {Forms} {Design} {Message} {1} {Message Body} ; Design form for MESSAGE
  MENU {Field} {Place} {Regular} {Line#}        ; Place LINE# field
  ENTER LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT; Truncate field size
  LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT LEFT ENTER

  MENU {Field} {Place} {Regular} {Text}         ; Place TEXT field
  RIGHT ENTER ENTER

  MENU {Multi} {Records} {Define}               ; Define as MultiRecord fields
  ENTER LEFT ENTER DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN ENTER

  ; The next four lines set up the position and color for hiding the line#
  ; field. It currently sets the color to a light grey on light grey.
  HOME CTRLHOME
  MENU {Style} {Color} {Area}
  ENTER DOWN DOWN DOWN DOWN DOWN DOWN DOWN DOWN ENTER
  RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT RIGHT ENTER

  DO_IT!                                        ; Save this form

  {Forms} {Design} {Header} {1} {Message}       ; Design for for HEADER table
  ENTER "  Date Received "                      ; Set up position and text
  MENU {Field} {Place} {Regular} {Date Received} ; Place DATE RECEIVED field

  ENTER ENTER ENTER  "  Subject ..... "         ; Set up position and text
  MENU {Field} {Place} {Regular} {Subject}      ; Place SUBJECT field

  ENTER ENTER ENTER  "  From ........ "         ; Set up position and text
  MENU {Field} {Place} {Regular} {From User}    ; Place FROM USER field
  ENTER ENTER

  MENU {Multi} {Tables} {Place}       ; Place MultiTable Linked Form from
  {Linked} {Message} {1} {Message#}   ; MESSAGE table.
  UP UP UP UP UP UP UP UP UP ENTER    ; Position the field

  DO_IT!                              ; Save this form
ENDPROC ; BuildMsgForms

PROC MenuScrn()
  CANVAS OFF
  @4,0
  SETMARGIN 25

  PAINTCANVAS ATTRIBUTE 31 2,0,24,79 ; Fill Background w/White on Blue
  PAINTCANVAS ATTRIBUTE 0 5,27,8,54  ; Shadow Box in Black
  STYLE ATTRIBUTE 7
  TEXT
----------------------------
|    MCI Message System    |
|                          |
----------------------------
  ENDTEXT

  SETMARGIN 0
  @22,0

  TEXT
 ----------------------------------------------------------------------------
     Use Cursor Keys <- -> To Move Between Menu Choices, or 1st Character
 ----------------------------------------------------------------------------
  ENDTEXT
  PAINTCANVAS ATTRIBUTE 31 22,0,24,79
  CANVAS ON
ENDPROC ; MenuScrn

PROC Inbox()
  IF ( ISEMPTY("Header") ) THEN
    @ 20,27
    ?? "No messages to be viewed"
    RETURN
  ENDIF
  VIEW "Header"
  PICKFORM 2
  WAIT TABLE
    PROMPT "Arrow to message, press RETURN to view.",
           "ESC aborts to main menu."
  UNTIL "Enter", "Esc"

  SWITCH
    CASE retval = "Enter" :
      ViewMessage()
    CASE retval = "Esc" :
      CLEAR
      CLEARALL
      RETURN
  ENDSWITCH
ENDPROC ; Inbox

PROC ViewMessage()
  PICKFORM 1
  DOWNIMAGE
  WHILE (TRUE)
    WAIT TABLE
      PROMPT "F3/F4 move to Previous/Next message. INS responds to message.",
             "Arrow keys scroll the current message. ESC aborts to main menu."
    UNTIL "Esc", "F3", "F4", "Ins"

    SWITCH
      CASE retval = "Ins":
        Respond();
        PICKFORM 1
        DOWNIMAGE
      CASE retval = "F3":
        UPIMAGE
        PGUP
        DOWNIMAGE
      CASE retval = "F4":
        UPIMAGE
        PGDN
        DOWNIMAGE
      CASE retval = "Esc":
        QUITLOOP
    ENDSWITCH
  ENDWHILE
  CLEAR
  CLEARALL
ENDPROC ; ViewMessage

PROC CheckPending()
  IF ( NOT ISTABLE("Pending") ) THEN
    CREATE "Pending"
      "Pending#" : "N*",
      "Action"   : "A8*",
      "Text"     : "A40*"
    RETURN 1
  ELSE
    IF ( ISEMPTY("Pending") ) THEN
      RETURN 1
    ELSE
      RETURN CMAX("Pending","Pending#") + 1
    ENDIF
  ENDIF
ENDPROC ; CheckPending

PROC Respond()
  UPIMAGE
  mnum    = [Message#]
  subject = [Subject]
  from    = [From ID]
  pnum    = CheckPending()

  QUERY
  Route | Message# | TOorCC | User Name | User ID |
        | ~mnum    | Check  |           | Check   |
        |          |        |           |         |
        |          |        |           |         |

  ENDQUERY
  DO_IT!

  VIEW "Answer"
  IF ( ISEMPTY("Answer") ) THEN
    answerTable=TRUE
  ELSE
    answerTable=FALSE
  ENDIF
  VIEW "Pending"
  EDIT "Pending"
  END
  DOWN
  [Pending#] = pnum
  [Action]   = "Filename"
  [Text]     = "Pending." + STRVAL(pnum)
  DOWN
  [Pending#] = pnum
  [Action]   = "Subject"
  [Text]     = "RE:" + subject
  DOWN
  [Pending#] = pnum
  [Action]   = "TO"
  [Text]     = from
  DOWN
  UPIMAGE

  IF ( NOT answerTable ) THEN
    WHILE ( TRUE )
      to_cc = [TOorCC]
      uid   = [User ID]

      DOWNIMAGE
      [Pending#] = pnum
      [Action]   = to_cc
      [Text]     = uid
      DOWN

      UPIMAGE
      IF ( ATLAST() ) THEN
        QUITLOOP
      ENDIF
      DOWN
    ENDWHILE
  ENDIF

  DO_IT!

  RUN BIG editor + " Pending." + STRVAL(pnum)
  CLEAR
  CLEARIMAGE          ; Close the Pending Table
  CLEARIMAGE          ; Close the Answer Table
  UPIMAGE
  CLEARIMAGE          ; Close Route Query Table
ENDPROC ; Respond()

PROC Compose()
  pnum = CheckPending()

  VIEW "Pending"
  EDIT "Pending"
  END
  DOWN

  [Pending#] = pnum
  [Action]   = "Filename"
  [Text]     = "Pending." + STRVAL(pnum)
  DOWN

  CLEAR
  @ 0,0
  GetMultiple("TO",TRUE);
  IF ( NOT retval ) THEN
    CANCELEDIT
    CLEAR
    CLEARALL
    RETURN
  ENDIF
  GetMultiple("CC",FALSE);
  IF ( NOT retval ) THEN
    CANCELEDIT
    CLEAR
    CLEARALL
    RETURN
  ENDIF

  sub = ""
  WHILE( LEN(sub) = 0 )
    ? "Subject: "
    ACCEPT "A40" to sub
    IF ( NOT retval ) THEN
      CANCELEDIT
      CLEAR
      CLEARALL
      RETURN
    ENDIF
  ENDWHILE
  [Pending#] = pnum
  [Action]   = "Subject"
  [Text]     = sub

  DO_IT!

  RUN BIG editor + " Pending." + STRVAL(pnum)
  CLEAR
  CLEARALL
ENDPROC ; Compose

PROC GetMultiple(where,one)
  IF (one) THEN
    name = ""
    WHILE ( LEN(name) = 0 )         ; Loop until at least one [where] field
      ? where + ": "                ; has been input.
      ACCEPT "A20" TO name
      IF ( NOT retval ) THEN        ; Esc was pressed
        RETURN FALSE
      ENDIF
    ENDWHILE
    [Pending#] = pnum
    [Action]   = where
    [Text]     = name
    DOWN
  ENDIF

  WHILE (TRUE)
    ? where + ": "
    ACCEPT "A20" TO name
    IF ( NOT retval ) THEN          ; Esc was pressed
      RETURN FALSE
    ENDIF
    IF ( LEN(name) = 0 ) THEN
      QUITLOOP
    ENDIF
    [Pending#] = pnum
    [Action]   = where
    [Text]     = name
    DOWN
  ENDWHILE
  RETURN TRUE
ENDPROC ; GetMultiple

PROC Main()
  WHILE (TRUE)
    Menuscrn()

    SHOWMENU
      "Inbox"  : "View Current Messages",
      "Compose": "Compose a new Message",
      "Quit"   : "Quit the MCI Message System"
      TO choice

    SWITCH
      CASE choice = "Inbox" :
        Inbox()
      CASE choice = "Compose" :
        Compose()
      CASE choice = "Quit" :
        SHOWMENU
          "No"  : "Do NOT Quit The Application.",
          "Yes" : "Quit The Application."
          TO theexit
        IF ( theexit = "Yes" ) THEN
          MENU {Exit} {Yes}
        ENDIF
    ENDSWITCH
  ENDWHILE
ENDPROC ; Main

;**** Mainline ****
RESET
CLEAR
CLEARALL

IF ( NOT ISTABLE("Message") ) OR ( NOT ISTABLE("Header") ) OR
   ( NOT ISTABLE("Route") ) THEN
  @ 12,17
  ?? "You must run MCI.EXE before running this script"
  SLEEP 5000
  RETURN
ENDIF

IF ( NOT ISFILE("Header.F1") ) OR ( NOT ISFILE("Message.F1") ) THEN
  BuildMsgForms();
ENDIF

IF ( NOT ISFILE("Header.F2") ) THEN
  BuildHdrForm();
ENDIF

Main()










Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.