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.

265 lines
10 KiB

  1. # -*- encoding: utf-8 -*-
  2. ##############################################################################
  3. #
  4. # OpenERP, Open Source Management Solution
  5. # This module copyright (C) 2013 Therp BV (<http://therp.nl>)
  6. # All Rights Reserved
  7. #
  8. # This program is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU Affero General Public License as
  10. # published by the Free Software Foundation, either version 3 of the
  11. # License, or (at your option) any later version.
  12. #
  13. # This program is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU Affero General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. ##############################################################################
  22. import logging
  23. import base64
  24. import simplejson
  25. from lxml import etree
  26. from openerp import models, fields, api, exceptions
  27. from openerp.tools.translate import _
  28. from openerp.tools.misc import UnquoteEvalContext
  29. _logger = logging.getLogger(__name__)
  30. class fetchmail_server(models.Model):
  31. _inherit = 'fetchmail.server'
  32. folder_ids = fields.One2many(
  33. 'fetchmail.server.folder', 'server_id', 'Folders',
  34. context={'active_test': False})
  35. object_id = fields.Many2one(required=False)
  36. _defaults = {
  37. 'type': 'imap',
  38. }
  39. def onchange_server_type(
  40. self, cr, uid, ids, server_type=False, ssl=False,
  41. object_id=False):
  42. retval = super(
  43. fetchmail_server, self).onchange_server_type(cr, uid,
  44. ids, server_type, ssl,
  45. object_id)
  46. retval['value']['state'] = 'draft'
  47. return retval
  48. def fetch_mail(self, cr, uid, ids, context=None):
  49. if context is None:
  50. context = {}
  51. check_original = []
  52. for this in self.browse(cr, uid, ids, context):
  53. if this.object_id:
  54. check_original.append(this.id)
  55. context.update(
  56. {
  57. 'fetchmail_server_id': this.id,
  58. 'server_type': this.type
  59. })
  60. connection = this.connect()
  61. for folder in this.folder_ids.filtered('active'):
  62. this.handle_folder(connection, folder)
  63. connection.close()
  64. return super(fetchmail_server, self).fetch_mail(
  65. cr, uid, check_original, context)
  66. @api.multi
  67. def handle_folder(self, connection, folder):
  68. '''Return ids of objects matched'''
  69. matched_object_ids = []
  70. for this in self:
  71. _logger.info(
  72. 'start checking for emails in %s server %s',
  73. folder.path, this.name)
  74. match_algorithm = folder.get_algorithm()
  75. if connection.select(folder.path)[0] != 'OK':
  76. _logger.error(
  77. 'Could not open mailbox %s on %s',
  78. folder.path, this.server)
  79. connection.select()
  80. continue
  81. result, msgids = this.get_msgids(connection)
  82. if result != 'OK':
  83. _logger.error(
  84. 'Could not search mailbox %s on %s',
  85. folder.path, this.server)
  86. continue
  87. for msgid in msgids[0].split():
  88. matched_object_ids += this.apply_matching(
  89. connection, folder, msgid, match_algorithm)
  90. _logger.info(
  91. 'finished checking for emails in %s server %s',
  92. folder.path, this.name)
  93. return matched_object_ids
  94. @api.multi
  95. def get_msgids(self, connection):
  96. '''Return imap ids of messages to process'''
  97. return connection.search(None, 'UNDELETED')
  98. @api.multi
  99. def apply_matching(self, connection, folder, msgid, match_algorithm):
  100. '''Return ids of objects matched'''
  101. matched_object_ids = []
  102. for this in self:
  103. result, msgdata = connection.fetch(msgid, '(RFC822)')
  104. if result != 'OK':
  105. _logger.error(
  106. 'Could not fetch %s in %s on %s',
  107. msgid, folder.path, this.server)
  108. continue
  109. mail_message = self.env['mail.thread'].message_parse(
  110. msgdata[0][1], save_original=this.original)
  111. if self.env['mail.message'].search(
  112. [('message_id', '=', mail_message['message_id'])]):
  113. continue
  114. found_ids = match_algorithm.search_matches(
  115. self.env.cr, self.env.uid, folder, mail_message, msgdata[0][1])
  116. if found_ids and (len(found_ids) == 1 or
  117. folder.match_first):
  118. try:
  119. self.env.cr.execute('savepoint apply_matching')
  120. match_algorithm.handle_match(
  121. self.env.cr, self.env.uid, connection,
  122. found_ids[0], folder, mail_message,
  123. msgdata[0][1], msgid, self.env.context)
  124. self.env.cr.execute('release savepoint apply_matching')
  125. matched_object_ids += found_ids[:1]
  126. except Exception:
  127. self.env.cr.execute('rollback to savepoint apply_matching')
  128. _logger.exception(
  129. "Failed to fetch mail %s from %s", msgid, this.name)
  130. elif folder.flag_nonmatching:
  131. connection.store(msgid, '+FLAGS', '\\FLAGGED')
  132. return matched_object_ids
  133. @api.multi
  134. def attach_mail(self, connection, object_id, folder, mail_message, msgid):
  135. '''Return ids of messages created'''
  136. mail_message_ids = []
  137. for this in self:
  138. partner_id = None
  139. if folder.model_id.model == 'res.partner':
  140. partner_id = object_id
  141. if 'partner_id' in self.env[folder.model_id.model]._columns:
  142. partner_id = self.env[folder.model_id.model].browse(object_id)\
  143. .partner_id.id
  144. attachments = []
  145. if this.attach and mail_message.get('attachments'):
  146. for attachment in mail_message['attachments']:
  147. fname, fcontent = attachment
  148. if isinstance(fcontent, unicode):
  149. fcontent = fcontent.encode('utf-8')
  150. data_attach = {
  151. 'name': fname,
  152. 'datas': base64.b64encode(str(fcontent)),
  153. 'datas_fname': fname,
  154. 'description': _('Mail attachment'),
  155. 'res_model': folder.model_id.model,
  156. 'res_id': object_id,
  157. }
  158. attachments.append(
  159. self.env['ir.attachment'].create(data_attach))
  160. mail_message_ids.append(
  161. self.env['mail.message'].create({
  162. 'author_id': partner_id,
  163. 'model': folder.model_id.model,
  164. 'res_id': object_id,
  165. 'type': 'email',
  166. 'body': mail_message.get('body'),
  167. 'subject': mail_message.get('subject'),
  168. 'email_from': mail_message.get('from'),
  169. 'date': mail_message.get('date'),
  170. 'message_id': mail_message.get('message_id'),
  171. 'attachment_ids': [(6, 0, [a.id for a in attachments])],
  172. }))
  173. if folder.delete_matching:
  174. connection.store(msgid, '+FLAGS', '\\DELETED')
  175. return mail_message_ids
  176. def button_confirm_login(self, cr, uid, ids, context=None):
  177. retval = super(fetchmail_server, self).button_confirm_login(
  178. cr, uid, ids, context)
  179. for this in self.browse(cr, uid, ids, context):
  180. this.write({'state': 'draft'})
  181. connection = this.connect()
  182. connection.select()
  183. for folder in this.folder_ids.filtered('active'):
  184. if connection.select(folder.path)[0] != 'OK':
  185. raise exceptions.ValidationError(
  186. _('Mailbox %s not found!') % folder.path)
  187. connection.close()
  188. this.write({'state': 'done'})
  189. return retval
  190. def fields_view_get(self, cr, user, view_id=None, view_type='form',
  191. context=None, toolbar=False, submenu=False):
  192. result = super(fetchmail_server, self).fields_view_get(
  193. cr, user, view_id, view_type, context, toolbar, submenu)
  194. if view_type == 'form':
  195. view = etree.fromstring(
  196. result['fields']['folder_ids']['views']['form']['arch'])
  197. modifiers = {}
  198. docstr = ''
  199. for algorithm in self.pool['fetchmail.server.folder']\
  200. ._get_match_algorithms().itervalues():
  201. for modifier in ['required', 'readonly']:
  202. for field in getattr(algorithm, modifier + '_fields'):
  203. modifiers.setdefault(field, {})
  204. modifiers[field].setdefault(modifier, [])
  205. if modifiers[field][modifier]:
  206. modifiers[field][modifier].insert(0, '|')
  207. modifiers[field][modifier].append(
  208. ("match_algorithm", "==", algorithm.__name__))
  209. docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \
  210. '\n\n'
  211. for field in view.xpath('//field'):
  212. if field.tag == 'field' and field.get('name') in modifiers:
  213. field.set('modifiers', simplejson.dumps(
  214. dict(
  215. eval(field.attrib['modifiers'],
  216. UnquoteEvalContext({})),
  217. **modifiers[field.attrib['name']])))
  218. if (field.tag == 'field' and
  219. field.get('name') == 'match_algorithm'):
  220. field.set('help', docstr)
  221. result['fields']['folder_ids']['views']['form']['arch'] = \
  222. etree.tostring(view)
  223. return result