|
|
|
@ -20,17 +20,20 @@ |
|
|
|
|
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
A simple archiver which copies each message into a folder for the current |
|
|
|
|
month. |
|
|
|
|
A simple archiver which copies each message into a maildir folder for the |
|
|
|
|
current year and month. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
from contextlib import suppress |
|
|
|
|
from datetime import timedelta |
|
|
|
|
from email.utils import parsedate |
|
|
|
|
import gzip |
|
|
|
|
import logging |
|
|
|
|
from mailbox import Mailbox |
|
|
|
|
import os |
|
|
|
|
import time |
|
|
|
|
|
|
|
|
|
from flufl.lock import Lock |
|
|
|
|
from mailbox import Maildir |
|
|
|
|
from mailman.config import config |
|
|
|
|
from mailman.interfaces.archiver import IArchiver |
|
|
|
|
from public import public |
|
|
|
@ -44,7 +47,7 @@ log = logging.getLogger('mailman.archiver') |
|
|
|
|
@implementer(IArchiver) |
|
|
|
|
class MonthlyArchiver: |
|
|
|
|
""" |
|
|
|
|
A simple archiver which copies each message into a folder for the |
|
|
|
|
A simple archiver which copies each message into a maildir for the |
|
|
|
|
current month. It is adapted from the `Prototype` archiver. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
@ -67,8 +70,8 @@ class MonthlyArchiver: |
|
|
|
|
def archive_message(mlist, message): |
|
|
|
|
"""See `IArchiver`. |
|
|
|
|
|
|
|
|
|
This archiver saves messages into a folder for the current year and |
|
|
|
|
month, e.g. '2021-April'. |
|
|
|
|
This archiver saves messages into a maildir for the current year and |
|
|
|
|
month, e.g. '2021/April'. |
|
|
|
|
|
|
|
|
|
:type mlist: mailman.model.listmanager.ListManager |
|
|
|
|
:type message: mailman.email.message.Message |
|
|
|
@ -81,24 +84,35 @@ class MonthlyArchiver: |
|
|
|
|
log.warning('Could not extract date from message %s, using ' |
|
|
|
|
'local time instead' % message_id) |
|
|
|
|
date = time.localtime() |
|
|
|
|
year_and_month = time.strftime('%Y-%B', date) |
|
|
|
|
year = time.strftime('%Y') |
|
|
|
|
month = time.strftime('%B') |
|
|
|
|
|
|
|
|
|
archive_dir = os.path.join( |
|
|
|
|
config.ARCHIVE_DIR, |
|
|
|
|
MonthlyArchiver.name, |
|
|
|
|
mlist.fqdn_listname, |
|
|
|
|
year_and_month |
|
|
|
|
year, |
|
|
|
|
month |
|
|
|
|
) |
|
|
|
|
with suppress(FileExistsError): |
|
|
|
|
os.makedirs(archive_dir, 0o775) |
|
|
|
|
for subdir in ['cur', 'new', 'tmp']: |
|
|
|
|
with suppress(FileExistsError): |
|
|
|
|
os.makedirs(os.path.join(archive_dir, subdir), 0o775) |
|
|
|
|
maildir = Maildir(archive_dir, create=True) |
|
|
|
|
|
|
|
|
|
# maildir.add() is not thread-safe |
|
|
|
|
lock_file = os.path.join( |
|
|
|
|
config.LOCK_DIR, '{0}-maildir.lock'.format(mlist.fqdn_listname)) |
|
|
|
|
lock = Lock(lock_file) |
|
|
|
|
try: |
|
|
|
|
lock.lock(timeout=timedelta(seconds=3)) |
|
|
|
|
filename = maildir.add(message) |
|
|
|
|
finally: |
|
|
|
|
lock.unlock(unconditionally=True) |
|
|
|
|
# If we fail to acquire the lock, let the error propagate up |
|
|
|
|
|
|
|
|
|
# Just use the current timestamp as the file name |
|
|
|
|
filename = str(round(time.time(), 3)) + '.mail.gz' |
|
|
|
|
dst = os.path.join(archive_dir, filename) |
|
|
|
|
log.info('MonthlyArchiver archived message %s to %s' % |
|
|
|
|
(message_id, dst)) |
|
|
|
|
# gzip the file to try to save some disk space |
|
|
|
|
with gzip.open(dst, 'wb') as fo: |
|
|
|
|
fo.write(message.as_bytes()) |
|
|
|
|
(message_id, filename)) |
|
|
|
|
|
|
|
|
|
return None |
|
|
|
|