# Copyright (C) 2013 by the Free Software Foundation, Inc.
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

"""Handler to limit a member's posts to a list to M per calendar day or M per
time period and/or limit the total number of posts to the to the list to L in
the same period.

This handler should go in the pipeline before Moderate.

This file should be copied to Mailman/Handlers/PostLimit.py

To activate it for a particular list put the following code, edited for the
parameters you want, in lists/LISTNAME/extend.py

-------------------------- cut ------------
import copy
from Mailman import mm_cfg
def extend(mlist):
    if not hasattr(mlist, 'pipeline'):
        mlist.pipeline = copy.copy(mm_cfg.GLOBAL_PIPELINE)
    if 'PostLimit' not in mlist.pipeline:
        mlist.pipeline.insert(mlist.pipeline.index('Moderate'), 'PostLimit')
    if not hasattr(mlist, 'post_member_limit'):
        # To limit the number of member posts per period, set the following
        # to the limit.
        mlist.post_member_limit = 0
    if not hasattr(mlist, 'post_list_limit'):
        # To limit the number of list posts per period, set the following
        # to the limit.
        mlist.post_list_limit = 0
    if not hasattr(mlist, 'post_period'):
        # To set the time period, set the following.
        #  <=0 -> current day
        #   >0 -> number of hours
        mlist.post_period = 0
-------------------------- cut ------------
"""

import time

from Mailman import Errors
from Mailman.i18n import _

# L if > 0, is the maximum number of list posts allowed in the period
# M if > 0, is the maximum number of per member posts allowed in the period
# PERIOD is an integer that defines the period
#  <=0 -> the current calendar day (local time)
#   >0 -> the length of the period in hours

# Do not set these here. Set the corresponding list attributes in the list's
# extend.py or via withlist or config_list (see above).
L = 0
M = 0
PERIOD = 0



def process(mlist, msg, msgdata):
    global L, M, PERIOD
    if msgdata.get('approved'):
        return
    if hasattr(mlist, 'post_list_limit'):
        L = mlist.post_list_limit
    if hasattr(mlist, 'post_member_limit'):
        M = mlist.post_member_limit
    if hasattr(mlist, 'post_period'):
        PERIOD = mlist.post_period
    if L <= 0 and M <= 0:
        return
    # First of all, is the poster a member or not (same test as
    # Moderate.py)?
    for sender in msg.get_senders():
        if mlist.isMember(sender):
            break
    else:
        sender = None
    if not sender and L <= 0:
        # Sender is not a member and we're not limiting the list, let
        # Moderate handle it.
        return

    # If this is the first time for this list, we need to create the
    # dictionary for post counting
    if not hasattr(mlist, 'post_counts'):
        mlist.post_counts = {}
    now = time.time()
    lposts = mlist.post_counts.setdefault('list', [])
    lposts = drop_old(lposts, now)
    mposts = mlist.post_counts.setdefault(sender, [])
    mposts = drop_old(mposts, now)
    if (L <= 0 and len(mposts) < M or
            M <= 0 and len(lposts) < L or
            len(mposts) < M and len(lposts) < L):
        lposts.append(now)
        mlist.post_counts['list'] = lposts
        mposts.append(now)
        mlist.post_counts[sender] = mposts
        return
    if PERIOD <= 0:
        period = _('today')
    else:
        period = _('in the last %(PERIOD)s hours')
    if M > 0 and len(mposts) >= M:
        what = _('You have already sent %(M)s')
    else:
        what = _('There have already been %(L)s')
    rmsg = _('%(what)s posts to this list %(period)s.')
    raise Errors.RejectMessage, rmsg



def drop_old(posts, now):
    new_posts = []
    for post in posts:
        if PERIOD > 0:
            if post + (PERIOD * 3600) > now:
                new_posts.append(post)
        else:
            if (time.localtime(post)[7] == time.localtime(now)[7] and
                    time.localtime(post)[0] == time.localtime(now)[0]):
                new_posts.append(post)
    return new_posts

