Building an E-mail Manager

Mike uses PowerBuilder Desktop and QmodemPro for Windows to build a communications engine which automatically logs onto online services such as CompuServe, MCI Mail, Internet, and DDJ Online, then exchanges e-mail between you and others.


December 01, 1994
URL:http://www.drdobbs.com/web-development/building-an-e-mail-manager/184409366

Figure 2


Copyright © 1994, Dr. Dobb's Journal

Figure 2


Copyright © 1994, Dr. Dobb's Journal

DEC94: Building an E-mail Manager

Building an E-mail Manager

PowerBuilder Desktop meets QmodemPro

Michael Floyd

Michael is DDJ's executive editor. He can be reached at [email protected], on CompuServe at 76703,3047, or through the DDJ offices.


Rapid application development (or RAD) tools emphasize drag-and-drop visual programming and client/server development (even though most of the tools I've seen address only the client side). To a large degree, the "rapid" part of the application development stems from a focus on building user interfaces using software "components." Among the RAD tools are Microsoft's Visual Basic, Powersoft's PowerBuilder (both based on Basic), IBM's Visual Age, Symantec's Enterprise Developer, and Borland's yet-to-come Pascal-based Delphi95.

To explore RAD-based development, I've created a minimal communications engine which allows me to automatically log on and exchange e-mail using a variety of online services such as CompuServe, MCI Mail, Internet, and DDJ Online. In this article, I'll use PowerBuilder Desktop to build the front end. In the future, I'll revisit this project using other RAD front-end tools.

On the communications side, I use QmodemPro for Windows from Mustang Software. While there are a number of asynchronous communications libraries available--CommLib 5.0 from Greenleaf Software or Asynch Professional from Turbo Power Software, among them--I decided to use QmodemPro for Windows because of its built-in scripting language, SLIQ (Script Language Interface for Qmodem). This powerful scripting language does much more than simply automate communications tasks. SLIQ scripts, which are compiled by Qmodem's built-in script compiler, allow you to create windows and dialog boxes and call Windows DLLs, as well as providing a number of useful functions for handling communications. Also, the script compiler includes a debugger facility that lets you set breakpoints, step through code, and set variable watches.

While the engine handles sending and receiving mail from each of the different services, a front-end Windows application handles the storage and retrieval of messages in the mail database, displaying of messages, and so on. The front-end application simply invokes QmodemPro by calling the appropriate script.

The Qmodem Connection

The first time I wrote publicly about Qmodem was in a 1984 issue of the CCF Bulletin, a newsletter for users of the Central Computing Facility at NASA/Ames Research Center in Mountain View, CA. Written by John Friel of the Forbin Project, Qmodem was a pioneer in many areas. Qmodem was, at the time, arguably the best PC communications program around, and it was freeware ("shareware" had not yet taken hold). Qmodem was also an early example of what could be done with Turbo Pascal. In fact, Friel wrote freely about his use of the Turbo in the doc files.

Since then, Qmodem has been acquired by Mustang Software, well known for its Mustang BBS, and turned into a commercial toolkit. John Friel continues to work on the DOS version. The Qmodem family now includes a Windows version, although it is under separate development. Because of these separate development paths, there are significant differences between the DOS and Windows versions, and little compatibility between the scripting language, the phone book, and so on.

QmodemPro for Windows provides full support for asynchronous connection, a long list of terminal-emulation options, file-transfer protocols (including several variations of XModem and YModem, Kermit, CIS B+, and ASCII), fax and host-mode capabilities, and SLIQ. SLIQ is a Basic-like scripting language which includes some rather powerful features for Windows developers. For starters, the scripting language provides the ability to create Windows dialog boxes. To do so, you create a dialog-box template like that shown in Figure 1. The dialog-box definition includes definitions for controls such as check boxes, radio buttons, combo boxes, group boxes, list boxes, justified text, edit controls, pushbuttons, and so on. Once the dialog is defined, you simply create an instance of the dialog box based on the template. Note in Figure 1 that the width and height of the dialog are specified in "dialog units." Because they are based on the size of the font used in the dialog, dialog units are used to avoid scaling problems when changing fonts.

Listing One presents a script to parse mail headers in CompuServe Mail and determine whether a message is new. If the message is new, the script downloads the message to a file. A feature I don't like in mail-reader programs such as ConnectSoft's E-Mail Connection is that they automatically delete online mail after it has been downloaded. Although you have the option of doing so, the scripts I present here do not automatically delete mail. Instead, a mail header file (MAIL.HDR) is opened in capture mode and the CompuServe SCAN command is issued. SCAN displays all mailbox information without disturbing a message's "read" status. The presence of the string "Expire:" within the header indicates that the message has already been read; headers not containing the string are new. So, the mail script parses this file to determine which messages are new and downloads them using the Download function. The filename is constructed based on the service from which it was downloaded and the message number assigned to it by the service. For example, Message number 23 on CompuServe is given the filename CIS.23. This is a temporary file which will be deleted when the message is stored in the database by the front-end application.

As you can see in Listing One, SLIQ provides a number of built-in functions like Dial, Send, Waitfor, Delay, and HangUp that make communication with the online service painless. SLIQ also includes a LogFile function used to create a record of the session. Although I haven't used the log file on the PowerBuilder side, it's useful in determining the success or failure of a message transfer.

Because the transfer uses CompuServe's B+ file-transfer protocol, it doesn't matter to the script whether the file is a text message or a binary file such as a .ZIP file. If the file transfer is successful, an appropriate message is displayed and the script logs off the system. For the sake of simplicity, however, I didn't implement error handling with this version. If you plan to use these scripts, error handling will be the first enhancement you'll want to make. Complete scripts for sending and receiving e-mail via DDJ Online, MCI Mail, and CompuServe are provided electronically; see "Availability," page 3.

Database and Interface

PowerBuilder is an object-oriented development environment that supports features such as inheritance, encapsulation, and user-defined objects. It includes an enriched set of database portability and management functions; the ability to support large-scale projects, including report generation and object libraries with check-in/check-out procedures; and a complete implementation of Windows objects, events, functions, and communications--including OLE, MDI, DDE, and DLL calls. PowerBuilder also comes with the Watcom SQL database and a complete set of ODBC drivers covering virtually all PC databases.

When developing an application, you work with various painters, much like you work with wizards in Visual C++. You start by creating an application object using the application painter. Database tables and connections are painted using the database painter, and views into the database are created using a data window painter. The Window painter, not to be confused with the data window painter, is the portion of PowerBuilder that is most like Visual Basic. There, you create windows and controls, set their properties, and create handlers for events. PowerBuilder includes a scripting language called "PowerScript" for this purpose. Scripts are primarily used to handle events associated with an object or control (such as a click event for a button). PowerScript is Basic-like in syntax and includes a sizable function library. Additionally, PowerScript allows you to embed SQL statements within a script, as well as create user-defined functions. Scripts are written using the script painter, which includes a debug facility that lets you set breakpoints; set watches for local, global, and shared variables; and step through code. When you finish building the script, it is compiled and placed in temporary storage. When you save the object, the script is saved in an application library (.PBL file) with the object.

The e-mail app consists of a main window with two data windows and several control buttons; see Figure 2. Buttons are used to initiate communications and manage the mail database. The two data windows are different views into the same table. The top window provides a summary of messages in the database, including message status and header information. The user can scroll through the database and click on any row in the table. When clicked, the lower data window receives an update message and displays the mail for that row. If the mail is plain ASCII, the message itself is displayed. If, however, the mail is binary in nature (such as a .ZIP file), then a message (containing the fully qualified path and filename) is displayed.

The bulk of the work takes place in the script for the click event of the Send/Receive button; see Listing Two. The first task is to invoke QmodemPro, passing the name of the script in Listing One as a parameter. The Qmodem script downloads the mail to temporary files and creates a temporary mail-header file. Listing Two reads the mail-header file, parses the mail messages, and stores them in the database. At the same time, the two data windows are updated with the new mail messages.

One minor problem I encountered involves the formatting of date and time strings. Date strings in particular are presented in different formats depending on the service from which the message was received. And even though PowerBuilder can display date strings in any format, it expects to receive them in the form YYYY-MM-DD. Listing Two determines from which system the message originated and formats the date string accordingly. Once the string is parsed, the new date string is constructed using PowerBuilder's replace() function and then stored in the DateStr variable. Later, DateStr will be concatenated to a larger string containing the complete formatted record for the mail message.

A simple method to import records into the database is to create a delimited string and call PowerBuilder's ImportString() function. However, I found a few quirks using this function, particularly with large bodies of text such as those associated with e-mail. The problem arises from the fact that ImportString() uses carriage returns, line feeds, and EOF markers to separate fields upon importing. However, you cannot specify which of these are used. To get around this, Listing Two Opens the same file twice; once in line mode and again in stream mode. In line mode, FileRead() reads until either a carriage return, line feed, or EOF is encountered. In stream mode, FileRead() will read the entire file until an EOF is encountered or the maximum 32,765 bytes are read.

Once the import string is formatted, InsertRow() is called to create a new row in the table. Next, dw_2.ImportString(ImportStr) imports the string stored in ImportStr to the database. The dw_2 object qualifier references data window 2. Finally, dw_2.update() adds the new record to the table and dw_1.retrieve() makes the change visible in the top data window.

Wrapping It Up

PowerBuilder includes many other features which I have not explored, including the ability to communicate via DDE and OLE. And because PowerScript supports calls to DLLs, you can extend the environment. In fact, the communications engine could be rewritten in, say C, and called directly from PowerScript. That way, you could add support for network mail systems such as cc:Mail.

Coincidentally, PowerBuilder supports MAPI. PowerBuilder supplies a system object called MailSession, and some mail-related structures, enumerated data types, and object-level functions including MailLogon, MailGetMessages, MailAddress, and MailSend. One enhancement you might consider is creating a mail server for the e-mail application. The server could be used to handle requests from other applications for e-mail services.

PowerBuilder 4.0 is due out by the end of this year, according to Powersoft. While company officials are reluctant to give out details of this new release, they have indicated the new version will support both Windows and Windows NT, and that Macintosh support will be available in early 1995. A Powersoft spokesperson also indicated plans for several UNIX implementations with Sun Solaris at the top of the list.

Finally, using Qmodem over a roll-your-own approach saves the headaches of implementing terminal emulations and the sometimes-obscure file-transfer protocols. And on the PowerBuilder side, you no longer have to agonize over user-interface and database-connectivity issues. You may not have thought of putting these two tools together in a programming project; nevertheless, it is surprising what you can accomplish in a short time when you use RADical tools.

For More Information

QmodemPro For Windows 1.1

Mustang Software

6200 Lake Ming Road

Bakersfield, CA 93306

805-395-2500

Price: $99.00

Requirements: Windows 3.1

PowerBuilder Desktop 3.0

Powersoft Corp.

561 Virginia Rd.

Concord, MA 01742-2732

508-287-1500

Price: $695.00

Figure 1: Creating a dialog-box template using SLIQ.

DIALOG dialogtype x, y, w, h
  [CAPTION caption]
  [FONT size, fontname]
  [integer-field AS CHECKBOX title, id, x, y, w, h]
  [integer-field AS COMBOBOX id, x, y, w, h]
  [CTEXT title, id, x, y, w, h]
  [DEFPUSHBUTTON title, id, x, y, w, h]
  [string-field as EDITTEXT id, x, y, w, h]
  [GROUPBOX title, id, x, y, w, h]
  [integer-field AS LISTBOX id, x, y, w, h]
  [LTEXT title, id, x, y, w, h]
  [PUSHBUTTON title, id, x, y, w, h]
  [integer-field AS RADIOBUTTON title, id, x, y, w, h]
  [RTEXT title, id, x, y, w, h]
  ...
END DIALOG

Figure 2 Main window of the e-mail application.

Listing One


'
' Script to parse mail headers in CIS Mail and determine whether a message
' is new. If new, then download
'

DIM Str1 as string
DIM Str2 as string
DIM Str3 as string
DIM FileName as string
DIM I, N, J as Integer
DIM MsgNum as string
DIM DownLoadStr as string

LogFile ON

If Exists("cis.hdr") then
   del "cis.hdr"
End If

dial manual "9,434-1580"

striphibit on
DELAY 1
SEND "^C";
WAITFOR "User ID: "
SEND "76703,4057"
WAITFOR "Password: "
SEND "elder*wholly"

WAITFOR "!"
SEND "go mail"

WAITFOR "!"
CAPTURE "cis.hdr"
SEND "scan"
WAITFOR "!"
CAPTURE OFF
SEND
WAITFOR "!"

OPEN "cis.hdr" for input as #1

For I = 1 to 4
   INPUT #1, Str1
Next I

Do While not eof(1)
   INPUT #1, Str1
   INPUT #1, Str2
   If Str2 = "" then
      Exit Do
   End If
   INPUT #1, Str3

   MsgNum = " "
   I = InStr(Str3, "Expire:")
   If I = 0 then
      While MsgNum = " "
        MsgNum = LEFT(Str1, 1)
        N = Len(Str1) - 1
        Str1 = RIGHT(Str1, N)
      WEnd

      FileName = "cis." + MsgNum
      DownLoadStr =  "download/pro:b " + MsgNum

      Send DownLoadStr
      Waitfor ":"
      Send FileName
      If download("c:\apps\qmwin\download", bplus) = 0 Then
         'PRINT "file transfer OK"
      End If
      Waitfor "!"
      SEND
   End If
   IF Str3 <> "" then
      INPUT #1, Str1
   End If
Loop
   CLOSE #1

SEND "BYE"
Waitfor "!"
Send "N"
DELAY 5
HANGUP
LogFile OFF


Listing Two


string Fname, TmpStr, DateStr, FromStr, SubjectStr, ImportStr
string StatusStr, SystemStr, MsgStr, FExtension
int Fnum, HdrNum, retrn, HdrFileRet, I
long StrPos, RowNum
Boolean OK

run("c:\apps\qmwin\qmwin c:\apps\qmwin\scripts\email.scr")

// Construct filename from mail.hdr
Fname = "c:\apps\qmwin\cis.hdr"
OK = FileExists(Fname)
If OK then 
   HdrNum = FileOpen(Fname)
   For I = 1 to 5
      HdrFileRet = FileRead(HdrNum, TmpStr)
   Next
  Do While HdrFileRet > 0
   FExtension = mid(TmpStr, 3, 1)
   FName = "c:\apps\qmwin\download\cis."
   FName = replace(FName,Len(FName)+1,Len(FExtension), FExtension)

   // Determine if file is binary
   For I = 1 to 3
      HdrFileRet = FileRead(HdrNum, TmpStr)
   Next
   StrPos = pos("* Binary *", TmpStr)

   // If file is text then open mail and process
   If StrPos = 0 Then
      OK = FileExists(Fname)
      If OK then 
         Fnum = FileOpen(Fname)

         //   Get and format the date time string
         retrn = FileRead(Fnum, TmpStr)
         If retrn > 0 then 
            StrPos = pos(TmpStr, ":  ")
            StrPos = StrPos + 2
            DateStr = mid(TmpStr, StrPos)
            DateStr = replace(DateStr,Len(DateStr)+1,Len("~t"), "~t")
         End If
   
        //   Get and format the "From" string
         retrn = FileRead(Fnum, TmpStr)
         If retrn > 0 then 
            StrPos = pos(TmpStr, ":  ")
            StrPos = StrPos + 2
            FromStr = mid(TmpStr, StrPos)
            FromStr = replace(FromStr,Len(FromStr)+1,Len("~t"), "~t")
         End If

         //   Get and format the Subject string
         retrn = FileRead(Fnum, TmpStr)
         If retrn > 0 then 
            StrPos = pos(TmpStr, ":")
            StrPos = StrPos + 2
            SubjectStr = mid(TmpStr, StrPos)
            SubjectStr = replace(SubjectStr,Len(SubjectStr)+1,Len("~t"), "~t")
         End If

         // Get the mail message
         Do While retrn >= 0
            retrn = FileRead(Fnum, TmpStr)
            MsgStr = replace(MsgStr,Len(MsgStr)+1,Len(TmpStr), TmpStr)
         Loop
         FileClose(Fnum)
      End If

   Else // Process binary file     
      MsgStr = "Binary file copied to download directory~t"
      FromStr = TmpStr
      SubjectStr = TmpStr
      DateStr = "1994-01-01"
      HdrFileRet = FileRead(Fnum, TmpStr)
      HdrFileRet = FileRead(HdrNum, TmpStr)
   End If
   
   StatusStr = "received~t"
   SystemStr = "Compuserve~t"
   RowNum = dw_2.InsertRow(0)
   ImportStr = String(Rownum)
   ImportStr = replace(ImportStr,Len(ImportStr)+1,Len("~t"), "~t")
   ImportStr = replace(ImportStr,Len(ImportStr)+1,Len(DateStr), DateStr)
   ImportStr = replace(ImportStr,Len(ImportStr)+1,Len(FromStr), FromStr)
   ImportStr = replace(ImportStr,Len(ImportStr)+1,Len(SubjectStr), SubjectStr)
   ImportStr = replace(ImportStr,Len(ImportStr)+1,Len(StatusStr), StatusStr)
   ImportStr = replace(ImportStr,Len(ImportStr)+1,Len(SystemStr), SystemStr)
   ImportStr = replace(ImportStr,Len(ImportStr)+1,Len(MsgStr), MsgStr)

   dw_2.ImportString(ImportStr)
   dw_2.update()
   dw_1.retrieve()
 Loop
FileClose(HdrNum)
End If

Copyright © 1994, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.