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.

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