[FIX] report_webkit: Use the parent of the root_path if we use openerp on Windows
[odoo/odoo.git] / addons / fetchmail / fetchmail.py
index 4b2b44a..d1e23f4 100644 (file)
 #
 ##############################################################################
 
+import logging
 import time
-
 from imaplib import IMAP4
 from imaplib import IMAP4_SSL
 from poplib import POP3
 from poplib import POP3_SSL
+try:
+    import cStringIO as StringIO
+except ImportError:
+    import StringIO
+
+import zipfile
+import base64
+import addons
 
 import netsvc
 from osv import osv, fields
 import tools
+from tools.translate import _
 
-logger = netsvc.Logger()
-
+logger = logging.getLogger('fetchmail')
 
-class email_server(osv.osv):
-
-    _name = 'email.server'
+class fetchmail_server(osv.osv):
+    """Incoming POP/IMAP mail server account"""
+    _name = 'fetchmail.server'
     _description = "POP/IMAP Server"
+    _order = 'priority'
 
     _columns = {
         'name':fields.char('Name', size=256, required=True, readonly=False),
         'active':fields.boolean('Active', required=False),
         'state':fields.selection([
             ('draft', 'Not Confirmed'),
-            ('waiting', 'Waiting for Verification'),
             ('done', 'Confirmed'),
         ], 'State', select=True, readonly=True),
-        'server' : fields.char('Server', size=256, required=True, readonly=True, states={'draft':[('readonly', False)]}),
-        'port' : fields.integer('Port', required=True, readonly=True, states={'draft':[('readonly', False)]}),
+        'server' : fields.char('Server Name', size=256, readonly=True, help="Hostname or IP of the mail server", states={'draft':[('readonly', False)]}),
+        'port' : fields.integer('Port', readonly=True, states={'draft':[('readonly', False)]}),
         'type':fields.selection([
             ('pop', 'POP Server'),
             ('imap', 'IMAP Server'),
-        ], 'Server Type', select=True, readonly=False),
-        'is_ssl':fields.boolean('SSL ?', required=False),
-        'attach':fields.boolean('Add Attachments ?', required=False, help="Fetches mail with attachments if true."),
-        'date': fields.date('Date', readonly=True, states={'draft':[('readonly', False)]}),
-        'user' : fields.char('User Name', size=256, required=True, readonly=True, states={'draft':[('readonly', False)]}),
-        'password' : fields.char('Password', size=1024, invisible=True, required=True, readonly=True, states={'draft':[('readonly', False)]}),
-        'note': fields.text('Description'),
-        'action_id':fields.many2one('ir.actions.server', 'Email Server Action', required=False, domain="[('state','=','email')]", help="An Email Server Action. It will be run whenever an e-mail is fetched from server."),
-        'object_id': fields.many2one('ir.model', "Model", required=True, help="OpenObject Model. Generates a record of this model.\nSelect Object with message_new attrbutes."),
-        'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Priority between 0 to 10, select define the order of Processing"),
-        'user_id':fields.many2one('res.users', 'User', required=False),
-        'message_ids': fields.one2many('email.message', 'server_id', 'Messages', readonly=True),
+            ('local', 'Local Server'),
+        ], 'Server Type', select=True, required=True, readonly=False),
+        'is_ssl':fields.boolean('SSL/TLS', help="Connections are encrypted with SSL/TLS through a dedicated port (default: IMAPS=993, POP3S=995)"),
+        'attach':fields.boolean('Keep Attachments', help="Whether attachments should be downloaded. "
+                                                         "If not enabled, incoming emails will be stripped of any attachments before being processed"),
+        'original':fields.boolean('Keep Original', help="Whether a full original copy of each email should be kept for reference"
+                                                        "and attached to each processed message. This will usually double the size of your message database."),
+        'date': fields.datetime('Last Fetch Date', readonly=True),
+        'user' : fields.char('Username', size=256, readonly=True, states={'draft':[('readonly', False)]}),
+        'password' : fields.char('Password', size=1024, readonly=True, states={'draft':[('readonly', False)]}),
+        'action_id':fields.many2one('ir.actions.server', 'Server Action', help="Optional custom server action to trigger for each incoming mail, "
+                                                                               "on the record that was created or updated by this mail"),
+        'object_id': fields.many2one('ir.model', "Create a New Record", required=True, help="Process each incoming mail as part of a conversation "
+                                                                                             "corresponding to this document type. This will create "
+                                                                                             "new documents for new conversations, or attach follow-up "
+                                                                                             "emails to the existing conversations (documents)."),
+        'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Defines the order of processing, "
+                                                                                                                  "lower values mean higher priority"),
+        'message_ids': fields.one2many('mail.message', 'fetchmail_server_id', 'Messages', readonly=True),
+        'configuration' : fields.text('Configuration'),
+        'script' : fields.char('Script', readonly=True, size=64),
     }
     _defaults = {
-        'state': lambda *a: "draft",
-        'active': lambda *a: True,
-        'priority': lambda *a: 5,
-        'date': lambda *a: time.strftime('%Y-%m-%d'),
-        'user_id': lambda self, cr, uid, ctx: uid,
+        'state': "draft",
+        'type': "pop",
+        'active': True,
+        'priority': 5,
+        'attach': True,
+        'script': '/mail/static/scripts/openerp_mailgate.py',
     }
 
-    def check_duplicate(self, cr, uid, ids, context=None):
-        # RFC *-* Why this limitation? why not in SQL constraint?
-        vals = self.read(cr, uid, ids, ['user', 'password'], context=context)[0]
-        cr.execute("select count(id) from email_server where user=%s and password=%s", (vals['user'], vals['password']))
-        res = cr.fetchone()
-        if res:
-            if res[0] > 1:
-                return False
-        return True
 
-    def check_model(self, cr, uid, ids, context = None):
-        if context is None:
-            context = {}
-        current_rec = self.read(cr, uid, ids, context)
-        if current_rec:
-            current_rec = current_rec[0]
-            model_name = self.pool.get('ir.model').browse(cr, uid, current_rec.get('object_id')[0]).model
-            model = self.pool.get(model_name)
-            if hasattr(model, 'message_new'):
-                return True
-        return False
-
-    _constraints = [
-        (check_duplicate, 'Warning! Can\'t have duplicate server configuration!', ['user', 'password']),
-        (check_model, 'Warning! Record for selected Model can not be created\nPlease choose valid Model', ['object_id'])
-    ]
-
-    def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False):
+    def onchange_server_type(self, cr, uid, ids, server_type=False, ssl=False, object_id=False):
         port = 0
+        values = {}
         if server_type == 'pop':
             port = ssl and 995 or 110
         elif server_type == 'imap':
             port = ssl and 993 or 143
-
-        return {'value':{'port':port}}
+        else:
+            values['server'] = ''
+        values['port'] = port
+
+        conf = {
+            'dbname' : cr.dbname,
+            'uid' : uid,
+            'model' : 'MODELNAME',
+        }
+        if object_id:
+            m = self.pool.get('ir.model')
+            r = m.read(cr,uid,[object_id],['model'])
+            conf['model']=r[0]['model']
+        values['configuration'] = """Use the below script with the following command line options with your Mail Transport Agent (MTA)
+
+openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOSTNAME --port=PORT 
+""" % conf
+
+        return {'value':values}
 
     def set_draft(self, cr, uid, ids, context=None):
         self.write(cr, uid, ids , {'state':'draft'})
         return True
 
+    def connect(self, cr, uid, server_id, context=None):
+        if isinstance(server_id, (list,tuple)):
+            server_id = server_id[0]
+        server = self.browse(cr, uid, server_id, context)
+        if server.type == 'imap':
+            if server.is_ssl:
+                connection = IMAP4_SSL(server.server, int(server.port))
+            else:
+                connection = IMAP4(server.server, int(server.port))
+            connection.login(server.user, server.password)
+        elif server.type == 'pop':
+            if server.is_ssl:
+                connection = POP3_SSL(server.server, int(server.port))
+            else:
+                connection = POP3(server.server, int(server.port))
+            #TODO: use this to remove only unread messages
+            #connection.user("recent:"+server.user)
+            connection.user(server.user)
+            connection.pass_(server.password)
+        return connection
+
     def button_confirm_login(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
         for server in self.browse(cr, uid, ids, context=context):
-            logger.notifyChannel(server.type, netsvc.LOG_INFO, 'fetchmail start checking for new emails on %s' % (server.name))
-            context.update({'server_id': server.id})
             try:
-                if server.type == 'imap':
-                    imap_server = None
-                    if server.is_ssl:
-                        imap_server = IMAP4_SSL(server.server, int(server.port))
-                    else:
-                        imap_server = IMAP4(server.server, int(server.port))
-
-                    imap_server.login(server.user, server.password)
-                    ret_server = imap_server
-
-                elif server.type == 'pop':
-                    pop_server = None
-                    if server.is_ssl:
-                        pop_server = POP3_SSL(server.server, int(server.port))
-                    else:
-                        pop_server = POP3(server.server, int(server.port))
-
-                    #TODO: use this to remove only unread messages
-                    #pop_server.user("recent:"+server.user)
-                    pop_server.user(server.user)
-                    pop_server.pass_(server.password)
-                    ret_server = pop_server
-
-                self.write(cr, uid, [server.id], {'state':'done'})
-                if context.get('get_server',False):
-                    return ret_server
+                connection = server.connect()
+                server.write({'state':'done'})
             except Exception, e:
-                logger.notifyChannel(server.type, netsvc.LOG_WARNING, '%s' % (e))
-        return True
-
-    def button_fetch_mail(self, cr, uid, ids, context=None):
-        self.fetch_mail(cr, uid, ids, context=context)
+                logger.exception("Failed to connect to %s server %s", server.type, server.name)
+                raise osv.except_osv(_("Connection test failed!"), _("Here is what we got instead:\n %s") % tools.ustr(e))
+            finally:
+                try:
+                    if connection:
+                        if server.type == 'imap':
+                            connection.close()
+                        elif server.type == 'pop':
+                            connection.quit()
+                except Exception:
+                    # ignored, just a consequence of the previous exception
+                    pass
         return True
 
     def _fetch_mails(self, cr, uid, ids=False, context=None):
         if not ids:
-            ids = self.search(cr, uid, [])
+            ids = self.search(cr, uid, [('state','=','done')])
         return self.fetch_mail(cr, uid, ids, context=context)
 
     def fetch_mail(self, cr, uid, ids, context=None):
+        """WARNING: meant for cron usage only - will commit() after each email!"""
         if context is None:
             context = {}
-        email_tool = self.pool.get('email.server.tools')
+        mail_thread = self.pool.get('mail.thread')
         action_pool = self.pool.get('ir.actions.server')
-        context.update({'get_server': True})
         for server in self.browse(cr, uid, ids, context=context):
+            logger.info('start checking for new emails on %s server %s', server.type, server.name)
+            context.update({'fetchmail_server_id': server.id, 'server_type': server.type})
             count = 0
-            user = server.user_id.id or uid
             if server.type == 'imap':
                 try:
-                    imap_server = self.button_confirm_login(cr, uid, [server.id], context=context)
+                    imap_server = server.connect()
                     imap_server.select()
                     result, data = imap_server.search(None, '(UNSEEN)')
                     for num in data[0].split():
                         result, data = imap_server.fetch(num, '(RFC822)')
-                        res_id = email_tool.process_email(cr, user, server.object_id.model, data[0][1], attach=server.attach, context=context)
+                        res_id = mail_thread.message_process(cr, uid, server.object_id.model, data[0][1],
+                                                             save_original=server.original,
+                                                             strip_attachments=(not server.attach),
+                                                             context=context)
                         if res_id and server.action_id:
-                            action_pool.run(cr, user, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]})
-
+                            action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]})
                             imap_server.store(num, '+FLAGS', '\\Seen')
+                            cr.commit()
                         count += 1
-                    logger.notifyChannel(server.type, netsvc.LOG_INFO, 'fetchmail fetch/process %s email(s) from %s' % (count, server.name))
-
+                    logger.info("fetched/processed %s email(s) on %s server %s", count, server.type, server.name)
                 except Exception, e:
-                    logger.notifyChannel(server.type, netsvc.LOG_WARNING, '%s' % (tools.ustr(e)))
+                    logger.exception("Failed to fetch mail from %s server %s", server.type, server.name)
                 finally:
                     if imap_server:
                         imap_server.close()
                         imap_server.logout()
             elif server.type == 'pop':
                 try:
-                    pop_server = self.button_confirm_login(cr, uid, [server.id], context=context)
+                    pop_server = server.connect()
                     (numMsgs, totalSize) = pop_server.stat()
                     pop_server.list()
                     for num in range(1, numMsgs + 1):
                         (header, msges, octets) = pop_server.retr(num)
                         msg = '\n'.join(msges)
-                        res_id = email_tool.process_email(cr, user, server.object_id.model, msg, attach=server.attach, context=context)
+                        res_id = mail_thread.message_process(cr, uid, server.object_id.model,
+                                                             msg,
+                                                             save_original=server.original,
+                                                             strip_attachments=(not server.attach),
+                                                             context=context)
                         if res_id and server.action_id:
-                            action_pool.run(cr, user, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]})
-
+                            action_pool.run(cr, uid, [server.action_id.id], {'active_id': res_id, 'active_ids':[res_id]})
                         pop_server.dele(num)
-                    logger.notifyChannel(server.type, netsvc.LOG_INFO, 'fetchmail fetch %s email(s) from %s' % (numMsgs, server.name))
+                        cr.commit()
+                    logger.info("fetched/processed %s email(s) on %s server %s", numMsgs, server.type, server.name)
                 except Exception, e:
-                    logger.notifyChannel(server.type, netsvc.LOG_WARNING, '%s' % (tools.ustr(e)))
+                    logger.exception("Failed to fetch mail from %s server %s", server.type, server.name)
                 finally:
                     if pop_server:
                         pop_server.quit()
+            server.write({'date': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)})
         return True
 
-email_server()
-
-class email_message(osv.osv):
-
-    _inherit = "email.message"
-
+class mail_message(osv.osv):
+    _inherit = "mail.message"
     _columns = {
-        'server_id': fields.many2one('email.server', "Mail Server", readonly=True, select=True),
+        'fetchmail_server_id': fields.many2one('fetchmail.server', "Inbound Mail Server",
+                                               readonly=True,
+                                               select=True,
+                                               oldname='server_id'),
     }
 
     def create(self, cr, uid, values, context=None):
         if context is None:
             context={}
-        server_id = context.get('server_id',False)
-        if server_id:
-            values['server_id'] = server_id
-        res = super(email_message,self).create(cr, uid, values, context=context)
+        fetchmail_server_id = context.get('fetchmail_server_id')
+        if fetchmail_server_id:
+            values['fetchmail_server_id'] = fetchmail_server_id
+        res = super(mail_message,self).create(cr, uid, values, context=context)
         return res
 
     def write(self, cr, uid, ids, values, context=None):
         if context is None:
             context={}
-        server_id = context.get('server_id',False)
-        if server_id:
-            values['server_id'] = server_id
-        res = super(email_message,self).write(cr, uid, ids, values, context=context)
+        fetchmail_server_id = context.get('fetchmail_server_id')
+        if fetchmail_server_id:
+            values['fetchmail_server_id'] = server_id
+        res = super(mail_message,self).write(cr, uid, ids, values, context=context)
         return res
 
-email_message()
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: