commit e824785e0a160356c20b3c261f8439caeef24c25 Author: Max Erenberg Date: Tue Apr 6 02:32:35 2021 -0400 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f5ffc5 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Extra Mailman Archivers + +This contains some extra archivers to be used with Mailman 3. +Currently there is only one archiver, `MonthlyArchiver`, which simply gzips +each message and stores it in a folder named by the year and month. Each +message's file name is a Unix epoch timestamp. + +Example directory structure: +``` +/var/lib/mailman3/archives +\_ monthly_archiver + \_ mylist@mydomain.com + \_ 2021-April + \_ 1617678398.173.mail.gz + \_ 1617678805.351.mail.gz + \_ 2021-May + \_ ... +``` + +The motivation behind `MonthlyArchiver` was to avoid storing thousands of +messages in a single directory, which the `Prototype` archiver currently +does since it uses a maildir. + +## Installation +Make sure to have the following packages installed first: +```sh +apt install python3-pip python3-setuptools python3-wheel +``` +Then: +```sh +pip3 install . +``` diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8407662 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,23 @@ +[metadata] +name = extra-mailman-archivers +version = 0.0.1 +author = Max Erenberg +author_email = merenber@csclub.uwaterloo.ca +description = Some extra archivers for Mailman 3 +long_description = file: README.md +long_description_content_type = text/markdown +url = https://git.csclub.uwaterloo.ca/merenber/extra-mailman-archivers.git +license = GPLv3 +classifiers = + Programming Language :: Python :: 3 + OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Operating System :: OS Independent + +[options] +package_dir = + = src +packages = find: +python_requires = >=3.5 + +[options.packages.find] +where = src diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b908cbe --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +import setuptools + +setuptools.setup() diff --git a/src/extra_mailman_archivers/__init__.py b/src/extra_mailman_archivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/extra_mailman_archivers/monthly_archiver.py b/src/extra_mailman_archivers/monthly_archiver.py new file mode 100644 index 0000000..b601f48 --- /dev/null +++ b/src/extra_mailman_archivers/monthly_archiver.py @@ -0,0 +1,108 @@ +# 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 folder for the current +month. +""" + +from contextlib import suppress +import datetime +from datetime import timedelta +from email.utils import parsedate +import gzip +import logging +import os +import time + +from flufl.lock import Lock, TimeOutError +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.error') + + +@public +@implementer(IArchiver) +class MonthlyArchiver: + """ + A simple archiver which copies each message into a folder 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 folder for the current year and + month, e.g. '2021-April'. + + :type mlist: mailman.model.listmanager.ListManager + :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_and_month = time.strftime('%Y-%B', date) + + archive_dir = os.path.join( + config.ARCHIVE_DIR, + MonthlyArchiver.name, + mlist.fqdn_listname, + year_and_month + ) + with suppress(FileExistsError): + os.makedirs(archive_dir, 0o775) + + # 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()) + + return None