/ProcImap/Utils/MailboxFactory.py

http://github.com/goerz/procimap · Python · 239 lines · 206 code · 4 blank · 29 comment · 2 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. import mailbox
  27. import sys
  28. if sys.version_info > (3, 0):
  29. from configparser import ConfigParser
  30. else:
  31. from ConfigParser import ConfigParser
  32. from ProcImap.ImapServer import ImapServer
  33. from ProcImap.ImapMailbox import ImapMailbox
  34. class UnknownMailboxTypeError(Exception):
  35. """ Raised when there is a mailbox type in the config file that is
  36. unknown to MailboxFactory. You may have to add it first with the
  37. 'set_type' method.
  38. """
  39. pass
  40. class FactoryIsNotMailboxTypeError(Exception):
  41. """ Raised when you supplied a factory that is not a subclass of
  42. mailbox.Mailbox.
  43. """
  44. pass
  45. class PathgeneratorNotCallableError(Exception):
  46. """ Raised when you supplied a pathgenerator that is not callable """
  47. pass
  48. class MailboxOptionsNotCompleteError(Exception):
  49. """ Raised if there are options missing in the config file that are
  50. needed in order to produce a mailbox object
  51. """
  52. pass
  53. class MailboxFactory:
  54. """ MailboxFactory is a factory class for Mailbox objects. You can define
  55. mailboxes of different types in an INI-style config file (the file
  56. has to parsable by ConfigParser.ConfigParser; the exceptions defined
  57. in ConfigParser may be thrown if the config file is not well-formed.)
  58. Each section in the config file describes one mailbox.
  59. An example of a valid config file 'mailboxes.cfg' is the following:
  60. [Standard]
  61. type = IMAP
  62. mailbox = INBOX
  63. server = mail.physik.fu-berlin.de
  64. username = goerz
  65. password = secret
  66. ssl = True
  67. port = 933
  68. [Sent]
  69. type = IMAP
  70. mailbox = Sent
  71. server = mail.physik.fu-berlin.de
  72. username = goerz
  73. password = secret
  74. [Backup]
  75. type = mbox
  76. path = /home/goerz/Mail/backup.mbox
  77. The type of the mailbox is described by the 'type' parameters. The
  78. types known by default are 'imap', 'mbox', 'maildir', 'MH', 'Babyl',
  79. and 'MMDF', all of which have corresponding subclasses of
  80. mailbox.Mailbox (all except ImapMailbox are defined in the standard
  81. library). The type specification is not case sensitive.
  82. The remaining parameters in a specific section depend on the type. The
  83. Mailbox classes from the standard library need only a path; IMAP needs
  84. type, mailbox, server, username, and password. The ssl and port
  85. parameters are optional. ssl is enabled by default; the port, if
  86. unspecified, is the standard port (933 for ssl, 433 otherwise).
  87. MailboxFactory has capabilities to extend the set of known types by
  88. using the set_type method.
  89. The MailboxFactory partly supports a read-only dictionary interface.
  90. """
  91. def __init__(self, configfilename):
  92. """ Initialize MailboxFactory files.
  93. The mailbox objects that can be generated must be described in
  94. configfilename.
  95. """
  96. self._types = {}
  97. self.set_type('mbox', mailbox.mbox, standard_pathgenerator)
  98. self.set_type('maildir', mailbox.Maildir, standard_pathgenerator)
  99. self.set_type('mh', mailbox.MH, standard_pathgenerator)
  100. self.set_type('babyl', mailbox.Babyl, standard_pathgenerator)
  101. self.set_type('mmdf', mailbox.MMDF, standard_pathgenerator)
  102. self.set_type('imap', ImapMailbox, imap_pathgenerator)
  103. self._configparser = ConfigParser()
  104. self._configparser.read(configfilename)
  105. def get(self, name):
  106. """ Create the Mailbox object that is described in section 'name'
  107. in the config file. For example,
  108. >>> mailboxes = MailboxFactory("mailboxes.cfg")
  109. >>> mb = mailboxes.get('Standard')
  110. mb would now be an object of type ImapMailbox if mailboxes.cfg
  111. contained the data as the example in the class docstring.
  112. """
  113. mailboxtype = self._configparser.get(name, 'type').lower()
  114. if not mailboxtype in self._types.keys():
  115. raise UnknownMailboxTypeError("Unknown type: %s" % mailboxtype)
  116. factory, pathgenerator = self._types[mailboxtype]
  117. path = pathgenerator(dict(self._configparser.items(name)))
  118. return(factory(path))
  119. def __getitem__(self, name):
  120. """ Shorthand for the get method.
  121. For example,
  122. >>> mailboxes = MailboxFactory("mailboxes.cfg")
  123. >>> mb = mailboxes['Standard']
  124. """
  125. return self.get(name)
  126. def get_server(self, name):
  127. """ Return an ImapServer instance from the server data that is
  128. described in section 'name'. The section must have the form of
  129. an imap mailbox (as described above). A TypeError will be raised
  130. if the section is not of type IMAP. The 'mailbox' key is ignored.
  131. For example, you could create an ImapServer like this:
  132. >>> mailboxes = MailboxFactory("mailboxes.cfg")
  133. >>> server = mailboxes.get_server('StandardServer')
  134. """
  135. mailboxtype = self._configparser.get(name, 'type').lower()
  136. if mailboxtype != 'imap':
  137. raise TypeError("You can only create a server from an IMAP mailbox")
  138. factory, pathgenerator = self._types[mailboxtype]
  139. path = pathgenerator(dict(self._configparser.items(name)))
  140. return(path[0])
  141. def __contains__(self, name):
  142. """ Return True if there is a mailbox with the given name,
  143. False otherwise """
  144. return (name in self._configparser.sections())
  145. def list(self):
  146. """ List all mailboxes defined in the config file """
  147. return self._configparser.sections()
  148. def data(self, name):
  149. """ List all the data associated with the mailbox name """
  150. return self._configparser.items(name)
  151. def set_type(self, typename, factory, pathgenerator):
  152. """ Make a new typename of Mailbox known. This allows you to
  153. handle new types of Mailbox objects beyond IMAP and the
  154. mailboxes of the standard library.
  155. factory is the class that generates the Mailbox object and must
  156. be a subclass of mailbox.Mailbox
  157. pathgenerator is a callable that receives a dict of options set
  158. in a section of the config file, and returns the 'path' that
  159. is passed as the first argument to the factory. For the standard
  160. mailboxes of the standard library, the 'path' is just a string,
  161. the path of the mailbox in the filesystem. For IMAP, the path
  162. is a tuple (server, name). For new types, this may be anything.
  163. For example the constructor of this class makes the 'mbox'
  164. type known as:
  165. self.set_type('mbox', mailbox.mbox, standard_pathgenerator)
  166. In combination,
  167. factory(pathgenerator(dict_of_options_in_configfile_section))
  168. should create a Mailbox object of the appropriate type.
  169. """
  170. if not issubclass(factory, mailbox.Mailbox):
  171. raise FactoryIsNotMailboxTypeError
  172. if not callable(pathgenerator):
  173. raise PathgeneratorNotCallableError
  174. self._types[str(typename).lower()] = (factory, pathgenerator)
  175. def imap_pathgenerator(optionsdict):
  176. """ Converts options into (server, name) tuple """
  177. try:
  178. name = optionsdict['mailbox']
  179. serveraddress = optionsdict['server']
  180. username = optionsdict['username']
  181. password = optionsdict['password']
  182. ssl = True
  183. if optionsdict.has_key('ssl'):
  184. if optionsdict['ssl'].lower() in ['0', 'false', 'no']:
  185. ssl = False
  186. port = None
  187. if optionsdict.has_key('port'):
  188. port = int(optionsdict['port'])
  189. except KeyError:
  190. raise MailboxOptionsNotCompleteError(
  191. "IMAP Mailbox object needs the following parameters\n " \
  192. + "'mailbox', 'server', 'username', 'password'.\n" \
  193. + "The 'ssl' and 'port' parameters are optional.")
  194. server = ImapServer(serveraddress, username, password, ssl, port)
  195. return(tuple((server, name)))
  196. def standard_pathgenerator(optionsdict):
  197. """ Extract 'path' from options """
  198. try:
  199. return optionsdict['path']
  200. except KeyError:
  201. raise MailboxOptionsNotCompleteError(
  202. "Standard Mailbox object needs 'path' parameter")