#!/usr/bin/python
#
# Best read in a 120-char wide window.
#
# A simple script to make sounds for mail in a POP3 mailbox. Attempts to find sounds by appending
# email address to sounddir; if a file exists, the file is played using the playcmd. For me, such
# files  contain recordings  of speech,  so my email is announced with speech rather than hearing
# random sounds.
#
#
# 0.1: Released version. Let me (hmv@port.ac.uk) know any improvements.
# 0.2: Copes with POP3 server being unavailable, and (in theory) with being
#      unable to login to POP3 server.
# 0.3: Updates to work when moved to new system
# 0.4: Minor cleanup,
#      Better messages,
#      Load configuration from configuration file
#      Option for being 'noisy' so information on actions can be seen
#      Command line options for usage and generating default config file
#      Tests whether 'sounddir' actually exists and is a directory
#      Added space when constructing playcmd to make it more likely that command will work,
#      Added a 'slash' when constructing 'soundfile' to make it more likely to work (don't care about Windows),
#      Cleanly handle interrupt

import sys
import os
import poplib
import time
import getopt
import ConfigParser

popserver = ''    # POP server to interrogate
username = ''     # Username to login with
password = ''     # Password to login with.
sounddir = ''     # Where sounds for email addresses are found
playcmd = ''      # Command used to play sounds
pause = ''        # Amount to wait after every POP session
#
# The values for the variables above are now loaded from a
# configuration file. These variables are *required* to be in the
# file.
#

noisy = False
# Be noisy or not. Optionally loaded from the configuration file

seen = []                                     # A list of seen UIDLs
configfile = "/home/mike/lib/popspeaker.config"
#    Default for where the configuration file is found

def loadoptions(options):
    """
    Process command line options and call any appropriate functions.
    """

    for opt, arg in options:
        if opt == "--defaultconfig":
            writedefaultconfig()
            sys.exit(0)
        if opt in ['-h', '--help', '--usage']:
            usage()
            sys.exit(0)

def usage():
    """
    Just output usage information
    """
    print """
Usage:
    popspeaker [-h] [--usage] [--help] [--defaultconfig]
        -h
        --usage
        --help
                  Display this message.
        --defaultconfig
                  Output a default configuration file for you to edit.
"""

def writedefaultconfig():
    """
    Output a default configuration file for user to edit.
    """
    print "# The default location for this file is ", configfile
    print """
# if you want to change this please edit the script and change the
# setting for 'configfile' which is found near the top of the script
# --CUT HERE--
# configuration file for popspeaker
[popspeaker]
# Section 'popspeaker' must exist but is the only section.
popserver = your-pop-server-name
# Which POP3 server to talk to
username = your-username
# The username to use to login
password = your-password
# The password for that account
playcmd = play
# Command line for a tool to play sounds that appear in the directory below
sounddir = directory
# This should be a directory on your system that contains sound files
# for each address that you want to announce. I usually have default
# sound files ("you-have-mail.wav") which I link to email addresses
# ("fred.smith@zonky.org") unless I have a custom announcement for a
# special person's email.
#
pause = 30
# How many seconds to pause after every connection to the POP3 server
noisy = True
# Whether to output details of what is happening. Silence is golden
# but perhaps worrying the first few times :)
#
# I would suggest leaving a blank comment at the end of this file as
# this avoids the 'no end of line' glitch preventing the last configuration
# item from being read.
#
"""    

def loadconfig(str):
    """
    Load configuration from specified file.
    """

    global popserver, username, password, playcmd, sounddir, pause, noisy
    
    config = ConfigParser.ConfigParser()      # Initialise parser
    try:
        file = open(str, "r")
    except:
        print "Cannot open configuration file '", str, "'. Please\ngenerate with 'popspeaker -defaultconfig'"
        sys.exit(1)
        
    config.readfp(file)                   # 'Give' file to parser
    try:
        popserver = config.get("popspeaker", "popserver")
    except:
        print "There is no value for the 'popserver' in the configuration file"
        sys.exit(1)
    try:
        username = config.get("popspeaker", "username")
    except:
        print "There is no value for the 'username' in the configuration file"
        sys.exit(1)
    try:
        password = config.get("popspeaker", "password")
    except:
        print "There is no value for the 'password' in the configuration file"
        sys.exit(1)
    try:
        playcmd = config.get("popspeaker", "playcmd")
    except:
        print "There is no value for the 'playcmd' in the configuration file"
        sys.exit(1)
    try:
        sounddir = config.get("popspeaker", "sounddir")
    except:
        print "There is no value for the 'sounddir' in the configuration file"
        sys.exit(1)
    try:
        pause = int(config.get("popspeaker", "pause"))
        # Must cast numbers!
    except:
        print "There is no value for the 'pause' in the configuration file"
        sys.exit(1)

    try:
        noisy = config.getboolean("popspeaker", "noisy")
    except:
        pass

    file.close()

def configchecks():
    """
    Go through and make some basic sanity checks on the configuration. Initially we just check if 'sounddir'
    exists and is a directory.
    """

    if not os.access(sounddir, os.F_OK):
        print "Error: Directory ", sounddir, " does not appear to exist"
        sys.exit(1)

def cleanfrom(str):
    """
    Takes an RFC2821 From header and returns an email address. Not safe (should exclude "../") and is just good
    enough for my purposes.
    """
    
    list = str.split()
    for item in list:
        if '@' in item:
            # If we find an '@' then this item is the email address
            str = item
            break
        
    if str[0] == "<":
        str = str[1:]
        # If first char is '<' then remove it
    if str[-1] == ">":
        str = str[:-1]
        # If last char is '>' then remove it
    return str


def playsound(str):
    """
    Uses playcmd and sounddir global variables, and a string parameter to play a sound.
    """
    
    soundfile = sounddir + "/" + str
    #print "Should ", soundfile, " be playable?"
    if os.access(soundfile, os.F_OK):
        if noisy:
            print "Announcing for: ", str
            print "Playcommand: ", playcmd + soundfile
        os.system(playcmd + " " + soundfile)

def iteratepop():
    """
    Uses popserver, username, and password global variables to iterate over a POP3 mailbox to attempt to
    play a sound for each From address.
    """
    
    try:
        mbox = poplib.POP3(popserver)
    except:
        print "Error: Cannot connect to POP3 server ", popserver
        return
    
    mbox.user(username)
    try:
        mbox.pass_(password)
    except:
        print "Error: Cannot login to pop server ", popserver
        return

    if noisy:
        n = mbox.stat()[0]
        if n == 1:
            print "Connected: ", n, "message found"
        else:
            print "Connected: ", n, "messages found"

    list = mbox.list()
    if list[0][:3] != "+OK":
        print "Error: POP3 LIST command failed:", list[0]
        return
    for item in list[1]:
        item = item.split()[0]
        try:
            uidl = mbox.uidl(item).split()[2]
        except:
            continue
        
        if not(uidl in seen):
            try:
                headers = mbox.top(item, 0)
            except:
                continue
            
            headers = headers[1]
            for head in headers:
                if head[:5] == "From:":
                    head = cleanfrom(head[6:])
                    playsound(head)
            seen.append(uidl)

    mbox.quit()

#
# Do the main thing ... repeatedly iterate over the mailbox attempting to play sounds for each From
# address.
#

try:
    options, rargs = getopt.getopt(sys.argv[1:], "h", ['help', 'usage', 'defaultconfig'])
except getopt.GetoptError:
    usage()
    sys.exit(1)

try:
    loadoptions(options)
    loadconfig(configfile)
    configchecks()

    if noisy:
        print "Starting to check pop server ", popserver, " every ", pause, " seconds"
        
    while True:
        iteratepop()
        time.sleep(pause)

except KeyboardInterrupt:
    if noisy:
        print "Exiting as you asked me to."
    sys.exit(0)
except SystemExit:
    sys.exit(0)
except:
    print "Unexpected error: ", sys.exc_info()[0]
