PageRenderTime 32ms CodeModel.GetById 24ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 0ms

/ProcImap/Utils/MailboxFactory.py

http://github.com/goerz/procimap
Python | 239 lines | 206 code | 4 blank | 29 comment | 1 complexity | 9545f97ee1fbe506710312033c344bc3 MD5 | raw file
  1############################################################################
  2#    Copyright (C) 2008 by Michael Goerz                                   #
  3#    http://www.physik.fu-berlin.de/~goerz                                 #
  4#                                                                          #
  5#    This program is free software; you can redistribute it and#or modify  #
  6#    it under the terms of the GNU General Public License as published by  #
  7#    the Free Software Foundation; either version 3 of the License, or     #
  8#    (at your option) any later version.                                   #
  9#                                                                          #
 10#    This program is distributed in the hope that it will be useful,       #
 11#    but WITHOUT ANY WARRANTY; without even the implied warranty of        #
 12#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
 13#    GNU General Public License for more details.                          #
 14#                                                                          #
 15#    You should have received a copy of the GNU General Public License     #
 16#    along with this program; if not, write to the                         #
 17#    Free Software Foundation, Inc.,                                       #
 18#    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             #
 19############################################################################
 20"""
 21    This module contains the MailboxFactory class, together with some
 22    Exceptions that are thrown by MailboxFactory, and the pathgenerator
 23    functions used for the default mailbox types (see documentation
 24    of MailboxFactory class)
 25
 26
 27"""
 28
 29import mailbox
 30import sys
 31
 32if sys.version_info > (3, 0):
 33    from configparser import ConfigParser
 34else:
 35    from ConfigParser import ConfigParser
 36
 37from ProcImap.ImapServer import ImapServer
 38from ProcImap.ImapMailbox import ImapMailbox
 39
 40class UnknownMailboxTypeError(Exception):
 41    """ Raised when there is a mailbox type in the config file that is
 42        unknown to MailboxFactory. You may have to add it first with the
 43        'set_type' method.
 44    """
 45    pass
 46
 47class FactoryIsNotMailboxTypeError(Exception):
 48    """ Raised when you supplied a factory that is not a subclass of
 49        mailbox.Mailbox.
 50    """
 51    pass
 52
 53class PathgeneratorNotCallableError(Exception):
 54    """ Raised when you supplied a pathgenerator that is not callable """
 55    pass
 56
 57class MailboxOptionsNotCompleteError(Exception):
 58    """ Raised if there are options missing in the config file that are
 59        needed in order to produce a mailbox object
 60    """
 61    pass
 62
 63class MailboxFactory:
 64    """ MailboxFactory is a factory class for Mailbox objects. You can define
 65        mailboxes of different types in an INI-style config file (the file
 66        has to parsable by ConfigParser.ConfigParser; the exceptions defined
 67        in ConfigParser may be thrown if the config file is not well-formed.)
 68        Each section in the config file describes one mailbox.
 69
 70        An example of a valid config file 'mailboxes.cfg' is the following:
 71
 72            [Standard]
 73            type = IMAP
 74            mailbox = INBOX
 75            server = mail.physik.fu-berlin.de
 76            username = goerz
 77            password = secret
 78            ssl = True
 79            port = 933
 80
 81            [Sent]
 82            type = IMAP
 83            mailbox = Sent
 84            server = mail.physik.fu-berlin.de
 85            username = goerz
 86            password = secret
 87
 88            [Backup]
 89            type = mbox
 90            path = /home/goerz/Mail/backup.mbox
 91
 92        The type of the mailbox is described by the 'type' parameters. The
 93        types known by default are 'imap', 'mbox', 'maildir', 'MH', 'Babyl',
 94        and 'MMDF', all of which have corresponding subclasses of
 95        mailbox.Mailbox (all except ImapMailbox are defined in the standard
 96        library). The type specification is not case sensitive.
 97
 98        The remaining parameters in a specific section depend on the type. The
 99        Mailbox classes from the standard library need only a path; IMAP needs
100        type, mailbox, server, username, and password. The ssl and port
101        parameters are optional. ssl is enabled by default; the port, if
102        unspecified, is the standard port (933 for ssl, 433 otherwise).
103
104        MailboxFactory has capabilities to extend the set of known types by
105        using the set_type method.
106
107        The MailboxFactory partly supports a read-only dictionary interface.
108    """
109    def __init__(self, configfilename):
110        """ Initialize MailboxFactory files.
111            The mailbox objects that can be generated must be described in
112            configfilename.
113        """
114        self._types = {}
115        self.set_type('mbox', mailbox.mbox, standard_pathgenerator)
116        self.set_type('maildir', mailbox.Maildir, standard_pathgenerator)
117        self.set_type('mh', mailbox.MH, standard_pathgenerator)
118        self.set_type('babyl', mailbox.Babyl, standard_pathgenerator)
119        self.set_type('mmdf', mailbox.MMDF, standard_pathgenerator)
120        self.set_type('imap', ImapMailbox, imap_pathgenerator)
121        self._configparser = ConfigParser()
122        self._configparser.read(configfilename)
123
124    def get(self, name):
125        """ Create the Mailbox object that is described in section 'name'
126            in the config file. For example,
127                >>> mailboxes = MailboxFactory("mailboxes.cfg")
128                >>> mb = mailboxes.get('Standard')
129            mb would now be an object of type ImapMailbox if mailboxes.cfg
130            contained the data as the example in the class docstring.
131        """
132        mailboxtype = self._configparser.get(name, 'type').lower()
133        if not mailboxtype in self._types.keys():
134            raise UnknownMailboxTypeError("Unknown type: %s" % mailboxtype)
135        factory, pathgenerator = self._types[mailboxtype]
136        path = pathgenerator(dict(self._configparser.items(name)))
137        return(factory(path))
138
139    def __getitem__(self, name):
140        """ Shorthand for the get method.
141            For example,
142                >>> mailboxes = MailboxFactory("mailboxes.cfg")
143                >>> mb = mailboxes['Standard']
144        """
145        return self.get(name)
146
147
148    def get_server(self, name):
149        """ Return an ImapServer instance from the server data that is
150            described in section 'name'. The section must have the form of
151            an imap mailbox (as described above). A TypeError will be raised
152            if the section is not of type IMAP. The 'mailbox' key is ignored.
153
154            For example, you could create an ImapServer like this:
155
156                >>> mailboxes = MailboxFactory("mailboxes.cfg")
157                >>> server = mailboxes.get_server('StandardServer')
158        """
159        mailboxtype = self._configparser.get(name, 'type').lower()
160        if mailboxtype != 'imap':
161            raise TypeError("You can only create a server from an IMAP mailbox")
162        factory, pathgenerator = self._types[mailboxtype]
163        path = pathgenerator(dict(self._configparser.items(name)))
164        return(path[0])
165
166    def __contains__(self, name):
167        """ Return True if there is a mailbox with the given name, 
168            False otherwise """
169        return (name in self._configparser.sections())
170
171
172    def list(self):
173        """ List all mailboxes defined in the config file """
174        return self._configparser.sections()
175
176    def data(self, name):
177        """ List all the data associated with the mailbox name """
178        return self._configparser.items(name)
179
180    def set_type(self, typename, factory, pathgenerator):
181        """ Make a new typename of Mailbox known. This allows you to
182            handle new types of Mailbox objects beyond IMAP and the
183            mailboxes of the standard library.
184
185            factory is the class that generates the Mailbox object and must
186            be a subclass of mailbox.Mailbox
187
188            pathgenerator is a callable that receives a dict of options set
189            in a section of the config file, and returns the 'path' that
190            is passed as the first argument to the factory. For the standard
191            mailboxes of the standard library, the 'path' is just a string,
192            the path of the mailbox in the filesystem. For IMAP, the path
193            is a tuple (server, name). For new types, this may be anything.
194
195            For example the constructor of this class makes the 'mbox'
196            type known as:
197            self.set_type('mbox', mailbox.mbox, standard_pathgenerator)
198
199            In combination,
200            factory(pathgenerator(dict_of_options_in_configfile_section))
201            should create a Mailbox object of the appropriate type.
202        """
203        if not issubclass(factory, mailbox.Mailbox):
204            raise FactoryIsNotMailboxTypeError
205        if not callable(pathgenerator):
206            raise PathgeneratorNotCallableError
207        self._types[str(typename).lower()] = (factory, pathgenerator)
208
209
210def imap_pathgenerator(optionsdict):
211    """ Converts options into (server, name) tuple """
212    try:
213        name = optionsdict['mailbox']
214        serveraddress = optionsdict['server']
215        username = optionsdict['username']
216        password = optionsdict['password']
217        ssl = True
218        if optionsdict.has_key('ssl'):
219            if optionsdict['ssl'].lower() in ['0', 'false', 'no']:
220                ssl = False
221        port = None
222        if optionsdict.has_key('port'):
223            port = int(optionsdict['port'])
224    except KeyError:
225        raise MailboxOptionsNotCompleteError(
226              "IMAP Mailbox object needs the following parameters\n " \
227              + "'mailbox', 'server', 'username', 'password'.\n" \
228              + "The 'ssl' and 'port' parameters are optional.")
229    server = ImapServer(serveraddress, username, password, ssl, port)
230    return(tuple((server, name)))
231
232
233def standard_pathgenerator(optionsdict):
234    """ Extract 'path' from options """
235    try:
236        return optionsdict['path']
237    except KeyError:
238        raise MailboxOptionsNotCompleteError(
239              "Standard Mailbox object needs 'path' parameter")