The Internet Message Access Protocol (IMAP) was designed to allow for remote access, storage, and management of email messages. This ability to store messages on a central server is useful for a couple of reasons. First, it makes email available in more than one place. If your mail is on an IMAP server, you can switch between your desktop and your laptop and still access your mail. Second, it makes it easier to administer email for workgroups and corporations. Instead of having to track and back up email across hundreds of hard drives, it can be managed in a single, central place.
The specification for the current version of IMAP (Version 4, Revision 1) is defined in RFC 3501 (http://www.faqs.org/rfcs/rfc3501.html). IMAP is a powerful but complicated protocol, and the RFC takes up more than 100 pages. It's the kind of protocol that would be a ton of work to implement yourself. Fortunately, the Twisted developers have written an complete IMAP implementation, which provides a nice API for working with IMAP servers. This lab demonstrates how to log in to an IMAP server and get a list of available mailboxes.
7.4.1. How Do I Do That?
Use a subclass of imap4.IMAP4Client as your Protocol. In the serverGreeting function, call self.login with a username and password. Once successfully logged in, call self.list to get a list of the available mailboxes (see Example 7-5).
Example 7-5. imapfolders.py
from twisted.protocols import imap4 from twisted.internet import protocol, defer class IMAPFolderListProtocol(imap4.IMAP4Client): def serverGreeting(self, capabilities): login = self.login(self.factory.username, self.factory.password) login.addCallback(self._ _loggedIn) login.chainDeferred(self.factory.deferred) def _ _loggedIn(self, results): return self.list("", "*").addCallback(self._ _gotMailboxList) def _ _gotMailboxList(self, list): return [boxInfo for boxInfo in list] def connectionLost(self, reason): if not self.factory.deferred.called: # connection was lost unexpectedly! self.factory.deferred.errback(reason) class IMAPFolderListFactory(protocol.ClientFactory): protocol = IMAPFolderListProtocol def _ _init_ _(self, username, password): self.username = username self.password = password self.deferred = defer.Deferred( ) def clientConnectionFailed(self, connection, reason): self.deferred.errback(reason) if __name__ == "_ _main_ _": from twisted.internet import reactor import sys, getpass def printMailboxList(list): list.sort( ) for box in list: print box reactor.stop( ) def handleError(error): print >> sys.stderr, "Error:", error.getErrorMessage( ) reactor.stop( ) if not len(sys.argv) == 3: print "Usage: %s server login" % sys.argv sys.exit(1) server = sys.argv user = sys.argv password = getpass.getpass("Password: ") factory = IMAPFolderListFactory(user, password) factory.deferred.addCallback( printMailboxList).addErrback( handleError) reactor.connectTCP(server, 143, factory) reactor.run( )
Run imapfolders.py with two arguments: the IMAP server and your login username. It will prompt you for your password, log you in to the server, and then download and print a list of your mailboxes:
$ python imapfolders.py imap.myisp.com mylogin Password: INBOX Archive Drafts Mailing Lists Mailing Lists/Twisted Mailing Lists/Twisted Web Sent Spam Trash
7.4.2. How Does That Work?
imapfolders.py uses the familiar pattern of a ClientFactory and Protocol working together. The Protocol communicates with the server, and the ClientFactory provides a Deferred that the program uses to track the success or failure of the task at hand.
The IMAPFolderListProtocol class in imapfolders.py inherits from IMAP4Client, which provides a nice Deferred-based interface. Every IMAP command returns a Deferred that will be called back with the reply received from the server. The use of Deferreds makes it easy to run a series of commands, one after the other: have the callback handler for each command run the next command and return its Deferred.
There's one Deferred method used in imapfolders.py that hadn't been used in any of the previous examples: chainDeferred. The chainDeferred method is used when you want to take the results of one Deferred and pass them to another Deferred. The line deferredOne.chainDeferred(deferredTwo) is equivalent to:
The IMAPFolderListProtocol in imapfolders.py has its serverGreeting method called when the connection to the server is established and the server has indicated that it's ready to accept commands. In response to serverGreeting, it calls self.login, and sets up a callback to self._loggedIn. Then it uses chainDeferred to send the eventual results of self.login back to self.factory.deferred. The order of these steps is important. Because the callback to self._loggedIn is added before the call to chainDeferred, self.factory.deferred won't receive the direct result of self.login, but the result returned by self. _loggedIn. As it turns out, self. _loggedIn returns another Deferred, the result of self.list, which will be run through self. _gotMailboxList to extract the mailbox names from the full response returned by the server. So self.factory.deferred is called back with the value that IMAPFolderListProtocol is supposed to provide: a list of mailbox names.
One of the most useful things about using Deferreds in this way is error handling. Remember how in Example 7-2, the POP3DownloadProtocol had to check the results of every command, and call a function to let the factory know there was an error? There's none of that here. Since self.list is called as part of the event handler for self.login, and self.login is chained to self.factory.deferred, self.factory.deferred will receive any error that occurs in either of these steps.
Building Simple Clients and Servers
Web Services and RPC
NNTP Clients and Servers
Services, Processes, and Logging