The Network Kit: Mail Messages (BMailMessage)

Derived from: public BObject

Declared in: <net/E-mail.h>


Overview

When the mail daemon retrieves new mail from the mail server, it stores the retrieved messages in the boot volume's database, creating a single record (a "mail record") for each message. A mail-reading program can then pull the mail record out of the database (as a BRecord object) and display its contents.

Similarly, when the user composes new mail (on the BeBox) and submits the message for sending, the message-composing application adds the message (again, encapsulated in a mail record) to the database where it waits for the daemon to pick it up and send it to the mail server. The scheme looks something like this:

If you're writing a mail-reading or mail-writing application, then all you really need to know is the definition of the table to which the mail message records conform. With this knowledge, you can retrieve ("fetch") and parse in-coming messages, and create and submit (to the database) out-going messages. The mail message table is called "E-mail", and is described in The "E-mail" Table .

The Network Kit also supplies a BMailMessage class that acts as a convenient wrapper around mail records.

The following sections give you a "mail message" tutorial; we'll step through the database and mail message operations that you need to create a generic mail application. If you're already comfortable with database programming (and understand SMTP and POP), you can skip the tutorial and head straight for the "E-mail" and BMailMessage specifications.


Creating a Mail Reader

The design of a Be mail reader should follows this outline:

1. Ask the mail daemon to retrieve mail from the mail server.
2. Get the newly retrieved mail messages from the database.
3. Display the contents of the mail messages.

Throughout the following step-by-step explanations, we'll give both the general approach and also look at what BeMail does.

(Note that this tutorial is incomplete.)


Asking the Daemon to Get New Mail

There are a couple of ways to ask the mail daemon to retrieve newly arrived mail from the mail server:

  • You can ask it explicitly by calling check_for_mail()

  • You can wait for the mail schedule's automatic invocation of check_for_mail() .
  • You probably want to do both of these: You should provide a means for the user to ask that mail be retrieved right now, while also allowing the schedule to do its thing. check_for_mail() and set_mail_schedule() , which declares the periodicity of automatic mail retrieval, are described in The Mail Daemon . As explained there, the mail schedule "belongs" to the user; its default presentation is through the E-Mail preferences application.

    BeMail doesn't actually do anything about retrieving mail. It relies on the mail schedule, and on the mail daemon's "E-Mail Status" alert panel, which provides a "Check Now" button (as in "check for mail now").


    Getting Messages from the Database

    When new mail arrives, the mail daemon creates a database record to hold each new message, and then commits the records to the database. The table that a mail record conforms to is named "E-Mail". This table is kept in the database that corresponds to the boot volume. As a demonstration of these principles, the following example function counts the number of mail messages that currently reside in the database:

        #include <Database.h>
        #include <Table.h>
        #include <Volume.h>
        #include <Query.h>
       
        long count_all_email()
        {
            BVolume bootVol = boot_volume();
            BDatabase *bootDb = boolVol.Database();
            BTable *emailTable = bootDb->FindTable("E-Mail");
            BQuery *emailQuery = new BQuery();
            long result=0;
       
            if (emailTable != NULL) {
                emailQuery->AddTable(emailTable);
                emailQuery->PushOp(B_ALL);
                emailQuery->Fetch();
                result = emailQuery->CountRecordIDs();
            }
            delete emailQuery;
            return result;
        }

    Obviously, this example requires some knowledge of how the database works. You can mosey on over to the Storage Kit documentation for the Tolstoy version, or you can read between the lines of the following:

    As mentioned above, the mail daemon transforms mail messages into "E-Mail" conforming records, and then "commits" (in database lingo) these records to the boot volume's database. The first few lines of the example assemble the suspects: The boot volume, the database from the boot volume, and the "E-Mail" table from the boot database. If the table is found, then we construct a "query"--this is the vehicle that will let us retrieve our records. The query is told which table to look in and which records in that table to look for. This is done through AddTable(emailTable) and the PushOp(B_ALL) calls; in other words, we tell the query to look for all records in the "E-Mail" table. Then we tell the query to "fetch," or go out and actually get the records. Technically, it doesn't actually get records (this would be inefficient); instead, it gets record ID numbers. We count the record ID numbers that it has retrieved (the CountRecordIDs() call) and return the count.

    The Example Refined--E-Mail Status

    For the purposes of a mail reader--in other words, an application that wants to display messages that are received from the mail server--retrieving all mail messages isn't quite right. The "E-Mail" table is used to store both in-coming and out-going messages. So we have to fix our query to only count in-coming messages.

    The in-coming/out-going nature of a particular message is stored as a string in the "Status" field of the "E-Mail" table. The mail daemon understands three states: "New", "Pending", and "Sent" (a fourth state, "Read", is used by the BeMail application; we'll get to it later). We're only interested in "New" messages, so we change our query accordingly:

        long count_incoming_email()
        {
            /* declarations as above */
       
            if (emailTable != NULL) {
                emailQuery->AddTable(emailTable);
       
                emailQuery->PushField("Status");
                emailQuery->PushString("New");
                emailQuery->PushOp(B_EQ);
       
                emailQuery->Fetch();
                result = emailQuery->CountRecordIDs();
            }
            delete emailQuery;
            return result;
        }

    Here we've replaced the PushOp(B_ALL) call with a more refined predicate. Again, you can turn to the Storage Kit (the BQuery class, specifically) for the full story on query predicates. Briefly, predicates are expressed in RPN ("Reverse Polish Notation"). According to RPN, the operands of an operation are "pushed" first, followed by the operator. The evaluation of an operation becomes a valid operand for another operation. The series of "pushes" in the example expresses the boolean evaluation

        (status == "New")

    In other words, we're going to fetch all records (again, record IDs) that have a "Status" field value of "New".

    Let the Browser do the Work

    There's one other way to identify mail records: Let the Browser do it. When you "launch" the Browser-defined Mailbox, a query that looks a lot like the one we created above is formed and fetched. The result of the query, the list of found record ID numbers, is turned into a list of BRecord objects that are symbolically listed in the Mailbox window. If the user double-clicks on one of the record icons, the mail daemon passes the record's record_ref to the user-defined "mail reader." By default, the mail reader is BeMail. The user can select a different reader (yours) by dropping the reader's icon in the appropriate "icon well" in the E-Mail preferences panel. You can set the reader identity programmatically through the set_mail_reader() function, although, as with all E-Mail preferences, it's nicer to let the user make the decision.

    A mail reader application needs to be able "catch" the refs that are passed to it. It does this in its implementation of MessageReceived(). A simple implementation would look for the message type B_REFS_RECEIVED . The rest of this thought is left as an exercise for the mind reader.

    Creating BMailMessage Objects

    So far, we've determined where we have to go to find in-coming messages, and counted the messages that we found there, but we haven't actually retrieved the messages themselves. Here, we complete our message-retrieving example by fetching record IDs (as before) and then constructing a BRecord for each ID. Having done that, we pass the BRecord to the BMailMessage constructor. In the example, we'll add each BMailMessage to a BList (which is passed in to the function):

        #include <E-mail.h>
        /* and the others */
       
        long get_new_email(BList *list)
        {
            BRecord *emailRecord;
            BMailMessage *newMail;
            long count;
            record_id rec_id;
       
            /* and the others */
       
            if (emailTable != NULL) {
                emailQuery->AddTable(emailTable);
        
                emailQuery->PushField("Status");
                emailQuery->PushString("New");
                emailQuery->PushOp(B_EQ);
       
                emailQuery->Fetch();
                result = emailQuery->CountRecordIDs();
       
                for (count=0; count < result; count++) {
                    rec_id = emailQuery->RecordIdAt(count);
                    emailRecord = new BRecord(bootDb, rec_id);
                    newMail = new BMailMessage(emailRecord);
                    list->AddItem(newMail);
                    delete emailRecord;
                }
            delete EmailQuery;
            return result;
        }

    In the for loop, we step through the query's "record ID" list, creating a BRecord for each ID. To construct a BRecord from a record ID, you need to pass the appropriate BDatabase object; this is because record ID numbers are only valid within a specific database. Having gotten a BRecord, we pass the object to the BMailMessage constructor; the BMailMessage object copies all the data from the record into itself, such that the BRecord is no longer needed. The BRecord object can (and, unless you have something up your sleeve, should) be deleted after the BMailMessage object is constructed.

    When our example function returns, the argument BList will contain all the BMailMessage objects that we constructed, and the function will return the number of messages directly (as before). Note that we should be a bit pickier about checking for errors; you will, no doubt, correct this oversight in your own mail reader. Also --and here we're just being fussy--keep in mind that by adding the BMailMessages to a BList, we have implied that the BList is now responsible for these objects. More precisely, the entity that called get_new_mail() must delete the contents of the list when it's done doing whatever it does.


    Displaying the Contents of a Message

    The BMailMessage class provides a convenient object cover for mail records. By using BMailMessage objects, you avoid most of the fuss of parsing database records.

    When you construct a BMailMessage to represent an in-coming (or otherwise existing) mail message, the contents of the message are copied into the object's "fields." BMailMessage fields are similar to table fields in that they represent named categories of data. The fields that are defined by the BMailMessage class approximate those of the "E- Mail" table; however, you can add new fields that have no complement in the table --adding a field to a BMailMessage object won't extend the "E-Mail" table definition.

    The FindField() member function retrieves the data that's stored for a particular field within a BMailMessage object. The full protocol goes something like this:

    long FindField(const char *field_name, void ** data, long *length, long index=0)

    The function works as you would expect: You pass in a field name, and the function points *data at the contents of that field. The length of the data (in bytes) is returned in length. The final argument (index) is used to disambiguate between fields that have the same name (the exact value of index has no meaning other than ordinal position).

    To use FindField() properly, you have to know the names of the fields that you can expect to find there. The BMailMessage class defines a number of field names and provides constants to cover them:

    Field Name Constant
    "To: " B_MAIL_TO
    "Cc: " B_MAIL_CC
    "Bcc: " B_MAIL_BCC
    "From: " B_MAIL_FROM
    "Date: " B_MAIL_DATE
    "Reply: " B_MAIL_REPLY
    "Subject: " B_MAIL_SUBJECT
    "Priority: " B_MAIL_PRIORITY
    "Content" B_MAIL_CONTENT

    Each of the defined fields stores some number of bytes of "raw" (untyped) data. When you call FindField(), the function points the second argument ( data) to the raw data for the named field, and returns the number of bytes of data in the third argument ( length).

    A particular field (i.e. a field with a particular name) can store more than one entry. The final argument to FindField() (the argument named index) can be used to distinguish between multiple entries in the same field.

    (Here the master died. We'll complete this tutorial and post it on the Be Web site very soon.)


    The E-Mail Table

    The "E-Mail" database table defines records that hold mail messages. The fields in the table mimic the information that's found in an SMTP or POP mail message header. (See The Mail Daemon for more information on SMTP and POP). The table's fields are:


    Constructor and Destructor


    BMailMessage()

          BMailMessage(void)
          BMailMessage(BRecord *record)
    
          BMailMessage(BMailMessage *mail_message)

    Creates and returns a new BMailMessage object.

    The first version creates an empty, "abstract" message: The object doesn't correspond to the second creates an object that acts as a cover for the given BRecord, and the third creates a copy (more or less) of its argument.


    ~BMailMessage()

          virtual ~BMailMessage(void)

    Destroys the BMailMessage, even if the object's fields are "dirty." For example, let's say you create a new BMailMessage with the intention of sending a message. You start to edit the object--perhaps you fill in the "To: " field--but then you delete the object. The message that you were composing isn't sent. In other words, the BMailMessage object doesn't try to second-guess your intentions: When you destroy the object, it lies down and dies without whining about it.


    Member Functions


    CountFields(), GetFieldName() , FindField()

          long CountFields(char *name = NULL)
          long GetFieldName(char **field_name, long index)
          long FindField(char *field_name, 
             void **data, 
             long *length,
             long index = 0)

    These functions are used to step through and inspect the fields in a BMailMessage object. A field is identified, primarily, by its name. However, a field can have more than one entry, so a secondary identifier (an index) is also necessary. Through the combination of a field name and an index, you can identify and retrieve a specific piece of data. The names of the "standard" mail fields are listed in the SetField() description.

    CountFields() returns the entry count for the named field. If the name argument is NULL, the function returns the number of uniquely named fields in the object. Note that the NULL argument version doesn't necessarily return a count of all fields. For example, if a BMailMessage contains two B_MAIL_TO fields (only), the call

        CountFields(B_MAIL_TO);

    will return 2, while the call

        CountFields();

    will return 1.

    GetFieldName() returns, by reference in the field_name argument, the name of the field that occupies the index'th place in the object's list of uniquely named fields. If index is out-of-bounds, the function returns (directly) B_BAD_INDEX ; otherwise, it returns B_NO_ERROR.

    FindField() return the data that lies in the field that's identified by field_name. If the object contains more than one entry, you can use the index argument to differentiate them. The data that's found is returned by reference through *data; the *length value returns the amount of data (in bytes) that *data is pointing to. It's not a great idea to alter the pointed- to data, but as long as you don't exceed the existing length you'll probably get away with it.

    If field_name doesn't identify an existing field (in this object), B_MAIL_UNKNOWN_FIELD is returned; if the index is out-of-bounds, B_BAD_INDEX is returned. Otherwise, B_NO_ERROR is your reward.

    See also: SetField()


    Ref()

          record_ref Ref(void)

    Returns the record_ref structure that identifies the record that lies behind this BMailMessage object. Not every object corresponds to a record. In general, an in-coming message (a BMailMessages that was constructed from a BRecord object) will have a ref, but an out-going message won't have a ref until the message is actually sent. As always when dealing with refs, you mustn't assume that the ref that's returned here is actually valid--the record may have been removed since the BMailMessage object was constructed (or since the message was sent).


    Send()

          long Send(bool queue = TRUE, bool save = TRUE)

    Creates a record for this BMailMessage object, fills in the object's fields as appropriate for an out-going message in SMTP format, and then adds the record to the "E-Mail" table. If queue is TRUE , the record lies in the database until the mail daemon comes along of its own accord; if queue is FALSE, the mail daemon is told to send the message (and all other queued messages) right now. The BMailMessage's internal status (as returned by Status()) is set B_MAIL_QUEUED if queue is TRUE .

    If save is TRUE, the record that holds the message remains in the database after the mail daemon has done its job. Otherwise, the record is destroyed after the message is sent.

    The mail record's status is set to "Pending" by this function; when the mail daemon picks up the message, it (the daemon) will destroy the record (if it's not being saved), or change the status to "Sent".

    If the BMailMessage doesn't appear to have any recipients, the Send() function returns B_MAIL_NO_RECIPIENT and the message isn't sent. If queue is FALSE, the function sends the message and returns the value returned by its (automatic) invocation of check_for_mail() . If the message is queued, the function returns B_NO_ERROR .


    SetField(), RemoveField()

          void SetField(char *field_name, 
             void *data, 
             long length,
             bool append = FALSE)
          long RemoveField(char *field_name, long index = 0)

    These functions add and remove fields (or field entries) from the object.

    SetField() adds a field named field_name. The data and length arguments point to and describe the length of the data that you want the field to contain (the length is given in bytes). The final argument, append, states whether you want the data to be added (as a separate entry) to the data that already exists under the same name. If append is FALSE , the new data (the data that you're passing in this function call) becomes the field's only entry; if it's TRUE , and the field already exists, the "old" data isn't clobbered, and the field's "entry count" is increased by one.

    RemoveField() removes the data that corresponds to the given field name. If the field contains more than one entry, you can selectively remove a specific entry through the use of the index argument. If field_name doesn't identify an existing field (in this object), B_MAIL_UNKNOWN_FIELD is returned; if the index is out-of-bounds, B_BAD_INDEX is returned. Otherwise, B_NO_ERROR is returned.

    The field names that are defined by the class are:

    Field Name Constant
    "To: " B_MAIL_TO
    "Cc: " B_MAIL_CC
    "Bcc: " B_MAIL_BCC
    "From: " B_MAIL_FROM
    "Date: " B_MAIL_DATE
    "Reply: " B_MAIL_REPLY
    "Subject: " B_MAIL_SUBJECT
    "Priority: " B_MAIL_PRIORITY
    "Content" B_MAIL_CONTENT

    See also: FindField()


    SetEnclosure(), GetEnclosure() , RemoveEnclosure(), CountEnclosures()

          void SetEnclosure(record_ref *ref, 
             const char *mime_type,
             const char *mime_subtype)
          long GetEnclosure(record_ref **ref,
             char **mime_type,
             char **mime_subtype,
             long index = 0);
          long RemoveEnclosure(record_ref *ref)
          long CountEnclosures(void)

    These functions deal with a BMailMessage's "enclosures." An enclosure is a separate file that's included in the mail message. Enclosures are identified by index only--unlike a BMailMessage's fields, enclosures don't have names. Every enclosure is tagged with a MIME typifier. The MIME typifier is a human-readable string in the form "type/subtype" that attempts to describe the data that the enclosure contains. As shown in the protocol above, the BMailMessage class breaks the two MIME components apart so they can be set (or retrieved) separately.

    SetEnclosure() adds an enclosure to the object. The ref argument locates the enclosure's data; currently, the refs that you add may only refer to files. The other two arguments let you tag the enclosure with MIME type and subtype strings. (Note that BeMail currently tags all out-going enclosures as "application/befile".)

    GetEnclosure() returns, by reference through * ref, a pointer to the ref that represents the object's index'th enclosure. The enclosure's MIME type strings are pointed to by *mime_type and *mime_subtype. The MIME strings that the arguments point to are NULL-terminated for you. If index is out-of-bounds B_BAD_INDEX is returned (this includes the no-enclosure case). Otherwise, B_NO_ERROR is returned.

    RemoveEnclosure() removes the enclosure that's identified by the argument. If ref doesn't identify an existing enclosure, this function returns B_BAD_INDEX (look for the error return to change in a subsequent release). Otherwise, it returns B_NO_ERROR.

    CountEnclosures() returns the number of enclosures that are currently contained in the object.


    Status()

          long Status(void)

    Every BMailMessage has an internal state (that mustn't be confused with its record's status field) that tells whether the record that represents the object is currently queued to be sent. If it is, the status is B_MAIL_QUEUED, otherwise it's B_MAIL_NOT_QUEUED.




    The Be Book, HTML Edition, for Developer Release 8 of the Be Operating System.

    Copyright © 1996 Be, Inc. All rights reserved.

    Be, the Be logo, BeBox, BeOS, BeWare, and GeekPort are trademarks of Be, Inc.

    Last modified September 6, 1996.