/ProcImap/Utils/MailboxFactory.py
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")