# Copyright (C) 2021 Max Erenberg # This file is adapted from GNU Mailman. The original copyright notice # is preserved below. # # Copyright (C) 2008-2019 by the Free Software Foundation, Inc. # # This file is part of GNU Mailman. # # GNU Mailman is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # GNU Mailman 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 General Public License for # more details. # # You should have received a copy of the GNU General Public License along with # GNU Mailman. If not, see . """ 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 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 from zope.interface import implementer log = logging.getLogger('mailman.archiver') @public @implementer(IArchiver) class MonthlyArchiver: """ A simple archiver which copies each message into a maildir for the current month. It is adapted from the `Prototype` archiver. """ name = 'monthly_archiver' is_enabled = False @staticmethod def list_url(mlist): """See `IArchiver`.""" # This archiver is not web-accessible, therefore no URL is returned. return None @staticmethod def permalink(mlist, msg): """See `IArchiver`.""" # This archiver is not web-accessible, therefore no URL is returned. return None @staticmethod def archive_message(mlist, message): """See `IArchiver`. This archiver saves messages into a maildir for the current year and month, e.g. '2021/April'. :type mlist: mailman.interfaces.mailinglist.IMailingList :type message: mailman.email.message.Message """ message_id = message.get('message-id') date_secs = parsedate(message.get('date')) if date_secs is not None: date = time.localtime(time.mktime(date_secs)) else: log.warning('Could not extract date from message %s, using ' 'local time instead' % message_id) date = time.localtime() year = time.strftime('%Y') month = time.strftime('%B') archive_dir = os.path.join( config.ARCHIVE_DIR, MonthlyArchiver.name, mlist.fqdn_listname, 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 log.info('MonthlyArchiver archived message %s to %s' % (message_id, filename)) return None