#!/usr/bin/env python
#
# Copyright (C) 2001,2002 Jason R. Mastaler <jason@mastaler.com>
#
# This file is part of TMDA.
#
# TMDA 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.  A copy of this license should
# be included in the file COPYING.
#
# TMDA 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 TMDA; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Pending queue manipulation tool.

Usage:  %(program)s [OPTIONS] [messages ... ]

Where:
    -c <file>
    --config-file <file>
       Specify a different configuration file other than ~/.tmdarc.

    -i
    --interactive
       Display a summary of each pending message and prompt for
       disposal (pass, show, release, delete, quit).  Implies
       --verbose.  (default)

    -p
    --pretend
       Don't actually operate on messages, just show what would have happened.
       Implies --verbose.

    -b
    --batch
       Operate non-interactively.  Use with caution.
       
    -r
    --release
       Release messages.

    -d
    --delete
       Delete messages.

    -s
    --summary
       Print a summary of pending messages along with a release address link.
       Implies --verbose.

    -S
    --show
       Display the full contents of the given message with $PAGER (usually
       the `more', or `less' program).
       
    -C
    --cache
       Operate only on messages which aren't stored in .msgcache (i.e,
       haven't yet been seen).  After operation, store the message in
       .msgcache.  Useful primarily in conjunction with the --summary
       option.
       
    -A
    --ascending
       Operate on messages in ascending order (default).

    -D
    --descending
       Operate on messages in descending order.
      
    -R <user@host>
    --recipient <user@host>
       Override the address used to create the magic release address.
       Normally, this is determined by parsing the `X-TMDA-Recipient'
       header which every pending message should contain.

    -Y <interval>
    --younger <interval>
       Operate only on messages younger than the time interval given in
       seconds (s), minutes (m), hours (h), days (d), weeks (w), months
       (M), or years (Y).

    -O <interval>
    --older <interval>
       Operate only on messages older than the time interval given in
       seconds (s), minutes (m), hours (h), days (d), weeks (w), months
       (M), or years (Y).

    -v
    --verbose
       Display output (default).

    -q
    --quiet
       Suppress output.  Not compatible with --interactive.

    -V
    --version
       Print TMDA version information and exit.
 
    -h
    --help
       Print this help message and exit.

    messages
       If one or more messages are provided, operate just on them.
       Otherwise, operate on all messages in the pending queue.
       
    Examples:

    (interactively operate on all pending messages)
    %(program)s 

    (interactively operate on just these messages)
    %(program)s 1012182077.5803.msg 1012939546.7870.msg

    (immediately release these messages from the pending queue)
    %(program)s -b -r 1012182077.5803.msg 1012939546.7870.msg

    (immediately delete all messages from the pending queue)
    %(program)s -b -d

    (silently and immediately delete all messages older than 30 days)
    %(program)s -q -b -d -O 30d
    
    (mail a summary report of all new pending messages)
    %(program)s -C -b -s | mail -s 'TMDA pending summary' jason
"""

import cStringIO
import getopt
import glob
import os
import rfc822
import sys
import time

try:
    import paths
except ImportError:
    pass

from TMDA import Version


program = sys.argv[0]
# Defaults
cache = None
command_recipient = None
descending = None
dispose = None
interactive = 1
older = None
pretend = None
summary = None
threshold = None
verbose = 1
younger = None


def usage(code, msg=''):
    print __doc__ % globals()
    if msg:
        print msg
    sys.exit(code)

try:
    opts, args = getopt.getopt(sys.argv[1:],
                               'c:ipbrdsSCADR:Y:O:vqVh', ['config-file=',
                                                          'interactive',
                                                          'pretend',
                                                          'batch',
                                                          'release',
                                                          'delete',
                                                          'summary',
                                                          'show',
                                                          'cache',
                                                          'ascending',
                                                          'descending',
                                                          'recipient=',
                                                          'younger=',
                                                          'older=',
                                                          'verbose',
                                                          'quiet',
                                                          'version',
                                                          'help'])
except getopt.error, msg:
    usage(1, msg)

for opt, arg in opts:
    if opt in ('-h', '--help'):
        usage(0)
    if opt == '-V':
        print Version.ALL
        sys.exit()
    if opt == '--version':
        print Version.TMDA
        sys.exit()
    elif opt in ('-c', '--config-file'):
        os.environ['TMDARC'] = arg
    elif opt in ('-i', '--interactive'):
        interactive = verbose = 1
    elif opt in ('-p', '--pretend'):
        pretend = verbose = 1
    elif opt in ('-b', '--batch'):
        interactive = 0
    elif opt in ('-r', '--release'):
        dispose = 'release'
    elif opt in ('-d', '--delete'):
        dispose = 'delete'
    elif opt in ('-s', '--summary'):
        summary = 1
    elif opt in ('-S', '--show'):
        dispose = 'show'
    elif opt in ('-C', '--cache'):
        cache = 1
    elif opt in ('-A', '--ascending'):
        descending = 0
    elif opt in ('-D', '--descending'):
        descending = 1
    elif opt in ('-R', '--recipient'):
        command_recipient = arg
    elif opt in ('-Y', '--younger'):
        younger = 1
        threshold = arg
    elif opt in ('-O', '--older'):
        older = 1
        threshold = arg
    elif opt in ('-q', '--quiet'):
        verbose = 0
    elif opt in ('-v', '--verbose'):
        verbose = 1


from TMDA import Cookie
from TMDA import Defaults
from TMDA import Util


def cprint(verbose=1, *strings):
    """Conditionally print one or more strings."""
    if verbose:
        for s in strings:
            print s,
        print
    else:
        return


def confirm_accept_address(recipient, msg):
    (timestamp, pid, suffix) = msg.split('.')
    return Cookie.make_confirm_address(recipient, timestamp, pid, 'accept')


def confirm_done_address(recipient, msg):
    (timestamp, pid, suffix) = msg.split('.')
    return Cookie.make_confirm_address(recipient, timestamp, pid, 'done')


def release(headers, body, recipient):
    """Release a message from the pending queue."""
    del headers['x-tmda-recipient']
    # Add the released date in a header.
    headers['X-TMDA-Released'] = Util.unixdate()
    # Collect the envelope sender to pass to sendmail.
    return_path = headers.getaddr('return-path')[1]
    # Inject the message.
    Util.sendmail(headers, body, recipient, return_path)


def main():
    global cache
    global command_recipient
    global descending
    global dispose
    global interactive
    global older
    global summary
    global threshold
    global verbose
    global younger
    

    os.chdir(Defaults.DATADIR + 'pending')

    msgs = args
    if not msgs:
        msgs = glob.glob('*.*.msg')

    msgs.sort()
    if descending:
        msgs.reverse()
    total = len(msgs)
    count = 0
    
    if dispose is None:
        dispose_def = 'pass'
    else:
        dispose_def = dispose

    if os.path.exists(Defaults.DELIVERED_CACHE):
        delcache = Util.unpickle(Defaults.DELIVERED_CACHE)
    else:
        delcache = []
    if cache:
        if os.path.exists(Defaults.PENDING_CACHE):
            msgcache = Util.unpickle(Defaults.PENDING_CACHE)
        else:
            msgcache = []
        
    for msg in msgs:
        count = count + 1
        if not os.path.exists(msg):
            cprint(verbose, msg, 'not found!')
        else:
            delivered = None
            for dict in delcache:
                if dict.has_key(msg):
                    delivered = 1
                    break
            if delivered:
                if dispose == 'delete' and not interactive:
                    # continue if we are running in batch/delete mode,
                    # else delivered messages will never be removed
                    # from disk
                    pass
                else:
                    # skip delivered messages
                    continue
            if threshold:
                threshold_secs = Util.seconds(threshold)
                now = '%d' % time.time()
                min_time = int(now) - int(threshold_secs)
                msg_time = int(msg.split('.')[0])
                if (younger and msg_time < min_time) or \
                   (older and msg_time > min_time):
                    # skip this message
                    continue
            if cache:
                if msg in msgcache:
                    # skip this message
                    continue
                else:
                    msgcache.insert(0, msg)
            fp = cStringIO.StringIO(open(msg, 'r').read())
            headers = rfc822.Message(fp)
            body = fp.read()
            msg_size = len(body)
            bytes = 'bytes'
            if msg_size == 1:
                bytes = bytes[:-1]
            recipient_address = command_recipient
            if not recipient_address:
                recipient_address = headers.getheader('x-tmda-recipient')
            # Pass over the message if it lacks X-TMDA-Recipient and we
            # aren't using `-R'.
            if not recipient_address:
                cprint(verbose,
                       "can't determine recipient address, skipping", msg)
                continue
            if summary or interactive:
                print
                print msg, "(%s of %s / %s %s)" % (count,
                                                   total,
                                                   msg_size,
                                                   bytes)
                #print '  >> Sent:', Util.unixdate(int(msg.split('.')[0]))
                for hdr in ('date', 'from', 'to', 'subject'):
                    print "%s %s: %s" % ('  >>',
                                         hdr.capitalize()[:4].rjust(4),
                                         headers.getheader(hdr, 'None'))
                if summary:
                    print '<mailto:%s>' % (confirm_accept_address
                                           (recipient_address, msg))
            if interactive:
                try:
                    inp = raw_input(
                        '([p]ass / [s]how / [r]elease / [d]elete / [q]uit) [%s]: '
                        % dispose_def)
                    ans = inp[0:1].lower()
                    if ans == "":
                        dispose = dispose_def
                    elif ans == "p":
                        dispose = 'pass'
                    elif ans == "s":
                        dispose = 'show'
                    elif ans == "r":
                        dispose = 'release'
                    elif ans == 'd':
                        dispose = 'delete'
                    elif ans == "q":
                        break
                    else:
                        print '\n', "I don't understand %s" % (`inp`)
                        dispose = 'pass'
                except KeyboardInterrupt:
                    print
                    break
            # Optionally dispose of the message
            message = '%s %s' % (dispose, msg)
            if pretend:
                message = message + ' (not)'
            if dispose:
                cprint(verbose, '\n', message)
            if not pretend:
                if dispose == 'release':
                    release(headers, body,
                            confirm_done_address(recipient_address, msg))
                elif dispose == 'delete':
                    os.unlink(msg)
                elif dispose == 'pass':
                    continue
                elif dispose == 'show':
                    Util.pager(msg)
                    if interactive:
                        count = count - 1
                        msgs.insert(msgs.index(msg), msg)
                    
    if cache:
        # Trim tail entries off if necessary, and then save the cache.
        msgcache = msgcache[:Defaults.PENDING_CACHE_LEN]
        Util.pickleit(msgcache, Defaults.PENDING_CACHE)


# This is the end my friend.
if __name__ == '__main__':
    main()
