summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/digests.py118
-rw-r--r--src/mailman/app/tests/test_digests.py237
-rw-r--r--src/mailman/commands/cli_digests.py91
-rw-r--r--src/mailman/commands/cli_send_digests.py69
-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.py101
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)