#
##############################################################################
+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: