You are currently viewing a snapshot of taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to, please file a bug.

grendel architecture
(the old version)
by Jamie Zawinski

This is an older Grendel design document, which is probably fairly out of date at this point, but which might make interesting reading anyway. (Among other things, this document was written before we had heard of, let alone adopted, the JavaMail API.)

    Date: Tue, 12 Aug 1997 21:07:09 -0700
    From: Jamie Zawinski <>
    Subject: Re: some doodling about mail

Since I don't have Terry's spiffy drawing program, I've converted his stuff to HTML and expanded on it.

Things I changed:

  • I think getFolderTree() is better thought of as getRootFolder() which just returns a folder which probably has sub-folders (that would be the nsmail/ folder.)

  • I don't see the point of EditableFolder; even news folders are editable, since news supports cancel, and since users can post new messages. Also, any non-news folder could change its editability status: chmod, or IMAP permissions. So I think the editability of these folders should be a runtime property of the folder.

  • NNTP and IMAP are closer to each other than BSD, so I made the hierarchy show this.

  • I don't like the distinction between ``real'' folders and search results. Or rather, I like the search results folder to be the more general of the two, and the ``real'' folder to be a specific instance of that (one with a highly constrained search term.) So I turned that part of the hierarchy unside out.

  • I don't see why BSDmessage should be its own class; that seems like a runtime decision to me. Each message points to the folder from which it came, and that folder has a particular (unchanging) type. What methods would you want to define on BSDmessage that wouldn't more properly be on BSDfolder instead?

  • I still absolutely disagree that threading is a kind of sorting. They are disjoint.

  • I think we should move away from the notion of a ``mail drop''; it seems to be widely hated. People like messages to just magically show up in their folders without having to bonk on Get Mail, at least some of the time.

    We need to be able to open /var/mail/$USER as a folder and have it work. You know how much I hate that model of operation, but we need to do it.

    But more importantly (and it's a more general problem) we need to let people use procmail, and dump messages right into their folders. This means we need some sort of asynchronous notification mechanism, where the backend tells the front end that new messages have appeared? I don't know.

    Perhaps the incoming-message-filter-rules should live on MessageStore. I guess this is distinct from killfiles, which really seem like they ought to live on MessageSetView.setSearchTerm().

    Here's a distinction between public and private folders: on a private folder, you want your filters to actually delete messages. On a public folder, you simply want it to never show them to you. Hmmmm...

So here's a new backend class hierarchy...

  • MessageStore
    This is the central storage place for the list of folders. There would be one of these for each IMAP and NNTP server, for example; and one for the folders on the local disk.
    • String descriptionString();
    • Folder getRootFolder();

  • Folder
    Folders might contain messages, or folders, or both, depending on the underlying representation.
    • String getName();
    • Enumeration getSubFolders();
    • Enumeration getMessages();
    • void addMessage(Message);
    • void deleteMessage(Message);

    • LocalFolder
      An abstract class for folders living on the local disk.

      • BSDFolder
        A folder who's underlying representation is a BSD mbox file (or perhaps the Content-Length variant, if we really feel adventurous.) This would contain the implementation of the caches used to quickly generate summary information (what used to be .summary files.)

    • RemoteFolder
      An abstract class for folders living on the network.

      • IMAPFolder
        A folder who's underlying representation resides on an IMAP server. This would implement summarization by talking to the server.

      • NewsFolder
        A folder who's underlying representation resides on an NNTP server. This implements deleteMessage in terms of ``cancel'', and addMessage by raising an exception (I guess.)

  • MessageSetView
    A view into one or more Folders; the UI will mostly be dealing with FolderViews, not Folders. The SearchTerm specifies how the set of messages in this view was acquired.

    The SearchTerm is where killfiles live.

    What about scorefiles?

    • SearchTerm getSearchTerm();
    • void setSearchTerm(SearchTerm);
    • int getNumMessages();
    • Message getMessage(int which);
    • Message getMessage(String messageID);
    • int getIndex(Message);
    • void setSort(SortOrder);
    • SortOrder getSort();
    • void setIsThreaded(Bool);
    • Bool isThreaded();

    • FolderView
      This is a particular, highly-constrained variant of MessageSetView, where the search term is chosen to show only messages from one particular folder. It might not show all of the messages in the folder, but all messages will be in the same folder.

      This does not imply that, should a search only happen to return messages from one particular folder, that a FolderView will be used: FolderView is only used when the desire to have messages from one particular folder is specified at the outset.

      The important distinction here is that you can unambiguously delete a message, and it's clear that it's going to vanish from the real folder, and not just from the ``virtual'' search-results folder.

      • Folder getFolder();

  • Message
    Actually this is more along the lines of MessageDescriptor, or MessageSummary, since this object represents a message without necessarily having access to the message itself: just the summary information. That's why it doesn't return an object representing the whole header block: that data probably isn't available until the message is actually displayed.

    The getMimeStructure() method might return ``unknown'' if the underlying folder representation doesn't save that information. (NNTP doesn't; BSD doesn't currently but could.) In no event will that method parse the folder to find out, because that would be expensive (and should be explicit.)

    What if someday someone wants to add some other header to their summary? Say they want to have a column listing the ``Approved'' header, just to pick a random example. Suppose further that some of the summary formats used support this (they might.) This model (with one method for each available header) doesn't deal with that nicely. Perhaps instead, there should be a getSummaryHeaders() method, which would return an object which contains some set of headers and values (some minimal set of which can be assumed to exist, but with others potentially present.)

    • String getMessageID();
    • String getSubject();
    • String getAuthor();
    • String getRecipients();
    • String getSentDate();
    • Priority getPriority();
    • void setPriority();
    • Bool isFlagged();
    • Bool setFlagged(Bool);
    • Bool isSigned();
    • Bool isEncrypted();
    • Folder getStorageFolder();
    • Int getStorageFolderIndex();
    • Message getThreadParent();
    • Enumeration getThreadChildren();
    • Enumeration getThreadSiblings();
    • InputStream getRawText();
    • MimePart getMimeStructure();

  • MimePart
    This describes, and gives access to, the MIME tree within a message: this is what Message.getMimeStructure() returns.
    • String getContentType();
    • String getFileName();
    • String getDescription();
    • String getContentID();
    • Int getApproximateByteSize();
    • Enumeration getMimeChildren();
    • InputStream getRawText();

  • MimeParser

What follows is a class hierarchy for what we used to call the front end. Does any of this make sense? At some level, this all just describes windows which interact with the objects described above, and with the user.

  • Displayer
    This is a generic window, basically.

    • LeafDisplayer
      Displayers which display single objects (not sets.)

      • MessageDisplayer
        Displays a single MIME message.
        • void delete()
        • void newMessage()
        • void replyToSender()
        • void replyToAll()
        • void forward()
        • void forwardQuoted()

      • PersonDisplayer
        Displays a single person (mail recipient.)
        • void delete()
        • void edit()
        • void mailTo()

    • SetDisplayer
      Displayers which display sets of objects.

      • MessageSetDisplayer
        Displays a set of messages (a FolderView.)
        • void delete()
        • void newMessage()
        • void replyToSender()
        • void replyToAll()
        • void forward()
        • void forwardQuoted()
        • void selectNext()
        • void selectPrevious()
        • void markRead()
        • void markUnread()
        • void markThreadRead()
        • void markThreadUnread()

      • MessageSetSetDisplayer
        Displays a set of folders (a MessageStore.)
        • void delete()
        • void compact()

      • PersonSetDisplayer
        Displays a set of people (an Address Book.)
        • void new()
        • void delete()
        • void edit()
        • void mailTo()

    • FrameDisplayer
      A window with subwindows. These bind together windows of other types.

      • ThreePaneMessageViewer
        This looks like Cheddar/Akbar.
        • void emptyTrash()
        • MessageSetSetDisplayer getFolderWindow()
        • MessageSetDisplayer getThreadWindow()
        • MessageDisplayer getMessageWindow()

      • MultiWindowMessageViewer
        This looks like Dogbert.
        • MessageSetSetDisplayer getFolderWindow()
        • MessageSetDisplayer createThreadWindow()
        • MessageDisplayer createMessageWindow()
        • Enumeration getThreadWindows()
        • Enumeration getMessageWindows()