[FIX] hr_payroll: skip missing structures in get_all_structures for hr.contracts...
[odoo/odoo.git] / addons / fetchmail / fetchmail.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 import logging
23 import time
24 from imaplib import IMAP4
25 from imaplib import IMAP4_SSL
26 from poplib import POP3
27 from poplib import POP3_SSL
28 try:
29     import cStringIO as StringIO
30 except ImportError:
31     import StringIO
32
33 import zipfile
34 import base64
35 from openerp import addons
36
37 from openerp import netsvc
38 from openerp.osv import fields, osv
39 from openerp import tools
40 from openerp.tools.translate import _
41
42 _logger = logging.getLogger(__name__)
43
44 class fetchmail_server(osv.osv):
45     """Incoming POP/IMAP mail server account"""
46     _name = 'fetchmail.server'
47     _description = "POP/IMAP Server"
48     _order = 'priority'
49
50     _columns = {
51         'name':fields.char('Name', size=256, required=True, readonly=False),
52         'active':fields.boolean('Active', required=False),
53         'state':fields.selection([
54             ('draft', 'Not Confirmed'),
55             ('done', 'Confirmed'),
56         ], 'Status', select=True, readonly=True),
57         'server' : fields.char('Server Name', size=256, readonly=True, help="Hostname or IP of the mail server", states={'draft':[('readonly', False)]}),
58         'port' : fields.integer('Port', readonly=True, states={'draft':[('readonly', False)]}),
59         'type':fields.selection([
60             ('pop', 'POP Server'),
61             ('imap', 'IMAP Server'),
62             ('local', 'Local Server'),
63         ], 'Server Type', select=True, required=True, readonly=False),
64         'is_ssl':fields.boolean('SSL/TLS', help="Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)"),
65         'attach':fields.boolean('Keep Attachments', help="Whether attachments should be downloaded. "
66                                                          "If not enabled, incoming emails will be stripped of any attachments before being processed"),
67         'original':fields.boolean('Keep Original', help="Whether a full original copy of each email should be kept for reference"
68                                                         "and attached to each processed message. This will usually double the size of your message database."),
69         'date': fields.datetime('Last Fetch Date', readonly=True),
70         'user' : fields.char('Username', size=256, readonly=True, states={'draft':[('readonly', False)]}),
71         'password' : fields.char('Password', size=1024, readonly=True, states={'draft':[('readonly', False)]}),
72         'action_id':fields.many2one('ir.actions.server', 'Server Action', help="Optional custom server action to trigger for each incoming mail, "
73                                                                                "on the record that was created or updated by this mail"),
74         'object_id': fields.many2one('ir.model', "Create a New Record", help="Process each incoming mail as part of a conversation "
75                                                                                              "corresponding to this document type. This will create "
76                                                                                              "new documents for new conversations, or attach follow-up "
77                                                                                              "emails to the existing conversations (documents)."),
78         'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Defines the order of processing, "
79                                                                                                                   "lower values mean higher priority"),
80         'message_ids': fields.one2many('mail.mail', 'fetchmail_server_id', 'Messages', readonly=True),
81         'configuration' : fields.text('Configuration', readonly=True),
82         'script' : fields.char('Script', readonly=True, size=64),
83     }
84     _defaults = {
85         'state': "draft",
86         'type': "pop",
87         'active': True,
88         'priority': 5,
89         'attach': True,
90         'script': '/mail/static/scripts/openerp_mailgate.py',
91     }
92
93     def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False, object_id=False):
94         port = 0
95         values = {}
96         if server_type == 'pop':
97             port = ssl and 995 or 110
98         elif server_type == 'imap':
99             port = ssl and 993 or 143
100         else:
101             values['server'] = ''
102         values['port'] = port
103
104         conf = {
105             'dbname' : cr.dbname,
106             'uid' : uid,
107             'model' : 'MODELNAME',
108         }
109         if object_id:
110             m = self.pool.get('ir.model')
111             r = m.read(cr,uid,[object_id],['model'])
112             conf['model']=r[0]['model']
113         values['configuration'] = """Use the below script with the following command line options with your Mail Transport Agent (MTA)
114
115 openerp_mailgate.py --host=HOSTNAME --port=PORT -u %(uid)d -p PASSWORD -d %(dbname)s
116
117 Example configuration for the postfix mta running locally:
118
119 /etc/postfix/virtual_aliases:
120 @youdomain openerp_mailgate@localhost
121
122 /etc/aliases:
123 openerp_mailgate: "|/path/to/openerp-mailgate.py --host=localhost -u %(uid)d -p PASSWORD -d %(dbname)s"
124
125 """ % conf
126
127         return {'value':values}
128
129     def set_draft(self, cr, uid, ids, context=None):
130         self.write(cr, uid, ids , {'state':'draft'})
131         return True
132
133     def connect(self, cr, uid, server_id, context=None):
134         if isinstance(server_id, (list,tuple)):
135             server_id = server_id[0]
136         server = self.browse(cr, uid, server_id, context)
137         if server.type == 'imap':
138             if server.is_ssl:
139                 connection = IMAP4_SSL(server.server, int(server.port))
140             else:
141                 connection = IMAP4(server.server, int(server.port))
142             connection.login(server.user, server.password)
143         elif server.type == 'pop':
144             if server.is_ssl:
145                 connection = POP3_SSL(server.server, int(server.port))
146             else:
147                 connection = POP3(server.server, int(server.port))
148             #TODO: use this to remove only unread messages
149             #connection.user("recent:"+server.user)
150             connection.user(server.user)
151             connection.pass_(server.password)
152         return connection
153
154     def button_confirm_login(self, cr, uid, ids, context=None):
155         if context is None:
156             context = {}
157         for server in self.browse(cr, uid, ids, context=context):
158             try:
159                 connection = server.connect()
160                 server.write({'state':'done'})
161             except Exception, e:
162                 _logger.exception("Failed to connect to %s server %s.", server.type, server.name)
163                 raise osv.except_osv(_("Connection test failed!"), _("Here is what we got instead:\n %s.") % tools.ustr(e))
164             finally:
165                 try:
166                     if connection:
167                         if server.type == 'imap':
168                             connection.close()
169                         elif server.type == 'pop':
170                             connection.quit()
171                 except Exception:
172                     # ignored, just a consequence of the previous exception
173                     pass
174         return True
175
176     def _fetch_mails(self, cr, uid, ids=False, context=None):
177         if not ids:
178             ids = self.search(cr, uid, [('state','=','done'),('type','in',['pop','imap'])])
179         return self.fetch_mail(cr, uid, ids, context=context)
180
181     def fetch_mail(self, cr, uid, ids, context=None):
182         """WARNING: meant for cron usage only - will commit() after each email!"""
183         if context is None:
184             context = {}
185         context['fetchmail_cron_running'] = True
186         mail_thread = self.pool.get('mail.thread')
187         action_pool = self.pool.get('ir.actions.server')
188         for server in self.browse(cr, uid, ids, context=context):
189             _logger.info('start checking for new emails on %s server %s', server.type, server.name)
190             context.update({'fetchmail_server_id': server.id, 'server_type': server.type})
191             count = 0
192             imap_server = False
193             pop_server = False
194             if server.type == 'imap':
195                 try:
196                     imap_server = server.connect()
197                     imap_server.select()
198                     result, data = imap_server.search(None, '(UNSEEN)')
199                     for num in data[0].split():
200                         result, data = imap_server.fetch(num, '(RFC822)')
201                         res_id = mail_thread.message_process(cr, uid, server.object_id.model, 
202                                                              data[0][1],
203                                                              save_original=server.original,
204                                                              strip_attachments=(not server.attach),
205                                                              context=context)
206                         if res_id and server.action_id:
207                             action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id], 'active_model': context.get("thread_model", server.object_id.model)})
208                             imap_server.store(num, '+FLAGS', '\\Seen')
209                             cr.commit()
210                         count += 1
211                     _logger.info("fetched/processed %s email(s) on %s server %s", count, server.type, server.name)
212                 except Exception:
213                     _logger.exception("Failed to fetch mail from %s server %s.", server.type, server.name)
214                 finally:
215                     if imap_server:
216                         imap_server.close()
217                         imap_server.logout()
218             elif server.type == 'pop':
219                 try:
220                     pop_server = server.connect()
221                     (numMsgs, totalSize) = pop_server.stat()
222                     pop_server.list()
223                     for num in range(1, numMsgs + 1):
224                         (header, msges, octets) = pop_server.retr(num)
225                         msg = '\n'.join(msges)
226                         res_id = mail_thread.message_process(cr, uid, server.object_id.model,
227                                                              msg,
228                                                              save_original=server.original,
229                                                              strip_attachments=(not server.attach),
230                                                              context=context)
231                         if res_id and server.action_id:
232                             action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id], 'active_model': context.get("thread_model", server.object_id.model)})
233                         pop_server.dele(num)
234                         cr.commit()
235                     _logger.info("fetched/processed %s email(s) on %s server %s", numMsgs, server.type, server.name)
236                 except Exception:
237                     _logger.exception("Failed to fetch mail from %s server %s.", server.type, server.name)
238                 finally:
239                     if pop_server:
240                         pop_server.quit()
241             server.write({'date': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)})
242         return True
243
244     def cron_update(self, cr, uid, context=None):
245         if context is None:
246             context = {}
247         if not context.get('fetchmail_cron_running'):
248             # Enabled/Disable cron based on the number of 'done' server of type pop or imap
249             ids = self.search(cr, uid, [('state','=','done'),('type','in',['pop','imap'])])
250             try:
251                 cron_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'fetchmail', 'ir_cron_mail_gateway_action')[1]
252                 self.pool.get('ir.cron').write(cr, 1, [cron_id], {'active': bool(ids)})
253             except ValueError:
254                 # Nevermind if default cron cannot be found
255                 pass
256
257     def create(self, cr, uid, values, context=None):
258         res = super(fetchmail_server, self).create(cr, uid, values, context=context)
259         self.cron_update(cr, uid, context=context)
260         return res
261
262     def write(self, cr, uid, ids, values, context=None):
263         res = super(fetchmail_server, self).write(cr, uid, ids, values, context=context)
264         self.cron_update(cr, uid, context=context)
265         return res
266
267 class mail_mail(osv.osv):
268     _inherit = "mail.mail"
269     _columns = {
270         'fetchmail_server_id': fields.many2one('fetchmail.server', "Inbound Mail Server",
271                                                readonly=True,
272                                                select=True,
273                                                oldname='server_id'),
274     }
275
276     def create(self, cr, uid, values, context=None):
277         if context is None:
278             context = {}
279         fetchmail_server_id = context.get('fetchmail_server_id')
280         if fetchmail_server_id:
281             values['fetchmail_server_id'] = fetchmail_server_id
282         res = super(mail_mail, self).create(cr, uid, values, context=context)
283         return res
284
285     def write(self, cr, uid, ids, values, context=None):
286         if context is None:
287             context = {}
288         fetchmail_server_id = context.get('fetchmail_server_id')
289         if fetchmail_server_id:
290             values['fetchmail_server_id'] = fetchmail_server_id
291         res = super(mail_mail, self).write(cr, uid, ids, values, context=context)
292         return res
293
294
295 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: