Alexandre Fayolle
9 years ago
4 changed files with 277 additions and 0 deletions
-
1mail_cleanup/__init__.py
-
88mail_cleanup/__openerp__.py
-
166mail_cleanup/mail_cleanup.py
-
22mail_cleanup/mail_view.xml
@ -0,0 +1 @@ |
|||||
|
from . import mail_cleanup |
@ -0,0 +1,88 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Author: Matthieu Dietrich |
||||
|
# Copyright 2015 Camptocamp SA |
||||
|
# |
||||
|
# This program is free software: you can redistribute it and/or modify |
||||
|
# it under the terms of the GNU Affero General Public License as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################## |
||||
|
|
||||
|
{ |
||||
|
'name': 'Mail cleanup', |
||||
|
'version': '0.1', |
||||
|
'category': 'Tools', |
||||
|
'author': "Camptocamp,Odoo Community Association (OCA)", |
||||
|
'summary': 'Clean up mails regularly', |
||||
|
'description': """ |
||||
|
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
Mail cleanup |
||||
|
=========== |
||||
|
|
||||
|
This module allows to mark e-mails older than x days as read |
||||
|
and optionally move them on IMAP servers, just before fetching them. |
||||
|
|
||||
|
Since the main "mail" module does not mark unroutable e-mails as read, |
||||
|
this means that if junk mail arrives in the catch-all address without |
||||
|
any default route, fetching newer e-mails will happen after re-parsing |
||||
|
those unroutable e-mails. |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
|
||||
|
This module depends on ``mail_environment`` in order to add "expiration dates" |
||||
|
per server. |
||||
|
|
||||
|
Example of a configuration file (add those values to your server):: |
||||
|
|
||||
|
[incoming_mail.openerp_pop_mail1] |
||||
|
cleanup_days = 30 # default value |
||||
|
cleanup_folder = NotParsed # optional parameter |
||||
|
|
||||
|
Known issues / Roadmap |
||||
|
====================== |
||||
|
|
||||
|
* None |
||||
|
|
||||
|
Credits |
||||
|
======= |
||||
|
|
||||
|
Contributors |
||||
|
------------ |
||||
|
|
||||
|
* Matthieu Dietrich <matthieu.dietrich@camptocamp.com> |
||||
|
|
||||
|
Maintainer |
||||
|
---------- |
||||
|
|
||||
|
.. image:: https://odoo-community.org/logo.png |
||||
|
:alt: Odoo Community Association |
||||
|
:target: https://odoo-community.org |
||||
|
|
||||
|
This module is maintained by the OCA. |
||||
|
|
||||
|
OCA, or the Odoo Community Association, is a nonprofit organization whose |
||||
|
mission is to support the collaborative development of Odoo features and |
||||
|
promote its widespread use. |
||||
|
|
||||
|
To contribute to this module, please visit http://odoo-community.org. |
||||
|
""", |
||||
|
'license': 'AGPL-3', |
||||
|
'website': 'http://openerp.camptocamp.com', |
||||
|
'depends': ['mail_environment'], |
||||
|
'data': ['mail_view.xml'], |
||||
|
'installable': True, |
||||
|
} |
@ -0,0 +1,166 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Author: Matthieu Dietrich |
||||
|
# Copyright 2015 Camptocamp SA |
||||
|
# |
||||
|
# This program is free software: you can redistribute it and/or modify |
||||
|
# it under the terms of the GNU Affero General Public License as |
||||
|
# published by the Free Software Foundation, either version 3 of the |
||||
|
# License, or (at your option) any later version. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU Affero General Public License for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU Affero General Public License |
||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################## |
||||
|
|
||||
|
import logging |
||||
|
from openerp.osv import orm, fields |
||||
|
from dateutil import relativedelta |
||||
|
from datetime import datetime |
||||
|
|
||||
|
from server_environment import serv_config |
||||
|
|
||||
|
_logger = logging.getLogger(__name__) |
||||
|
|
||||
|
|
||||
|
class FetchmailServer(orm.Model): |
||||
|
"""Incoming POP/IMAP mail server account""" |
||||
|
_inherit = 'fetchmail.server' |
||||
|
|
||||
|
def _get_cleanup_conf(self, cursor, uid, ids, name, args, context=None): |
||||
|
""" |
||||
|
Return configuration |
||||
|
""" |
||||
|
res = {} |
||||
|
for fetchmail in self.browse(cursor, uid, ids): |
||||
|
global_section_name = 'incoming_mail' |
||||
|
|
||||
|
# default vals |
||||
|
config_vals = {'cleanup_days': False, |
||||
|
'purge_days': False, |
||||
|
'cleanup_folder': False} |
||||
|
if serv_config.has_section(global_section_name): |
||||
|
config_vals.update(serv_config.items(global_section_name)) |
||||
|
|
||||
|
custom_section_name = '.'.join((global_section_name, |
||||
|
fetchmail.name)) |
||||
|
if serv_config.has_section(custom_section_name): |
||||
|
config_vals.update(serv_config.items(custom_section_name)) |
||||
|
|
||||
|
# convert string values to integer |
||||
|
if config_vals['cleanup_days']: |
||||
|
config_vals['cleanup_days'] = int(config_vals['cleanup_days']) |
||||
|
if config_vals['purge_days']: |
||||
|
config_vals['purge_days'] = int(config_vals['purge_days']) |
||||
|
res[fetchmail.id] = config_vals |
||||
|
return res |
||||
|
|
||||
|
_columns = { |
||||
|
'cleanup_days': fields.function( |
||||
|
_get_cleanup_conf, |
||||
|
method=True, |
||||
|
string='Expiration days', |
||||
|
type="integer", |
||||
|
multi='outgoing_mail_config', |
||||
|
help="Number of days before marking an e-mail as read"), |
||||
|
'cleanup_folder': fields.function( |
||||
|
_get_cleanup_conf, |
||||
|
method=True, |
||||
|
string='Expiration folder', |
||||
|
type="char", |
||||
|
multi='outgoing_mail_config', |
||||
|
help="Folder where an e-mail marked as read will be moved."), |
||||
|
'purge_days': fields.function( |
||||
|
_get_cleanup_conf, |
||||
|
method=True, |
||||
|
string='Deletion days', |
||||
|
type="integer", |
||||
|
multi='outgoing_mail_config', |
||||
|
help="Number of days before removing an e-mail"), |
||||
|
} |
||||
|
|
||||
|
def _cleanup_fetchmail_server(self, server, imap_server): |
||||
|
count, failed = 0, 0 |
||||
|
expiration_date = (datetime.now() + relativedelta.relativedelta( |
||||
|
days=-(server.cleanup_days))).strftime('%d-%b-%Y') |
||||
|
search_text = '(UNSEEN BEFORE %s)' % expiration_date |
||||
|
imap_server.select() |
||||
|
result, data = imap_server.search(None, search_text) |
||||
|
for num in data[0].split(): |
||||
|
try: |
||||
|
# Mark message as read |
||||
|
imap_server.store(num, '+FLAGS', '\\Seen') |
||||
|
if server.cleanup_folder: |
||||
|
# To move a message, you have to COPY |
||||
|
# then DELETE the message |
||||
|
result = imap_server.copy(num, server.cleanup_folder) |
||||
|
if result[0] == 'OK': |
||||
|
imap_server.store(num, '+FLAGS', '\\Deleted') |
||||
|
except Exception: |
||||
|
_logger.exception('Failed to cleanup mail from %s server %s.', |
||||
|
server.type, server.name) |
||||
|
failed += 1 |
||||
|
count += 1 |
||||
|
_logger.info("Marked %d email(s) as read on %s server %s; " |
||||
|
"%d succeeded, %d failed.", count, server.type, |
||||
|
server.name, (count - failed), failed) |
||||
|
|
||||
|
def _purge_fetchmail_server(self, server, imap_server): |
||||
|
# Purging e-mails older than the purge date, if available |
||||
|
count, failed = 0, 0 |
||||
|
purge_date = (datetime.now() + relativedelta.relativedelta( |
||||
|
days=-(server.purge_days))).strftime('%d-%b-%Y') |
||||
|
search_text = '(BEFORE %s)' % purge_date |
||||
|
imap_server.select() |
||||
|
result, data = imap_server.search(None, search_text) |
||||
|
for num in data[0].split(): |
||||
|
try: |
||||
|
# Delete message |
||||
|
imap_server.store(num, '+FLAGS', '\\Deleted') |
||||
|
except Exception: |
||||
|
_logger.exception('Failed to remove mail from %s server %s.', |
||||
|
server.type, server.name) |
||||
|
failed += 1 |
||||
|
count += 1 |
||||
|
_logger.info("Removed %d email(s) on %s server %s; " |
||||
|
"%d succeeded, %d failed.", count, server.type, |
||||
|
server.name, (count - failed), failed) |
||||
|
|
||||
|
def fetch_mail(self, cr, uid, ids, context=None): |
||||
|
""" Called before the fetch, in order to clean up |
||||
|
right before retrieving emails. """ |
||||
|
if context is None: |
||||
|
context = {} |
||||
|
context['fetchmail_cron_running'] = True |
||||
|
for server in self.browse(cr, uid, ids, context=context): |
||||
|
_logger.info('start cleaning up emails on %s server %s', |
||||
|
server.type, server.name) |
||||
|
context.update({'fetchmail_server_id': server.id, |
||||
|
'server_type': server.type}) |
||||
|
imap_server = False |
||||
|
if server.type == 'imap': |
||||
|
try: |
||||
|
imap_server = server.connect() |
||||
|
if server.cleanup_days > 0: |
||||
|
self._cleanup_fetchmail_server(server, imap_server) |
||||
|
if server.purge_days > 0: |
||||
|
self._purge_fetchmail_server(server, imap_server) |
||||
|
# Do the final cleanup: delete all messages |
||||
|
# flagged as deleted |
||||
|
imap_server.expunge() |
||||
|
except Exception: |
||||
|
_logger.exception("General failure when trying to cleanup " |
||||
|
"mail from %s server %s.", |
||||
|
server.type, server.name) |
||||
|
finally: |
||||
|
if imap_server: |
||||
|
imap_server.close() |
||||
|
imap_server.logout() |
||||
|
return super(FetchmailServer, self).fetch_mail(cr, uid, ids, |
||||
|
context=context) |
@ -0,0 +1,22 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<openerp> |
||||
|
<data> |
||||
|
<record model="ir.ui.view" id="inherit_fetchmail_cleanup"> |
||||
|
<!-- must be unique in this module. --> |
||||
|
<field name="name">inherit_fetchmail_for cleanup</field> |
||||
|
<field name="model">fetchmail.server</field> |
||||
|
<!--parent python entity --> |
||||
|
<field name="inherit_id" ref="mail_environment.inherit_fetchmail"/> |
||||
|
<!-- modulename.view --> |
||||
|
<field name="arch" type="xml"> |
||||
|
<field name="is_ssl" position="after"> |
||||
|
<field name="cleanup_days"/> |
||||
|
<field name="purge_days"/> |
||||
|
</field> |
||||
|
<field name="password" position="after"> |
||||
|
<field name="cleanup_folder"/> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
</data> |
||||
|
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue