commit
e824785e0a
@ -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 . |
||||
``` |
@ -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 |
@ -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 <http://www.gnu.org/licenses/>. |
||||
|
||||
""" |
||||
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 |
Loading…
Reference in new issue