From c9464cb64f434749fee21e5ebbb15ce3f2fc1691 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Tue, 22 Dec 2015 14:54:46 -0500 Subject: Refactor bump_digest_number_and_volume() and maybe_send_digest_now() into their own module inside the mailman.app package. With the latter, remove the "all lists" functionality and require the mlist argument. We'll handle the "all lists" use case higher up the stack. Also, rename the send-digests handler digests since we'll next add the bump functionality. --- src/mailman/app/digests.py | 118 ++++++++ src/mailman/app/tests/test_digests.py | 237 ++++++++++++++++ src/mailman/commands/cli_digests.py | 91 ++++++ src/mailman/commands/cli_send_digests.py | 69 ----- src/mailman/commands/tests/test_digests.py | 363 ++++++++++++++++++++++++ src/mailman/commands/tests/test_send_digests.py | 353 ----------------------- src/mailman/handlers/to_digest.py | 101 +------ 7 files changed, 810 insertions(+), 522 deletions(-) create mode 100644 src/mailman/app/digests.py create mode 100644 src/mailman/app/tests/test_digests.py create mode 100644 src/mailman/commands/cli_digests.py delete mode 100644 src/mailman/commands/cli_send_digests.py create mode 100644 src/mailman/commands/tests/test_digests.py delete mode 100644 src/mailman/commands/tests/test_send_digests.py diff --git a/src/mailman/app/digests.py b/src/mailman/app/digests.py new file mode 100644 index 000000000..b66a4c54c --- /dev/null +++ b/src/mailman/app/digests.py @@ -0,0 +1,118 @@ +# 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 . + +"""Digest functions.""" + +__all__ = [ + 'bump_digest_number_and_volume', + 'maybe_send_digest_now', + ] + + +import os + +from mailman.config import config +from mailman.email.message import Message +from mailman.interfaces.digests import DigestFrequency +from mailman.interfaces.listmanager import IListManager +from mailman.utilities.datetime import now as right_now +from zope.component import getUtility + + + +def bump_digest_number_and_volume(mlist): + """Bump the digest number and volume.""" + now = right_now() + if mlist.digest_last_sent_at is None: + # There has been no previous digest. + bump = False + elif mlist.digest_volume_frequency == DigestFrequency.yearly: + bump = (now.year > mlist.digest_last_sent_at.year) + elif mlist.digest_volume_frequency == DigestFrequency.monthly: + # Monthly. + this_month = now.year * 100 + now.month + digest_month = (mlist.digest_last_sent_at.year * 100 + + mlist.digest_last_sent_at.month) + bump = (this_month > digest_month) + elif mlist.digest_volume_frequency == DigestFrequency.quarterly: + # Quarterly. + this_quarter = now.year * 100 + (now.month - 1) // 4 + digest_quarter = (mlist.digest_last_sent_at.year * 100 + + (mlist.digest_last_sent_at.month - 1) // 4) + bump = (this_quarter > digest_quarter) + elif mlist.digest_volume_frequency == DigestFrequency.weekly: + this_week = now.year * 100 + now.isocalendar()[1] + digest_week = (mlist.digest_last_sent_at.year * 100 + + mlist.digest_last_sent_at.isocalendar()[1]) + bump = (this_week > digest_week) + elif mlist.digest_volume_frequency == DigestFrequency.daily: + bump = (now.toordinal() > mlist.digest_last_sent_at.toordinal()) + else: + raise AssertionError( + 'Bad DigestFrequency: {0}'.format( + mlist.digest_volume_frequency)) + if bump: + mlist.volume += 1 + mlist.next_digest_number = 1 + else: + # Just bump the digest number. + mlist.next_digest_number += 1 + mlist.digest_last_sent_at = now + + + +def maybe_send_digest_now(mlist, 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. + :type mlist: IMailingList + :param force: Should the digest be sent even if the size threshold hasn't + been met? + :type force: boolean + """ + mailbox_path = os.path.join(mlist.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/app/tests/test_digests.py b/src/mailman/app/tests/test_digests.py new file mode 100644 index 000000000..43ea012c5 --- /dev/null +++ b/src/mailman/app/tests/test_digests.py @@ -0,0 +1,237 @@ +# 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 . + +"""Digest helper tests.""" + +__all__ = [ + 'TestBumpDigest', + 'TestMaybeSendDigest', + ] + + +import os +import unittest + +from datetime import timedelta +from mailman.app.digests import ( + bump_digest_number_and_volume, maybe_send_digest_now) +from mailman.app.lifecycle import create_list +from mailman.config import config +from mailman.interfaces.digests import DigestFrequency +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 mailman.utilities.datetime import factory, now as right_now + + + +class TestBumpDigest(unittest.TestCase): + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('ant@example.com') + self._mlist.volume = 7 + self._mlist.next_digest_number = 4 + self.right_now = right_now() + + def test_bump_no_previous_digest(self): + self._mlist.digest_last_sent_at = None + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 7) + self.assertEqual(self._mlist.next_digest_number, 5) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_yearly(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + days=-370) + self._mlist.digest_volume_frequency = DigestFrequency.yearly + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 8) + self.assertEqual(self._mlist.next_digest_number, 1) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_yearly_not_yet(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + days=-200) + self._mlist.digest_volume_frequency = DigestFrequency.yearly + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 7) + self.assertEqual(self._mlist.next_digest_number, 5) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_monthly(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + days=-32) + self._mlist.digest_volume_frequency = DigestFrequency.monthly + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 8) + self.assertEqual(self._mlist.next_digest_number, 1) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_monthly_not_yet(self): + # The normal test date starts on the first day of the month, so let's + # fast forward it a few days so we can set the digest last sent time + # to earlier in the same month. + self._mlist.digest_last_sent_at = self.right_now + factory.fast_forward(days=26) + self._mlist.digest_volume_frequency = DigestFrequency.monthly + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 7) + self.assertEqual(self._mlist.next_digest_number, 5) + self.assertEqual(self._mlist.digest_last_sent_at, right_now()) + + def test_bump_quarterly(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + days=-93) + self._mlist.digest_volume_frequency = DigestFrequency.quarterly + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 8) + self.assertEqual(self._mlist.next_digest_number, 1) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_quarterly_not_yet(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + days=-88) + self._mlist.digest_volume_frequency = DigestFrequency.quarterly + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 7) + self.assertEqual(self._mlist.next_digest_number, 5) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_weekly(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + days=-8) + self._mlist.digest_volume_frequency = DigestFrequency.weekly + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 8) + self.assertEqual(self._mlist.next_digest_number, 1) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_weekly_not_yet(self): + # The normal test date starts on the first day of the week, so let's + # fast forward it a few days so we can set the digest last sent time + # to earlier in the same week. + self._mlist.digest_last_sent_at = self.right_now + factory.fast_forward(days=3) + self._mlist.digest_volume_frequency = DigestFrequency.weekly + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 7) + self.assertEqual(self._mlist.next_digest_number, 5) + self.assertEqual(self._mlist.digest_last_sent_at, right_now()) + + def test_bump_daily(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + hours=-27) + self._mlist.digest_volume_frequency = DigestFrequency.daily + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 8) + self.assertEqual(self._mlist.next_digest_number, 1) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_daily_not_yet(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + hours=-5) + self._mlist.digest_volume_frequency = DigestFrequency.daily + bump_digest_number_and_volume(self._mlist) + self.assertEqual(self._mlist.volume, 7) + self.assertEqual(self._mlist.next_digest_number, 5) + self.assertEqual(self._mlist.digest_last_sent_at, self.right_now) + + def test_bump_bad_frequency(self): + self._mlist.digest_last_sent_at = self.right_now + timedelta( + hours=-22) + self._mlist.digest_volume_frequency = -10 + self.assertRaises(AssertionError, + bump_digest_number_and_volume, self._mlist) + + + +class TestMaybeSendDigest(unittest.TestCase): + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('ant@example.com') + self._mlist.send_welcome_message = False + self._mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf') + # The mailing list needs at least one digest recipient. + member = subscribe(self._mlist, 'Anne') + member.preferences.delivery_mode = DeliveryMode.plaintext_digests + self._subject_number = 1 + self._runner = make_testable_runner(DigestRunner, 'digest') + + def _to_digest(self, count=1): + for i in range(count): + msg = mfs("""\ +To: ant@example.com +From: anne@example.com +Subject: message {} + +""".format(self._subject_number)) + self._subject_number += 1 + config.handlers['to-digest'].process(self._mlist, msg, {}) + + def test_send_digest_over_threshold(self): + # Put a few messages in the digest. + self._to_digest(3) + # Set the size threshold low enough to trigger a send. + self._mlist.digest_size_threshold = 0.1 + maybe_send_digest_now(self._mlist) + self._runner.run() + # There are no digests in flight now, and a single digest message has + # been sent. + self.assertEqual(len(get_queue_messages('digest')), 0) + self.assertFalse(os.path.exists(self._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_dont_send_digest_under_threshold(self): + # Put a few messages in the digest. + self._to_digest(3) + # Set the size threshold high enough to not trigger a send. + self._mlist.digest_size_threshold = 100 + maybe_send_digest_now(self._mlist) + self._runner.run() + # A digest is still being collected, but none have been sent. + self.assertEqual(len(get_queue_messages('digest')), 0) + self.assertGreater(os.path.getsize(self._mailbox_path), 0) + self.assertLess(os.path.getsize(self._mailbox_path), 100 * 1024.0) + items = get_queue_messages('virgin') + self.assertEqual(len(items), 0) + + def test_force_send_digest_under_threshold(self): + # Put a few messages in the digest. + self._to_digest(3) + # Set the size threshold high enough to not trigger a send. + self._mlist.digest_size_threshold = 100 + # Force sending a digest anyway. + maybe_send_digest_now(self._mlist, force=True) + self._runner.run() + # There are no digests in flight now, and a single digest message has + # been sent. + self.assertEqual(len(get_queue_messages('digest')), 0) + self.assertFalse(os.path.exists(self._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) diff --git a/src/mailman/commands/cli_digests.py b/src/mailman/commands/cli_digests.py new file mode 100644 index 000000000..9af91f1c3 --- /dev/null +++ b/src/mailman/commands/cli_digests.py @@ -0,0 +1,91 @@ +# 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 . + +"""The `send_digests` subcommand.""" + +__all__ = [ + 'Digests', + ] + + +import sys + +from mailman.app.digests import maybe_send_digest_now +from mailman.core.i18n import _ +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 Digests: + """Operate on digests.""" + + name = 'digests' + + def add(self, parser, command_parser): + """See `ICLISubCommand`.""" + + command_parser.add_argument( + '-l', '--list', + default=[], dest='lists', metavar='list', action='append', + help=_("""Operate on 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, + operate on the digests for all mailing lists.""")) + command_parser.add_argument( + '-s', '--send', + default=False, action='store_true', + help=_("""Send any collected digests right now, even if the size + threshold has not yet been met.""")) + command_parser.add_argument( + '-b', '--bump', + default=False, action='store_true', + help=_("""Increment the digest volume number and reset the digest + number to one. If given with --send, the volume number is + incremented after any current digests are sent.""")) + + def process(self, args): + """See `ICLISubCommand`.""" + list_manager = getUtility(IListManager) + if args.send: + if not args.lists: + # Send the digests for every list. + for mlist in list_manager.mailing_lists: + maybe_send_digest_now(mlist, force=True) + return + 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) + if args.bump: + if not args.lists: + mlists = list(list_manager.mailing_lists) + else: + # 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) diff --git a/src/mailman/commands/cli_send_digests.py b/src/mailman/commands/cli_send_digests.py deleted file mode 100644 index 842054982..000000000 --- a/src/mailman/commands/cli_send_digests.py +++ /dev/null @@ -1,69 +0,0 @@ -# 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 . - -"""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_digests.py b/src/mailman/commands/tests/test_digests.py new file mode 100644 index 000000000..2640580af --- /dev/null +++ b/src/mailman/commands/tests/test_digests.py @@ -0,0 +1,363 @@ +# 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 . + +"""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_digests import Digests +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 = [] + self.send = False + self.bump = False + + + +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 = Digests() + 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.send = True + 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.send = True + 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.send = True + args.lists.append('bee.example.com') + stderr = StringIO() + with patch('mailman.commands.cli_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.send = True + args.lists.append('bee@example.com') + stderr = StringIO() + with patch('mailman.commands.cli_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.send = True + args.lists.extend(('ant.example.com', 'bee.example.com')) + stderr = StringIO() + with patch('mailman.commands.cli_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.send = True + 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) + + def test_send_digests_for_all_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 all mailing list digests by not setting any arguments. + args = FakeArgs() + args.send = True + 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/commands/tests/test_send_digests.py b/src/mailman/commands/tests/test_send_digests.py deleted file mode 100644 index d0218ea12..000000000 --- a/src/mailman/commands/tests/test_send_digests.py +++ /dev/null @@ -1,353 +0,0 @@ -# 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 . - -"""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) - - def test_send_digests_for_all_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 all mailing list digests by not setting any arguments. - self._command.process(FakeArgs()) - 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/handlers/to_digest.py b/src/mailman/handlers/to_digest.py index 1b1ed84f1..fa43de030 100644 --- a/src/mailman/handlers/to_digest.py +++ b/src/mailman/handlers/to_digest.py @@ -19,22 +19,15 @@ __all__ = [ 'ToDigest', - 'bump_digest_number_and_volume', - 'maybe_send_digest_now', ] import os -from mailman.config import config +from mailman.app.digests import maybe_send_digest_now 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 @@ -58,95 +51,3 @@ class ToDigest: with Mailbox(mailbox_path, create=True) as mbox: mbox.add(msg) maybe_send_digest_now(mlist) - - - -def bump_digest_number_and_volume(mlist): - """Bump the digest number and volume.""" - now = right_now() - if mlist.digest_last_sent_at is None: - # There has been no previous digest. - bump = False - elif mlist.digest_volume_frequency == DigestFrequency.yearly: - bump = (now.year > mlist.digest_last_sent_at.year) - elif mlist.digest_volume_frequency == DigestFrequency.monthly: - # Monthly. - this_month = now.year * 100 + now.month - digest_month = (mlist.digest_last_sent_at.year * 100 + - mlist.digest_last_sent_at.month) - bump = (this_month > digest_month) - elif mlist.digest_volume_frequency == DigestFrequency.quarterly: - # Quarterly. - this_quarter = now.year * 100 + (now.month - 1) // 4 - digest_quarter = (mlist.digest_last_sent_at.year * 100 + - (mlist.digest_last_sent_at.month - 1) // 4) - bump = (this_quarter > digest_quarter) - elif mlist.digest_volume_frequency == DigestFrequency.weekly: - this_week = now.year * 100 + now.isocalendar()[1] - digest_week = (mlist.digest_last_sent_at.year * 100 + - mlist.digest_last_sent_at.isocalendar()[1]) - bump = (this_week > digest_week) - elif mlist.digest_volume_frequency == DigestFrequency.daily: - bump = (now.toordinal() > mlist.digest_last_sent_at.toordinal()) - else: - raise AssertionError( - 'Bad DigestFrequency: {0}'.format( - mlist.digest_volume_frequency)) - if bump: - mlist.volume += 1 - mlist.next_digest_number = 1 - else: - # 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 >= mailing_list.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( - mailing_list.data_path, - 'digest.{0.volume}.{0.next_digest_number}.mmdf'.format( - mailing_list)) - volume = mailing_list.volume - digest_number = mailing_list.next_digest_number - bump_digest_number_and_volume(mailing_list) - os.rename(mailbox_path, mailbox_dest) - config.switchboards['digest'].enqueue( - Message(), - listid=mailing_list.list_id, - digest_path=mailbox_dest, - volume=volume, - digest_number=digest_number) -- cgit v1.3.1