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.

281 lines
11 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.safe_eval import safe_eval
  28. from openerp.tools.translate import _
  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. if not this.folder_ids.filtered('active'):
  56. continue
  57. context.update(
  58. {
  59. 'fetchmail_server_id': this.id,
  60. 'server_type': this.type
  61. })
  62. connection = this.connect()
  63. for folder in this.folder_ids.filtered('active'):
  64. this.with_context(safe_eval(folder.context or '{}'))\
  65. .handle_folder(connection, folder)
  66. connection.close()
  67. return super(fetchmail_server, self).fetch_mail(
  68. cr, uid, check_original, context)
  69. @api.multi
  70. def handle_folder(self, connection, folder):
  71. '''Return ids of objects matched'''
  72. matched_object_ids = []
  73. for this in self:
  74. _logger.info(
  75. 'start checking for emails in %s server %s',
  76. folder.path, this.name)
  77. match_algorithm = folder.get_algorithm()
  78. if connection.select(folder.path)[0] != 'OK':
  79. _logger.error(
  80. 'Could not open mailbox %s on %s',
  81. folder.path, this.server)
  82. connection.select()
  83. continue
  84. result, msgids = this.get_msgids(connection)
  85. if result != 'OK':
  86. _logger.error(
  87. 'Could not search mailbox %s on %s',
  88. folder.path, this.server)
  89. continue
  90. for msgid in msgids[0].split():
  91. matched_object_ids += this.apply_matching(
  92. connection, folder, msgid, match_algorithm)
  93. _logger.info(
  94. 'finished checking for emails in %s server %s',
  95. folder.path, this.name)
  96. return matched_object_ids
  97. @api.multi
  98. def get_msgids(self, connection):
  99. '''Return imap ids of messages to process'''
  100. return connection.search(None, 'UNDELETED')
  101. @api.multi
  102. def apply_matching(self, connection, folder, msgid, match_algorithm):
  103. '''Return ids of objects matched'''
  104. matched_object_ids = []
  105. for this in self:
  106. result, msgdata = connection.fetch(msgid, '(RFC822)')
  107. if result != 'OK':
  108. _logger.error(
  109. 'Could not fetch %s in %s on %s',
  110. msgid, folder.path, this.server)
  111. continue
  112. mail_message = self.env['mail.thread'].message_parse(
  113. msgdata[0][1], save_original=this.original)
  114. if self.env['mail.message'].search(
  115. [('message_id', '=', mail_message['message_id'])]):
  116. continue
  117. found_ids = match_algorithm.search_matches(
  118. self.env.cr, self.env.uid, folder, mail_message, msgdata[0][1])
  119. if found_ids and (len(found_ids) == 1 or
  120. folder.match_first):
  121. try:
  122. self.env.cr.execute('savepoint apply_matching')
  123. match_algorithm.handle_match(
  124. self.env.cr, self.env.uid, connection,
  125. found_ids[0], folder, mail_message,
  126. msgdata[0][1], msgid, self.env.context)
  127. self.env.cr.execute('release savepoint apply_matching')
  128. matched_object_ids += found_ids[:1]
  129. except Exception:
  130. self.env.cr.execute('rollback to savepoint apply_matching')
  131. _logger.exception(
  132. "Failed to fetch mail %s from %s", msgid, this.name)
  133. elif folder.flag_nonmatching:
  134. connection.store(msgid, '+FLAGS', '\\FLAGGED')
  135. return matched_object_ids
  136. @api.multi
  137. def attach_mail(self, connection, object_id, folder, mail_message, msgid):
  138. '''Return ids of messages created'''
  139. mail_message_ids = []
  140. for this in self:
  141. partner_id = None
  142. if folder.model_id.model == 'res.partner':
  143. partner_id = object_id
  144. if 'partner_id' in self.env[folder.model_id.model]._columns:
  145. partner_id = self.env[folder.model_id.model].browse(object_id)\
  146. .partner_id.id
  147. attachments = []
  148. if this.attach and mail_message.get('attachments'):
  149. for attachment in mail_message['attachments']:
  150. fname, fcontent = attachment
  151. if isinstance(fcontent, unicode):
  152. fcontent = fcontent.encode('utf-8')
  153. data_attach = {
  154. 'name': fname,
  155. 'datas': base64.b64encode(str(fcontent)),
  156. 'datas_fname': fname,
  157. 'description': _('Mail attachment'),
  158. 'res_model': folder.model_id.model,
  159. 'res_id': object_id,
  160. }
  161. attachments.append(
  162. self.env['ir.attachment'].create(data_attach))
  163. mail_message_ids.append(
  164. self.env['mail.message'].create({
  165. 'author_id': partner_id,
  166. 'model': folder.model_id.model,
  167. 'res_id': object_id,
  168. 'type': 'email',
  169. 'body': mail_message.get('body'),
  170. 'subject': mail_message.get('subject'),
  171. 'email_from': mail_message.get('from'),
  172. 'date': mail_message.get('date'),
  173. 'message_id': mail_message.get('message_id'),
  174. 'attachment_ids': [(6, 0, [a.id for a in attachments])],
  175. }))
  176. if folder.delete_matching:
  177. connection.store(msgid, '+FLAGS', '\\DELETED')
  178. return mail_message_ids
  179. def button_confirm_login(self, cr, uid, ids, context=None):
  180. retval = super(fetchmail_server, self).button_confirm_login(
  181. cr, uid, ids, context)
  182. for this in self.browse(cr, uid, ids, context):
  183. this.write({'state': 'draft'})
  184. connection = this.connect()
  185. connection.select()
  186. for folder in this.folder_ids.filtered('active'):
  187. try:
  188. folder_context = safe_eval(folder.context or '{}')
  189. except Exception, e:
  190. raise exceptions.ValidationError(
  191. _('Invalid context "%s": %s') % (folder.context, e))
  192. if not isinstance(folder_context, dict):
  193. raise exceptions.ValidationError(
  194. _('Context "%s" is not a dictionary.') %
  195. folder.context)
  196. if connection.select(folder.path)[0] != 'OK':
  197. raise exceptions.ValidationError(
  198. _('Mailbox %s not found!') % folder.path)
  199. connection.close()
  200. this.write({'state': 'done'})
  201. return retval
  202. def fields_view_get(self, cr, user, view_id=None, view_type='form',
  203. context=None, toolbar=False, submenu=False):
  204. result = super(fetchmail_server, self).fields_view_get(
  205. cr, user, view_id, view_type, context, toolbar, submenu)
  206. if view_type == 'form':
  207. view = etree.fromstring(
  208. result['fields']['folder_ids']['views']['form']['arch'])
  209. modifiers = {}
  210. docstr = ''
  211. for algorithm in self.pool['fetchmail.server.folder']\
  212. ._get_match_algorithms().itervalues():
  213. for modifier in ['required', 'readonly']:
  214. for field in getattr(algorithm, modifier + '_fields'):
  215. modifiers.setdefault(field, {})
  216. modifiers[field].setdefault(modifier, [])
  217. if modifiers[field][modifier]:
  218. modifiers[field][modifier].insert(0, '|')
  219. modifiers[field][modifier].append(
  220. ("match_algorithm", "==", algorithm.__name__))
  221. docstr += _(algorithm.name) + '\n' + _(algorithm.__doc__) + \
  222. '\n\n'
  223. for field in view.xpath('//field'):
  224. if field.tag == 'field' and field.get('name') in modifiers:
  225. field.set(
  226. 'modifiers',
  227. simplejson.dumps(
  228. dict(
  229. simplejson.loads(field.attrib['modifiers']),
  230. **modifiers[field.attrib['name']]
  231. )
  232. ),
  233. )
  234. if (field.tag == 'field' and
  235. field.get('name') == 'match_algorithm'):
  236. field.set('help', docstr)
  237. result['fields']['folder_ids']['views']['form']['arch'] = \
  238. etree.tostring(view)
  239. return result