summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2015-12-20 12:44:06 -0500
committerBarry Warsaw2015-12-20 12:44:06 -0500
commitbb45766f91ddce5e68ddf0a1fe6fe67d2f93dd60 (patch)
tree1eb70a7836fc5e59968c635f65715de91c766c09
parentd7bc81a6ab97cd00c70da901cb1d04f80f896685 (diff)
downloadmailman-bb45766f91ddce5e68ddf0a1fe6fe67d2f93dd60.tar.gz
mailman-bb45766f91ddce5e68ddf0a1fe6fe67d2f93dd60.tar.zst
mailman-bb45766f91ddce5e68ddf0a1fe6fe67d2f93dd60.zip
-rw-r--r--port_me/senddigests.py83
-rw-r--r--src/mailman/app/lifecycle.py12
-rw-r--r--src/mailman/app/tests/test_lifecycle.py10
-rw-r--r--src/mailman/commands/cli_control.py2
-rw-r--r--src/mailman/commands/cli_help.py2
-rw-r--r--src/mailman/commands/cli_lists.py4
-rw-r--r--src/mailman/commands/cli_send_digests.py69
-rw-r--r--src/mailman/commands/tests/test_send_digests.py287
-rw-r--r--src/mailman/database/alembic/versions/70af5a4e5790_digests.py2
-rw-r--r--src/mailman/docs/NEWS.rst3
-rw-r--r--src/mailman/handlers/to_digest.py78
-rw-r--r--src/mailman/interfaces/mailinglist.py3
-rw-r--r--src/mailman/model/mailinglist.py2
-rw-r--r--src/mailman/runners/docs/digester.rst2
14 files changed, 433 insertions, 126 deletions
diff --git a/port_me/senddigests.py b/port_me/senddigests.py
deleted file mode 100644
index 59c03de2d..000000000
--- a/port_me/senddigests.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright (C) 1998-2015 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/>.
-
-import os
-import sys
-import optparse
-
-from mailman import MailList
-from mailman.core.i18n import _
-from mailman.initialize import initialize
-from mailman.version import MAILMAN_VERSION
-
-# Work around known problems with some RedHat cron daemons
-import signal
-signal.signal(signal.SIGCHLD, signal.SIG_DFL)
-
-
-
-def parseargs():
- parser = optparse.OptionParser(version=MAILMAN_VERSION,
- usage=_("""\
-%prog [options]
-
-Dispatch digests for lists w/pending messages and digest_send_periodic
-set."""))
- parser.add_option('-l', '--listname',
- type='string', default=[], action='append',
- dest='listnames', help=_("""\
-Send the digest for the given list only, otherwise the digests for all
-lists are sent out. Multiple -l options may be given."""))
- parser.add_option('-C', '--config',
- help=_('Alternative configuration file to use'))
- opts, args = parser.parse_args()
- if args:
- parser.print_help()
- print >> sys.stderr, _('Unexpected arguments')
- sys.exit(1)
- return opts, args, parser
-
-
-
-def main():
- opts, args, parser = parseargs()
- initialize(opts.config)
-
- for listname in set(opts.listnames or config.list_manager.names):
- mlist = MailList.MailList(listname, lock=False)
- if mlist.digest_send_periodic:
- mlist.Lock()
- try:
- try:
- mlist.send_digest_now()
- mlist.Save()
- # We are unable to predict what exception may occur in digest
- # processing and we don't want to lose the other digests, so
- # we catch everything.
- except Exception as errmsg:
- print >> sys.stderr, \
- 'List: %s: problem processing %s:\n%s' % \
- (listname,
- os.path.join(mlist.data_path, 'digest.mbox'),
- errmsg)
- finally:
- mlist.Unlock()
-
-
-
-if __name__ == '__main__':
- main()
diff --git a/src/mailman/app/lifecycle.py b/src/mailman/app/lifecycle.py
index 140ccab21..2120c12c6 100644
--- a/src/mailman/app/lifecycle.py
+++ b/src/mailman/app/lifecycle.py
@@ -94,14 +94,12 @@ def create_list(fqdn_listname, owners=None, style_name=None):
def remove_list(mlist):
"""Remove the list and all associated artifacts and subscriptions."""
- fqdn_listname = mlist.fqdn_listname
+ # Remove the list's data directory, if it exists.
+ try:
+ shutil.rmtree(mlist.data_path)
+ except FileNotFoundError:
+ pass
# Delete the mailing list from the database.
getUtility(IListManager).delete(mlist)
# Do the MTA-specific list deletion tasks
call_name(config.mta.incoming).delete(mlist)
- # Remove the list directory, if it exists.
- try:
- shutil.rmtree(os.path.join(config.LIST_DATA_DIR, fqdn_listname))
- except OSError as error:
- if error.errno != errno.ENOENT:
- raise
diff --git a/src/mailman/app/tests/test_lifecycle.py b/src/mailman/app/tests/test_lifecycle.py
index 62cbbf768..df2bf5233 100644
--- a/src/mailman/app/tests/test_lifecycle.py
+++ b/src/mailman/app/tests/test_lifecycle.py
@@ -26,7 +26,6 @@ import os
import shutil
import unittest
-from mailman.config import config
from mailman.interfaces.address import InvalidEmailAddressError
from mailman.interfaces.domain import BadDomainSpecificationError
from mailman.app.lifecycle import create_list, remove_list
@@ -47,13 +46,12 @@ class TestLifecycle(unittest.TestCase):
def test_unregistered_domain(self):
# Creating a list with an unregistered domain raises an exception.
self.assertRaises(BadDomainSpecificationError,
- create_list, 'test@nodomain.example.org')
+ create_list, 'test@nodomain.example.org')
def test_remove_list_error(self):
# An error occurs while deleting the list's data directory.
mlist = create_list('test@example.com')
- data_dir = os.path.join(config.LIST_DATA_DIR, mlist.fqdn_listname)
- os.chmod(data_dir, 0)
- self.addCleanup(shutil.rmtree, data_dir)
+ os.chmod(mlist.data_path, 0)
+ self.addCleanup(shutil.rmtree, mlist.data_path)
self.assertRaises(OSError, remove_list, mlist)
- os.chmod(data_dir, 0o777)
+ os.chmod(mlist.data_path, 0o777)
diff --git a/src/mailman/commands/cli_control.py b/src/mailman/commands/cli_control.py
index 5bd9ef1e3..df1cc373b 100644
--- a/src/mailman/commands/cli_control.py
+++ b/src/mailman/commands/cli_control.py
@@ -205,7 +205,7 @@ class Stop(SignalCommand):
class Reopen(SignalCommand):
- """Signal the Mailman processes to re-open their log files.."""
+ """Signal the Mailman processes to re-open their log files."""
name = 'reopen'
message = _('Reopening the Mailman runners')
diff --git a/src/mailman/commands/cli_help.py b/src/mailman/commands/cli_help.py
index eec47b0f0..df745244e 100644
--- a/src/mailman/commands/cli_help.py
+++ b/src/mailman/commands/cli_help.py
@@ -30,7 +30,7 @@ from zope.interface import implementer
@implementer(ICLISubCommand)
class Help:
# Lowercase, to match argparse's default --help text.
- """show this help message and exit"""
+ """Show this help message and exit."""
name = 'help'
diff --git a/src/mailman/commands/cli_lists.py b/src/mailman/commands/cli_lists.py
index 3d5fcd634..cbc797e5b 100644
--- a/src/mailman/commands/cli_lists.py
+++ b/src/mailman/commands/cli_lists.py
@@ -122,7 +122,7 @@ class Lists:
@implementer(ICLISubCommand)
class Create:
- """Create a mailing list"""
+ """Create a mailing list."""
name = 'create'
@@ -238,7 +238,7 @@ class Create:
@implementer(ICLISubCommand)
class Remove:
- """Remove a mailing list"""
+ """Remove a mailing list."""
name = 'remove'
diff --git a/src/mailman/commands/cli_send_digests.py b/src/mailman/commands/cli_send_digests.py
new file mode 100644
index 000000000..842054982
--- /dev/null
+++ b/src/mailman/commands/cli_send_digests.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2015 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/>.
+
+"""The `send_digests` subcommand."""
+
+__all__ = [
+ 'Send',
+ ]
+
+
+import sys
+
+from mailman.core.i18n import _
+from mailman.handlers.to_digest import maybe_send_digest_now
+from mailman.interfaces.command import ICLISubCommand
+from mailman.interfaces.listmanager import IListManager
+from zope.component import getUtility
+from zope.interface import implementer
+
+
+
+@implementer(ICLISubCommand)
+class Send:
+ """Send some mailing list digests right now."""
+
+ name = 'send-digests'
+
+ def add(self, parser, command_parser):
+ """See `ICLISubCommand`."""
+
+ command_parser.add_argument(
+ '-l', '--list',
+ default=[], dest='lists', metavar='list', action='append',
+ help=_("""Send the digests for this mailing list. Multiple --list
+ options can be given. The argument can either be a List-ID
+ or a fully qualified list name. Without this option, the
+ digests for all mailing lists will be sent if possible."""))
+
+ def process(self, args):
+ """See `ICLISubCommand`."""
+ if not args.lists:
+ # Send the digests for every list.
+ maybe_send_digest_now(force=True)
+ return
+ list_manager = getUtility(IListManager)
+ for list_spec in args.lists:
+ # We'll accept list-ids or fqdn list names.
+ if '@' in list_spec:
+ mlist = list_manager.get(list_spec)
+ else:
+ mlist = list_manager.get_by_list_id(list_spec)
+ if mlist is None:
+ print(_('No such list found: $list_spec'), file=sys.stderr)
+ continue
+ maybe_send_digest_now(mlist, force=True)
diff --git a/src/mailman/commands/tests/test_send_digests.py b/src/mailman/commands/tests/test_send_digests.py
new file mode 100644
index 000000000..937922d7c
--- /dev/null
+++ b/src/mailman/commands/tests/test_send_digests.py
@@ -0,0 +1,287 @@
+# Copyright (C) 2015 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/>.
+
+"""Test the send-digests subcommand."""
+
+__all__ = [
+ 'TestSendDigests',
+ ]
+
+
+import os
+import unittest
+
+from io import StringIO
+from mailman.app.lifecycle import create_list
+from mailman.commands.cli_send_digests import Send
+from mailman.config import config
+from mailman.interfaces.member import DeliveryMode
+from mailman.runners.digest import DigestRunner
+from mailman.testing.helpers import (
+ get_queue_messages, make_testable_runner,
+ specialized_message_from_string as mfs, subscribe)
+from mailman.testing.layers import ConfigLayer
+from unittest.mock import patch
+
+
+
+class FakeArgs:
+ def __init__(self):
+ self.lists = []
+
+
+
+class TestSendDigests(unittest.TestCase):
+ """Test the send-digests subcommand."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('ant@example.com')
+ self._mlist.digests_enabled = True
+ self._mlist.digest_size_threshold = 100000
+ self._mlist.send_welcome_message = False
+ self._command = Send()
+ self._handler = config.handlers['to-digest']
+ self._runner = make_testable_runner(DigestRunner, 'digest')
+ # The mailing list needs at least one digest recipient.
+ member = subscribe(self._mlist, 'Anne')
+ member.preferences.delivery_mode = DeliveryMode.plaintext_digests
+
+ def test_send_one_digest_by_list_id(self):
+ msg = mfs("""\
+To: ant@example.com
+From: anne@example.com
+Subject: message 1
+
+""")
+ self._handler.process(self._mlist, msg, {})
+ del msg['subject']
+ msg['subject'] = 'message 2'
+ self._handler.process(self._mlist, msg, {})
+ # There are no digests already being sent, but the ant mailing list
+ # does have a digest mbox collecting messages.
+ items = get_queue_messages('digest')
+ self.assertEqual(len(items), 0)
+ mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
+ self.assertGreater(os.path.getsize(mailbox_path), 0)
+ args = FakeArgs()
+ args.lists.append('ant.example.com')
+ self._command.process(args)
+ self._runner.run()
+ # Now, there's no digest mbox and there's a plaintext digest in the
+ # outgoing queue.
+ self.assertFalse(os.path.exists(mailbox_path))
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 1)
+ digest_contents = str(items[0].msg)
+ self.assertIn('Subject: message 1', digest_contents)
+ self.assertIn('Subject: message 2', digest_contents)
+
+ def test_send_one_digest_by_fqdn_listname(self):
+ msg = mfs("""\
+To: ant@example.com
+From: anne@example.com
+Subject: message 1
+
+""")
+ self._handler.process(self._mlist, msg, {})
+ del msg['subject']
+ msg['subject'] = 'message 2'
+ self._handler.process(self._mlist, msg, {})
+ # There are no digests already being sent, but the ant mailing list
+ # does have a digest mbox collecting messages.
+ items = get_queue_messages('digest')
+ self.assertEqual(len(items), 0)
+ mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
+ self.assertGreater(os.path.getsize(mailbox_path), 0)
+ args = FakeArgs()
+ args.lists.append('ant@example.com')
+ self._command.process(args)
+ self._runner.run()
+ # Now, there's no digest mbox and there's a plaintext digest in the
+ # outgoing queue.
+ self.assertFalse(os.path.exists(mailbox_path))
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 1)
+ digest_contents = str(items[0].msg)
+ self.assertIn('Subject: message 1', digest_contents)
+ self.assertIn('Subject: message 2', digest_contents)
+
+ def test_send_one_digest_to_missing_list_id(self):
+ msg = mfs("""\
+To: ant@example.com
+From: anne@example.com
+Subject: message 1
+
+""")
+ self._handler.process(self._mlist, msg, {})
+ del msg['subject']
+ msg['subject'] = 'message 2'
+ self._handler.process(self._mlist, msg, {})
+ # There are no digests already being sent, but the ant mailing list
+ # does have a digest mbox collecting messages.
+ items = get_queue_messages('digest')
+ self.assertEqual(len(items), 0)
+ mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
+ self.assertGreater(os.path.getsize(mailbox_path), 0)
+ args = FakeArgs()
+ args.lists.append('bee.example.com')
+ stderr = StringIO()
+ with patch('mailman.commands.cli_send_digests.sys.stderr', stderr):
+ self._command.process(args)
+ self._runner.run()
+ # The warning was printed to stderr.
+ self.assertEqual(stderr.getvalue(),
+ 'No such list found: bee.example.com\n')
+ # And no digest was prepared.
+ self.assertGreater(os.path.getsize(mailbox_path), 0)
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 0)
+
+ def test_send_one_digest_to_missing_fqdn_listname(self):
+ msg = mfs("""\
+To: ant@example.com
+From: anne@example.com
+Subject: message 1
+
+""")
+ self._handler.process(self._mlist, msg, {})
+ del msg['subject']
+ msg['subject'] = 'message 2'
+ self._handler.process(self._mlist, msg, {})
+ # There are no digests already being sent, but the ant mailing list
+ # does have a digest mbox collecting messages.
+ items = get_queue_messages('digest')
+ self.assertEqual(len(items), 0)
+ mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
+ self.assertGreater(os.path.getsize(mailbox_path), 0)
+ args = FakeArgs()
+ args.lists.append('bee@example.com')
+ stderr = StringIO()
+ with patch('mailman.commands.cli_send_digests.sys.stderr', stderr):
+ self._command.process(args)
+ self._runner.run()
+ # The warning was printed to stderr.
+ self.assertEqual(stderr.getvalue(),
+ 'No such list found: bee@example.com\n')
+ # And no digest was prepared.
+ self.assertGreater(os.path.getsize(mailbox_path), 0)
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 0)
+
+ def test_send_digest_to_one_missing_and_one_existing_list(self):
+ msg = mfs("""\
+To: ant@example.com
+From: anne@example.com
+Subject: message 1
+
+""")
+ self._handler.process(self._mlist, msg, {})
+ del msg['subject']
+ msg['subject'] = 'message 2'
+ self._handler.process(self._mlist, msg, {})
+ # There are no digests already being sent, but the ant mailing list
+ # does have a digest mbox collecting messages.
+ items = get_queue_messages('digest')
+ self.assertEqual(len(items), 0)
+ mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
+ self.assertGreater(os.path.getsize(mailbox_path), 0)
+ args = FakeArgs()
+ args.lists.extend(('ant.example.com', 'bee.example.com'))
+ stderr = StringIO()
+ with patch('mailman.commands.cli_send_digests.sys.stderr', stderr):
+ self._command.process(args)
+ self._runner.run()
+ # The warning was printed to stderr.
+ self.assertEqual(stderr.getvalue(),
+ 'No such list found: bee.example.com\n')
+ # But ant's digest was still prepared.
+ self.assertFalse(os.path.exists(mailbox_path))
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 1)
+ digest_contents = str(items[0].msg)
+ self.assertIn('Subject: message 1', digest_contents)
+ self.assertIn('Subject: message 2', digest_contents)
+
+ def test_send_digests_for_two_lists(self):
+ # Populate ant's digest.
+ msg = mfs("""\
+To: ant@example.com
+From: anne@example.com
+Subject: message 1
+
+""")
+ self._handler.process(self._mlist, msg, {})
+ del msg['subject']
+ msg['subject'] = 'message 2'
+ self._handler.process(self._mlist, msg, {})
+ # Create the second list.
+ bee = create_list('bee@example.com')
+ bee.digests_enabled = True
+ bee.digest_size_threshold = 100000
+ bee.send_welcome_message = False
+ member = subscribe(bee, 'Bart')
+ member.preferences.delivery_mode = DeliveryMode.plaintext_digests
+ # Populate bee's digest.
+ msg = mfs("""\
+To: bee@example.com
+From: bart@example.com
+Subject: message 3
+
+""")
+ self._handler.process(bee, msg, {})
+ del msg['subject']
+ msg['subject'] = 'message 4'
+ self._handler.process(bee, msg, {})
+ # There are no digests for either list already being sent, but the
+ # mailing lists do have a digest mbox collecting messages.
+ ant_mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
+ self.assertGreater(os.path.getsize(ant_mailbox_path), 0)
+ # Check bee's digest.
+ bee_mailbox_path = os.path.join(bee.data_path, 'digest.mmdf')
+ self.assertGreater(os.path.getsize(bee_mailbox_path), 0)
+ # Both.
+ items = get_queue_messages('digest')
+ self.assertEqual(len(items), 0)
+ # Process both list's digests.
+ args = FakeArgs()
+ args.lists.extend(('ant.example.com', 'bee@example.com'))
+ self._command.process(args)
+ self._runner.run()
+ # Now, neither list has a digest mbox and but there are plaintext
+ # digest in the outgoing queue for both.
+ self.assertFalse(os.path.exists(ant_mailbox_path))
+ self.assertFalse(os.path.exists(bee_mailbox_path))
+ items = get_queue_messages('virgin')
+ self.assertEqual(len(items), 2)
+ # Figure out which digest is going to ant and which to bee.
+ if items[0].msg['to'] == 'ant@example.com':
+ ant = items[0].msg
+ bee = items[1].msg
+ else:
+ assert items[0].msg['to'] == 'bee@example.com'
+ ant = items[1].msg
+ bee = items[0].msg
+ # Check ant's digest.
+ digest_contents = str(ant)
+ self.assertIn('Subject: message 1', digest_contents)
+ self.assertIn('Subject: message 2', digest_contents)
+ # Check bee's digest.
+ digest_contents = str(bee)
+ self.assertIn('Subject: message 3', digest_contents)
+ self.assertIn('Subject: message 4', digest_contents)
diff --git a/src/mailman/database/alembic/versions/70af5a4e5790_digests.py b/src/mailman/database/alembic/versions/70af5a4e5790_digests.py
index 5df46b584..2b53202d9 100644
--- a/src/mailman/database/alembic/versions/70af5a4e5790_digests.py
+++ b/src/mailman/database/alembic/versions/70af5a4e5790_digests.py
@@ -25,3 +25,5 @@ def downgrade():
batch_op.alter_column('digests_enabled', new_column_name='digestable')
# The data for this column is lost, it's not used anyway.
batch_op.add_column(sa.Column('nondigestable', sa.Boolean))
+
+# XXX - move list.data_path
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index 2cc72862b..6e098a8c0 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -132,6 +132,9 @@ Other
``list_url`` or permalink. Given by Aurélien Bompard.
* Large performance improvement in ``SubscriptionService.find_members()``.
Given by Aurélien Bompard.
+ * Rework the digest machinery, and add a new `send-digests` subcommand, which
+ can be used from the command line or cron to immediately send out any
+ partially collected digests.
3.0.0 -- "Show Don't Tell"
diff --git a/src/mailman/handlers/to_digest.py b/src/mailman/handlers/to_digest.py
index 89e1acaea..d9c707e12 100644
--- a/src/mailman/handlers/to_digest.py
+++ b/src/mailman/handlers/to_digest.py
@@ -19,6 +19,8 @@
__all__ = [
'ToDigest',
+ 'bump_digest_number_and_volume',
+ 'maybe_send_digest_now',
]
@@ -29,8 +31,10 @@ from mailman.core.i18n import _
from mailman.email.message import Message
from mailman.interfaces.digests import DigestFrequency
from mailman.interfaces.handler import IHandler
+from mailman.interfaces.listmanager import IListManager
from mailman.utilities.datetime import now as right_now
from mailman.utilities.mailbox import Mailbox
+from zope.component import getUtility
from zope.interface import implementer
@@ -53,29 +57,7 @@ class ToDigest:
# Lock the mailbox and append the message.
with Mailbox(mailbox_path, create=True) as mbox:
mbox.add(msg)
- # Calculate the current size of the mailbox file. This will not tell
- # us exactly how big the resulting MIME and rfc1153 digest will
- # actually be, but it's the most easily available metric to decide
- # whether the size threshold has been reached.
- size = os.path.getsize(mailbox_path)
- if size >= mlist.digest_size_threshold * 1024.0:
- # The digest is ready to send. Because we don't want to hold up
- # this process with crafting the digest, we're going to move the
- # digest file to a safe place, then craft a fake message for the
- # DigestRunner as a trigger for it to build and send the digest.
- mailbox_dest = os.path.join(
- mlist.data_path,
- 'digest.{0.volume}.{0.next_digest_number}.mmdf'.format(mlist))
- volume = mlist.volume
- digest_number = mlist.next_digest_number
- bump_digest_number_and_volume(mlist)
- os.rename(mailbox_path, mailbox_dest)
- config.switchboards['digest'].enqueue(
- Message(),
- listid=mlist.list_id,
- digest_path=mailbox_dest,
- volume=volume,
- digest_number=digest_number)
+ maybe_send_digest_now(mlist)
@@ -117,3 +99,53 @@ def bump_digest_number_and_volume(mlist):
# Just bump the digest number.
mlist.next_digest_number += 1
mlist.digest_last_sent_at = now
+
+
+
+def maybe_send_digest_now(mlist=None, force=False):
+ """Send this mailing list's digest now.
+
+ If there are any messages in this mailing list's digest, the
+ digest is sent immediately, regardless of whether the size
+ threshold has been met. When called through the subcommand
+ `mailman send_digest` the value of .digest_send_periodic is
+ consulted.
+
+ :param mlist: The mailing list whose digest should be sent. If this is
+ None, all mailing lists with non-zero sized digests will have theirs
+ sent immediately.
+ :type mlist: IMailingList or None
+ :param force: Should the digest be sent even if the size threshold hasn't
+ been met?
+ :type force: boolean
+ """
+ if mlist is None:
+ digestable_lists = getUtility(IListManager).mailing_lists
+ else:
+ digestable_lists = [mlist]
+ for mailing_list in digestable_lists:
+ mailbox_path = os.path.join(mailing_list.data_path, 'digest.mmdf')
+ # Calculate the current size of the mailbox file. This will not tell
+ # us exactly how big the resulting MIME and rfc1153 digest will
+ # actually be, but it's the most easily available metric to decide
+ # whether the size threshold has been reached.
+ size = os.path.getsize(mailbox_path)
+ if (size >= mlist.digest_size_threshold * 1024.0 or
+ (force and size > 0)):
+ # Send the digest. Because we don't want to hold up this process
+ # with crafting the digest, we're going to move the digest file to
+ # a safe place, then craft a fake message for the DigestRunner as
+ # a trigger for it to build and send the digest.
+ mailbox_dest = os.path.join(
+ mlist.data_path,
+ 'digest.{0.volume}.{0.next_digest_number}.mmdf'.format(mlist))
+ volume = mlist.volume
+ digest_number = mlist.next_digest_number
+ bump_digest_number_and_volume(mlist)
+ os.rename(mailbox_path, mailbox_dest)
+ config.switchboards['digest'].enqueue(
+ Message(),
+ listid=mlist.list_id,
+ digest_path=mailbox_dest,
+ volume=volume,
+ digest_number=digest_number)
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index 75fdce327..2d2062d9c 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -316,7 +316,8 @@ class IMailingList(Interface):
being collected.""")
digest_send_periodic = Attribute(
- "Should a digest be sent daily even when the size threshold isn't met?")
+ """Should a digest be sent by the `mailman send_digest` command even
+ when the size threshold hasn't yet been met?""")
digest_volume_frequency = Attribute(
"""How often should a new digest volume be started?""")
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 9ee52629f..44dd9998b 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -261,7 +261,7 @@ class MailingList(Model):
@property
def data_path(self):
"""See `IMailingList`."""
- return os.path.join(config.LIST_DATA_DIR, self.fqdn_listname)
+ return os.path.join(config.LIST_DATA_DIR, self.list_id)
# IMailingListAddresses
diff --git a/src/mailman/runners/docs/digester.rst b/src/mailman/runners/docs/digester.rst
index 9ff604340..00deb0d4a 100644
--- a/src/mailman/runners/docs/digester.rst
+++ b/src/mailman/runners/docs/digester.rst
@@ -56,7 +56,7 @@ But the message metadata has a reference to the digest file.
>>> dump_msgdata(entry.msgdata)
_parsemsg : False
digest_number: 1
- digest_path : .../lists/test@example.com/digest.1.1.mmdf
+ digest_path : .../lists/test.example.com/digest.1.1.mmdf
listid : test.example.com
version : 3
volume : 1