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()