diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/digests.py | 118 | ||||
| -rw-r--r-- | src/mailman/app/tests/test_digests.py | 237 | ||||
| -rw-r--r-- | src/mailman/commands/cli_digests.py | 91 | ||||
| -rw-r--r-- | src/mailman/commands/cli_send_digests.py | 69 | ||||
| -rw-r--r-- | src/mailman/commands/tests/test_digests.py (renamed from src/mailman/commands/tests/test_send_digests.py) | 22 | ||||
| -rw-r--r-- | src/mailman/handlers/to_digest.py | 101 |
6 files changed, 463 insertions, 175 deletions
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 <http://www.gnu.org/licenses/>. + +"""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 <http://www.gnu.org/licenses/>. + +"""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 <http://www.gnu.org/licenses/>. + +"""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 <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_digests.py index d0218ea12..2640580af 100644 --- a/src/mailman/commands/tests/test_send_digests.py +++ b/src/mailman/commands/tests/test_digests.py @@ -27,7 +27,7 @@ import unittest from io import StringIO from mailman.app.lifecycle import create_list -from mailman.commands.cli_send_digests import Send +from mailman.commands.cli_digests import Digests from mailman.config import config from mailman.interfaces.member import DeliveryMode from mailman.runners.digest import DigestRunner @@ -42,6 +42,8 @@ from unittest.mock import patch class FakeArgs: def __init__(self): self.lists = [] + self.send = False + self.bump = False @@ -55,7 +57,7 @@ class TestSendDigests(unittest.TestCase): self._mlist.digests_enabled = True self._mlist.digest_size_threshold = 100000 self._mlist.send_welcome_message = False - self._command = Send() + 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. @@ -80,6 +82,7 @@ Subject: message 1 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() @@ -110,6 +113,7 @@ Subject: message 1 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() @@ -140,9 +144,10 @@ Subject: message 1 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_send_digests.sys.stderr', stderr): + with patch('mailman.commands.cli_digests.sys.stderr', stderr): self._command.process(args) self._runner.run() # The warning was printed to stderr. @@ -171,9 +176,10 @@ Subject: message 1 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_send_digests.sys.stderr', stderr): + with patch('mailman.commands.cli_digests.sys.stderr', stderr): self._command.process(args) self._runner.run() # The warning was printed to stderr. @@ -202,9 +208,10 @@ Subject: message 1 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_send_digests.sys.stderr', stderr): + with patch('mailman.commands.cli_digests.sys.stderr', stderr): self._command.process(args) self._runner.run() # The warning was printed to stderr. @@ -260,6 +267,7 @@ Subject: message 3 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() @@ -327,7 +335,9 @@ Subject: message 3 items = get_queue_messages('digest') self.assertEqual(len(items), 0) # Process all mailing list digests by not setting any arguments. - self._command.process(FakeArgs()) + 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. 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) |
