#! /usr/bin/env python
#
# Copyright (C) 1998 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

"""Send password reminders for all lists to all users.

Any arguments are taken as a list of addresses that become the focus - only
the subscribers on the list are attended to, all other subscribers are
ignored.  In addition, if any addresses are specified, a line is printed for
each list where that address is found.  (Otherwise operation is silent.)

We accumulate users and their passwords, and use the last list to send a
single message to each user with their complete collection of passwords,
rather than sending a single message for each password.

If mm_cfg.VIRTUAL_HOST_OVERVIEW is true, we further group users by the virtual
host the mailing lists are assigned to.  This is so that virtual domains are
treated like real separate machines.

"""

# This puppy should probably do lots of logging.

import sys, os, string, time, errno
import paths
from Mailman import MailList
from Mailman import mm_cfg
from Mailman import Utils

# Work around known problems with some RedHat cron daemons
import signal
signal.signal(signal.SIGCHLD, signal.SIG_DFL)



def waitall():
    """Return only when there are no forked subprocesses running."""
    try:
        while 1:
            os.wait()
    except os.error, (code, msg):
        if code == errno.ECHILD:
            # errno.ECHILD: "No child processes"
            return
        else:
            Utils.reraise()


# Give time for the delivery process-forks to clear every so often, to
# avoid saturation of the process table.  Set zero or negative for no
# pauses.
PAUSE_FREQUENCY = 10

def MailAllPasswords(mlist, hosts):
    """Send each user their complete list of passwords.

    The list can be any random one - it is only used for the message
    delivery mechanism.  Users are grouped by virtual host.
    """
    count = PAUSE_FREQUENCY
    mailman_owner = mm_cfg.MAILMAN_OWNER
    for host, users in hosts.items():
        subj = host + ' mailing list memberships reminder'
        for addr, data in users.items():
            table = []
            for l, r, p, u in data:
                if len(l) > 39:
                    table.append("%s\n           %-10s\n%s\n" % (l, p, u))
                else:
                    table.append("%-40s %-10s\n%s\n" % (l, p, u))
            header = ("%-40s %-10s\n%-40s %-10s"
                      % ("List", "Password // URL", "----", "--------"))
            text = Utils.maketext(
                'cronpass.txt',
                {'hostname': host,
                 'useraddr': addr,
                 'exreq'   : r,
                 'owner'   : mailman_owner,
                 })
            # add this to the end so it doesn't get wrapped/filled
            text = text + header + '\n' + string.join(table, '\n')
            mlist.SendTextToUser(subject = subj,
                                 recipient = addr,
                                 text = text,
                                 sender = mailman_owner,
                                 add_headers = ["X-No-Archive: yes",
                                                "Precedence: bulk"])
            count = count - 1
            if count == 0:
                # The pause that refreshes.
                waitall()
                count = PAUSE_FREQUENCY


def main():
    """Consolidate all the list/url/password info for each user, so we send 
    the user a single message with the info for all their lists on this
    site.
    """
    # constrain to specified lists, if any
    confined_to = sys.argv[1:]
    # Use this list for message delivery only
    a_public_list = None
    # Group lists by the assigned virtual host, if
    # mm_cfg.VIRTUAL_HOST_OVERVIEW is true.  Otherwise, there's only one key
    # in this dictionary: mm_cfg.DEFAULT_HOST_NAME.  Each entry in this
    # dictionary is a dictionary of user email addresses
    hosts = {}
    for listname in Utils.list_names():
        if confined_to and listname not in confined_to:
            continue
##        else:
##            print 'Processing list:', listname
        mlist = MailList.MailList(listname, lock=0)
        if not a_public_list and mlist.advertised:
            a_public_list = mlist
        if not mlist.send_reminders:
            continue
        listaddr = mlist.GetListEmail()
        listreq = mlist.GetRequestEmail()
        umbrella = mlist.umbrella_list
        # get host information
        if mm_cfg.VIRTUAL_HOST_OVERVIEW:
            host = mlist.host_name
        else:
            host = mm_cfg.DEFAULT_HOST_NAME
        #
        # each entry in this dictionary is a list of tuples of the following
        # form: (listaddr, listreq, password, url)
        users = hosts.get(host, {})
        badaddrs = []
        for addr, passwd in mlist.passwords.items():
            url = mlist.GetAbsoluteOptionsURL(addr)
            realaddr = mlist.GetUserSubscribedAddress(addr)
            if not realaddr:
                badaddrs.append(addr)
                continue
            recip = mlist.GetMemberAdminEmail(realaddr)
            userinfo = (listaddr, listreq, passwd, url)
            infolist = users.get(recip, [])
            infolist.append(userinfo)
            users[recip] = infolist
        hosts[host] = users
        # were there any addresses that are in the password dictionary but are
        # not subscribed?
        if badaddrs:
            mlist.Lock()
            try:
                for addr in badaddrs:
                    del mlist.passwords[addr]
                mlist.Save()
            finally:
                mlist.Unlock()
    if a_public_list:
        MailAllPasswords(a_public_list, hosts)


if __name__ == '__main__':
    main()
