1 # Copyright (C) 2009 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Bulk message delivery."""
20 from __future__
import absolute_import
, unicode_literals
28 from mailman
.mta
.base
import BaseDelivery
31 # A mapping of top-level domains to bucket numbers. The zeroth bucket is
32 # reserved for everything else. At one time, these were the most common
45 class BulkDelivery(BaseDelivery
):
46 """Deliver messages to the MSA in as few sessions as possible."""
48 def __init__(self
, max_recipients
=None):
49 """See `BaseDelivery`.
51 :param max_recipients: The maximum number of recipients per delivery
52 chunk. None, zero or less means to group all recipients into one
54 :type max_recipients: integer
56 super(BulkDelivery
, self
).__init
__()
57 self
._max
_recipients
= (max_recipients
58 if max_recipients
is not None
61 def chunkify(self
, recipients
):
62 """Split a set of recipients into chunks.
64 The `max_recipients` argument given to the constructor specifies the
65 maximum number of recipients in each chunk.
67 :param recipients: The set of recipient email addresses
68 :type recipients: sequence of email address strings
69 :return: A list of chunks, where each chunk is a set containing no
70 more than `max_recipients` number of addresses. The chunk can
71 contain fewer, and no packing is guaranteed.
72 :rtype: list of sets of strings
74 if self
._max
_recipients
<= 0:
77 # This algorithm was originally suggested by Chuq Von Rospach. Start
78 # by splitting the recipient addresses into top-level domain buckets,
79 # using the "most common" domains. Everything else ends up in the
82 for address
in recipients
:
83 localpart
, at
, domain
= address
.partition('@')
84 domain_parts
= domain
.split('.')
85 bucket_number
= CHUNKMAP
.get(domain_parts
[-1], 0)
86 by_bucket
.setdefault(bucket_number
, set()).add(address
)
87 # Fill chunks by sorting the tld values by length.
89 for tld_chunk
in sorted(by_bucket
.values(), key
=len, reverse
=True):
91 chunk
.add(tld_chunk
.pop())
92 if len(chunk
) == self
._max
_recipients
:
95 # Every tld bucket starts a new chunk, but only if non-empty
99 # Be sure to include the last chunk, but only if it's non-empty.
103 def deliver(self
, mlist
, msg
, msgdata
):
104 """See `IMailTransportAgentDelivery`."""
106 for recipients
in self
.chunkify(msgdata
.get('recipients', set())):
107 chunk_refused
= self
._deliver
_to
_recipients
(
108 mlist
, msg
, msgdata
, recipients
)
109 refused
.update(chunk_refused
)