Commit c9464cb6 authored by Barry Warsaw's avatar Barry Warsaw

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.
parent 8e244768
# 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)
This diff is collapsed.
......@@ -18,14 +18,14 @@
"""The `send_digests` subcommand."""
__all__ = [
'Send',
'Digests',
]
import sys
from mailman.app.digests import maybe_send_digest_now
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
......@@ -34,10 +34,10 @@ from zope.interface import implementer
@implementer(ICLISubCommand)
class Send:
"""Send some mailing list digests right now."""
class Digests:
"""Operate on digests."""
name = 'send-digests'
name = 'digests'
def add(self, parser, command_parser):
"""See `ICLISubCommand`."""
......@@ -45,25 +45,47 @@ class Send:
command_parser.add_argument(
'-l', '--list',
default=[], dest='lists', metavar='list', action='append',
help=_("""Send the digests for this mailing list. Multiple --list
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, the
digests for all mailing lists will be sent if possible."""))
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`."""
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)
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:
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)
# 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)
......@@ -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('[email protected]')
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('[email protected]')
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', '[email protected]'))
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.
......
......@@ -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)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment