You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
8.6 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright - 2013-2018 Therp BV <https://therp.nl>.
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import base64
  5. import logging
  6. from odoo import _, api, models, fields
  7. from .. import match_algorithm
  8. _logger = logging.getLogger(__name__)
  9. class FetchmailServerFolder(models.Model):
  10. _name = 'fetchmail.server.folder'
  11. _rec_name = 'path'
  12. _order = 'sequence'
  13. def _get_match_algorithms(self):
  14. def get_all_subclasses(cls):
  15. return (cls.__subclasses__() +
  16. [subsub
  17. for sub in cls.__subclasses__()
  18. for subsub in get_all_subclasses(sub)])
  19. return dict([(cls.__name__, cls)
  20. for cls in get_all_subclasses(
  21. match_algorithm.base.Base)])
  22. def _get_match_algorithms_sel(self):
  23. algorithms = []
  24. for cls in self._get_match_algorithms().itervalues():
  25. algorithms.append((cls.__name__, cls.name))
  26. algorithms.sort()
  27. return algorithms
  28. sequence = fields.Integer('Sequence')
  29. path = fields.Char(
  30. 'Path',
  31. help="The path to your mail folder. Typically would be something like "
  32. "'INBOX.myfolder'", required=True)
  33. model_id = fields.Many2one(
  34. 'ir.model', 'Model', required=True,
  35. help='The model to attach emails to')
  36. model_field = fields.Char(
  37. 'Field (model)',
  38. help='The field in your model that contains the field to match '
  39. 'against.\n'
  40. 'Examples:\n'
  41. "'email' if your model is res.partner, or "
  42. "'partner_id.email' if you're matching sale orders")
  43. model_order = fields.Char(
  44. 'Order (model)',
  45. help='Field(s) to order by, this mostly useful in conjunction '
  46. "with 'Use 1st match'")
  47. match_algorithm = fields.Selection(
  48. _get_match_algorithms_sel,
  49. 'Match algorithm', required=True,
  50. help='The algorithm used to determine which object an email matches.')
  51. mail_field = fields.Char(
  52. 'Field (email)',
  53. help='The field in the email used for matching. Typically '
  54. "this is 'to' or 'from'")
  55. server_id = fields.Many2one('fetchmail.server', 'Server')
  56. delete_matching = fields.Boolean(
  57. 'Delete matches',
  58. help='Delete matched emails from server')
  59. flag_nonmatching = fields.Boolean(
  60. 'Flag nonmatching',
  61. default=True,
  62. help="Flag emails in the server that don't match any object in Odoo")
  63. match_first = fields.Boolean(
  64. 'Use 1st match',
  65. help='If there are multiple matches, use the first one. If '
  66. 'not checked, multiple matches count as no match at all')
  67. domain = fields.Char(
  68. 'Domain',
  69. help='Fill in a search filter to narrow down objects to match')
  70. msg_state = fields.Selection(
  71. selection=[('sent', 'Sent'), ('received', 'Received')],
  72. string='Message state',
  73. default='received',
  74. help='The state messages fetched from this folder should be '
  75. 'assigned in Odoo')
  76. active = fields.Boolean('Active', default=True)
  77. @api.multi
  78. def get_algorithm(self):
  79. return self._get_match_algorithms()[self.match_algorithm]()
  80. @api.multi
  81. def button_attach_mail_manually(self):
  82. return {
  83. 'type': 'ir.actions.act_window',
  84. 'res_model': 'fetchmail.attach.mail.manually',
  85. 'target': 'new',
  86. 'context': dict(self.env.context, default_folder_id=self.id),
  87. 'view_type': 'form',
  88. 'view_mode': 'form'}
  89. @api.multi
  90. def get_msgids(self, connection):
  91. """Return imap ids of messages to process"""
  92. return connection.search(None, 'UNDELETED')
  93. @api.multi
  94. def retrieve_imap_folder(self):
  95. """Retrieve all mails for one IMAP folder.
  96. For each folder on each server we create a separate connection.
  97. """
  98. for this in self:
  99. server = this.server_id
  100. try:
  101. _logger.info(
  102. 'start checking for emails in folder %s on server %s',
  103. this.path, server.name)
  104. imap_server = server.connect()
  105. if imap_server.select(this.path)[0] != 'OK':
  106. _logger.error(
  107. 'Could not open mailbox %s on %s',
  108. this.path, server.name)
  109. continue
  110. result, msgids = this.get_msgids(imap_server)
  111. if result != 'OK':
  112. _logger.error(
  113. 'Could not search mailbox %s on %s',
  114. this.path, server.name)
  115. continue
  116. match_algorithm = this.get_algorithm()
  117. for msgid in msgids[0].split():
  118. this.apply_matching(
  119. imap_server, msgid, match_algorithm)
  120. _logger.info(
  121. 'finished checking for emails in %s server %s',
  122. this.path, server.name)
  123. except Exception:
  124. _logger.info(_(
  125. "General failure when trying to fetch mail from"
  126. " %s server %s."),
  127. server.type, server.name, exc_info=True)
  128. finally:
  129. if imap_server:
  130. imap_server.close()
  131. imap_server.logout()
  132. @api.multi
  133. def apply_matching(self, connection, msgid, match_algorithm):
  134. """Return ids of objects matched"""
  135. self.ensure_one()
  136. result, msgdata = connection.fetch(msgid, '(RFC822)')
  137. if result != 'OK':
  138. _logger.error(
  139. 'Could not fetch %s in %s on %s',
  140. msgid, self.path, self.server_id.server)
  141. return
  142. mail_message = self.env['mail.thread'].message_parse(
  143. msgdata[0][1], save_original=self.server_id.original)
  144. if self.env['mail.message'].search(
  145. [('message_id', '=', mail_message['message_id'])]):
  146. # Ignore mails that have been handled already
  147. return
  148. matches = match_algorithm.search_matches(
  149. self, mail_message, msgdata[0][1])
  150. if matches and (len(matches) == 1 or self.match_first):
  151. try:
  152. self.env.cr.execute('savepoint apply_matching')
  153. match_algorithm.handle_match(
  154. connection,
  155. matches[0], self, mail_message,
  156. msgdata[0][1], msgid)
  157. self.env.cr.execute('release savepoint apply_matching')
  158. except Exception:
  159. self.env.cr.execute('rollback to savepoint apply_matching')
  160. _logger.exception(
  161. "Failed to fetch mail %s from %s",
  162. msgid, self.server_id.name)
  163. elif self.flag_nonmatching:
  164. connection.store(msgid, '+FLAGS', '\\FLAGGED')
  165. @api.multi
  166. def attach_mail(self, match_object, mail_message):
  167. """Attach mail to match_object."""
  168. self.ensure_one()
  169. partner = False
  170. model_name = self.model_id.model
  171. if model_name == 'res.partner':
  172. partner = match_object
  173. elif 'partner_id' in self.env[model_name]._fields:
  174. partner = match_object.partner_id
  175. attachments = []
  176. if self.server_id.attach and mail_message.get('attachments'):
  177. for attachment in mail_message['attachments']:
  178. # Attachment should at least have filename and data, but
  179. # might have some extra element(s)
  180. if len(attachment) < 2:
  181. continue
  182. fname, fcontent = attachment[:2]
  183. if isinstance(fcontent, unicode):
  184. fcontent = fcontent.encode('utf-8')
  185. data_attach = {
  186. 'name': fname,
  187. 'datas': base64.b64encode(str(fcontent)),
  188. 'datas_fname': fname,
  189. 'description': _('Mail attachment'),
  190. 'res_model': model_name,
  191. 'res_id': match_object.id}
  192. attachments.append(
  193. self.env['ir.attachment'].create(data_attach))
  194. self.env['mail.message'].create({
  195. 'author_id': partner and partner.id or False,
  196. 'model': model_name,
  197. 'res_id': match_object.id,
  198. 'message_type': 'email',
  199. 'body': mail_message.get('body'),
  200. 'subject': mail_message.get('subject'),
  201. 'email_from': mail_message.get('from'),
  202. 'date': mail_message.get('date'),
  203. 'message_id': mail_message.get('message_id'),
  204. 'attachment_ids': [(6, 0, [a.id for a in attachments])]})