#!/usr/bin/env python
# encoding: utf-8
"""
conf_tracker.py

Created by João Mesquita on 2010-12-25.
"""

import sys
import os
from urllib import unquote_plus
import ConfigParser
import logging
import logging.handlers
from ESL import ESLconnection
from datetime import datetime
import smtplib

from paste.util.converters import aslist

# We don't know which version of python we might be using...
try:
    from email.mime.text import MIMEText
except ImportError:
    from email.MIMEText import MIMEText
    
from threading import Thread

from sqlalchemy import create_engine, and_, desc
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from sqlalchemy.pool import NullPool

from mako.template import Template

import ldap

# This is only needed because we are SCPing...
import subprocess

# For testing purposes only...
sys.path.append('/Users/jmesquita/Documents/Programacao/ppdi/ppdi')
sys.path.append('/home/fsdeveloper/ppdi')

from ppdi.model import Base, Conference, ConferenceAction, ModeratorPin



LEVELS = {'debug': logging.DEBUG,
          'info': logging.INFO,
          'warning': logging.WARNING,
          'error': logging.ERROR,
          'critical': logging.CRITICAL}


def copyFileSftp(config, f, host):
    """Copy the file from server using SFTP"""
    log = logging.getLogger('copyFileSftp')
    try:
        cmd = 'scp %s@%s:%s %s' % (config.get('mp3', 'ssh_user'), host, f, config.get('mp3', 'store_from'))
        log.info('Executing command: %s' % cmd)
        retcode = subprocess.call(cmd, shell=True)
        if retcode != 0:
            raise Exception('Could not complete execution because child returned %d' % retcode)
    except OSError, e:
        raise e
    return

def transcodeFile(config, f, confObj):
    """Transcode the file to MP3"""
    log = logging.getLogger('transcodeFile')
    try:
        filename = f.split('/').pop()
        cmd = '%s -V2 %s %s' % (config.get('mp3', 'lame'), os.path.join(config.get('mp3', 'store_from'),filename), os.path.join(config.get('mp3', 'store_to'), filename[:-3] + 'mp3'))
        log.info('Executing command: %s' % cmd)
        retcode = subprocess.call(cmd, shell=True)
        if retcode != 0:
            raise Exception('Could not complete execution because child returned %d' % retcode)
    except OSError, e:
        raise e
    return

def sendEmail(config, f, confObj, owner_obj):
    log = logging.getLogger('sendEmail')
    """Send the email"""
    subject_tmpl = Template(config.get('email', 'subject'), input_encoding='utf-8', output_encoding='utf-8')
    email_tmpl = Template(filename=config.get('email', 'template'), input_encoding='utf-8', output_encoding='utf-8')
    email_from = config.get('email', 'from')

    # Authenticate the user!
    Base = config.get('ldap', 'base')
    
    Scope = ldap.SCOPE_SUBTREE
    Filter = "(&(objectClass=user)(sAMAccountName="+owner_obj.username+"))"
    Attrs = ["displayName", "mail"]

    # Set the LDAP timeout for the connection.
    ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, config.getint('ldap', 'timeout'))

    for Server in aslist(config.get('ldap','hosts'), sep=','):
        Server = 'ldap://' + Server
        log.info('Connecting to server: %s' % Server)
        l = ldap.initialize(Server)
        l.protocol_version = 3
        l.set_option(ldap.OPT_REFERRALS, 0)
        try:
            l.simple_bind_s(config.get('ldap', 'username'), config.get('ldap', 'password'))
            user = l.search_ext_s(Base, Scope, Filter, Attrs, timeout=10)
            Name,Attrs = user[0]
            Name,Attrs = user[0]
            email_to = None
            displayName = None
            if hasattr(Attrs, 'has_key') and Attrs.has_key('mail'):
                email_to = Attrs['mail'][0]
            else:
                log.warning('User %s has not email set. Not sending email.' % owner_obj.username)
                return
            if hasattr(Attrs, 'has_key') and Attrs.has_key('displayName'):
                displayName = Attrs['displayName'][0]

            msg = MIMEText(email_tmpl.render(displayName=displayName,
                            email_to=email_to,
                            conf_name=confObj.name,
                            conf_recording=confObj.recording,
                            url=config.get('http', 'root')+'/main/reports/report_detail/'+confObj.id,
                            conf_date=confObj.ended), 'html')
            msg['Subject'] = subject_tmpl.render(displayName=displayName,
                                            email_to=email_to,
                                            conf_name=confObj.name,
                                            conf_recording=confObj.recording,
                                            conf_date=confObj.ended)
            msg['From'] = email_from
            msg['To'] = email_to

            if config.getboolean('email', 'internal_server'):
                log.debug(msg.as_string())
                p = subprocess.Popen(config.get('email', 'command'), stdin=subprocess.PIPE, shell=True)
                p.communicate(input=msg.as_string())
                p.stdin.close()
                log.debug('Message sent.')
            else:
                s = smtplib.SMTP(config.get('email', 'server'), config.getint('email', 'port'))
                s.ehlo()
                s.starttls()
                s.ehlo()
                s.login(config.get('email', 'username'), config.get('email', 'password'))
                s.sendmail(email_from,[email_to],msg.as_string())
                s.quit()
            return
        except ldap.SERVER_DOWN, e:
            log.warning('Server %s is down!' % Server)

            if aslist(config.get('ldap', 'hosts'), sep=',').pop() in Server:
                # We are the last server, so ...
                log.critical('All LDAP servers are down. Last server tried was: %s. Not sending email.' % Server)
                raise(e)
            else:
                continue
        except ldap.LDAPError, e:
            log.critical('Critical error while connecting to ldap %s. Error: %s' % (Server, e))
            raise(e)

        # If we get here, it means that we authenticated just fine. Break the loop.
        break

def postRecordingThread(config, f, confObj, host, ownerObj):
    """Process the post recording details"""
    log = logging.getLogger('postRecordingThread')
    try:
        if config.getboolean('mp3', 'use_ssh'):
            copyFileSftp(config, f, host)
            
        transcodeFile(config, f, confObj)
        sendEmail(config, f, confObj, ownerObj)
    except Exception, e:
        log.critical('Could not process the thread because: %s' % e)
    return

def getUnquotedHeader(e, h):
    """Return the unquoted header, checking for null"""
    log = logging.getLogger('getUnquotedHeader')
    hs = e.getHeader(h)
    if hs is not None:
        hs = unquote_plus(hs)
    return hs

def configureLogging(config):
    """Configure logging"""
    # create logger
    logger = logging.getLogger("")
    logger.setLevel(LEVELS.get(config.get('logging', 'level'), logging.NOTSET))
    # create console handler and set level to debug
    ch = logging.handlers.RotatingFileHandler(sys.argv[0]+'.log')
    ch.setLevel(logging.DEBUG)
    # create formatter
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s")
    # add formatter to ch
    ch.setFormatter(formatter)
    # add ch to logger
    logger.addHandler(ch)
    
    if config.getboolean('logging', 'stdout'):
        ch = logging.StreamHandler()
        ch.setLevel(LEVELS.get(config.get('logging', 'level'), logging.NOTSET))
        ch.setFormatter(formatter)
        logger.addHandler(ch)

def main(config):
            
    configureLogging(config)
    log = logging.getLogger('main')
    
    pw = config.get('host', 'password')
    h = config.get('host', 'host')
    p = config.get('host', 'port')
    
    #Setting up the DB connection
    log.info('Connecting to DB on: %s ' % (config.get('database', 'url')))

    if ("sqlite" in config.get('database', 'url')):
        engine = create_engine( config.get('database', 'url'), poolclass=NullPool )
        log.warning('You are using SQLite and we will use NullPool as pooling engine.')
    else:
        engine = create_engine( config.get('database', 'url'), pool_recycle=3600 )


    sm = sessionmaker( autoflush=False, autocommit=False, bind=engine)
    Session = scoped_session( sm )
    
    log.info('Connecting to: %s:%s ' % (h, p))
    con = ESLconnection(h, p, pw)
    con.events('plain', 'CUSTOM conference::maintenance')
    while con.connected():
        e = con.recvEvent()
        if e is None:
            continue
        log.debug('Received event:\n%s%30s' % (e.serialize(), '='))
        conf_uuid = getUnquotedHeader(e, 'Conference-Unique-ID')
        conf_profile = getUnquotedHeader(e, 'Conference-Profile-Name')
        conf_name = getUnquotedHeader(e, 'Conference-Name')
        conf_size = getUnquotedHeader(e, 'Conference-Size')
        caller_id_name = getUnquotedHeader(e, 'Caller-Caller-ID-Name')
        caller_id_number = getUnquotedHeader(e, 'Caller-Caller-ID-Number')
        member_id = getUnquotedHeader(e, 'Member-ID')
        member_type = getUnquotedHeader(e, 'Member-Type')
        action = getUnquotedHeader(e, 'Action')
        event_date = getUnquotedHeader(e, 'Event-Date-Local')
        if event_date is not None:
            # Check the python version, because strptime does not exist in it.
            if sys.version_info[2] < 5:
                spl = event_date.split('-')
                year = int(spl[0])
                month = int(spl[1])
                spl2 = spl[2].split(' ')
                day = int(spl2[0])
                spl3 = spl2[1].split(':')
                hour = int(spl3[0])
                minutes = int(spl3[1])
                seconds = int(spl3[2])
                event_date = datetime(year=year, month=month, day=day, hour=hour, minute=minutes, second=seconds)
            else:
                event_date = datetime.strptime(event_date, '%Y-%m-%d %H:%M:%S')
        
        try:
            owner = Session.query(ModeratorPin).get(conf_name)
        except Exception, e:
            log.critical(e)
        if owner is None:
            log.critical('Error because we could not get owner.')
            continue
            
        confObj = Session.query(Conference).get(conf_uuid)
        if confObj is None:
            log.debug('Conference does not exist, we must create one.')
        
        if action == 'add-member':
            log.info('Member added...')
            if conf_size == '1':
                confObj = Conference(conf_uuid, event_date, unicode(conf_name), unicode(conf_profile))
                owner.conferences.append(confObj)
                Session.add(confObj)
                Session.commit()
                log.debug('Conference %s has been created.' % conf_name)
                
        elif action == 'del-member':
            if conf_size == '0':
                confObj.ended = event_date
                log.debug('Action taken on %s. %s' % (conf_name, action))
                actionObj = ConferenceAction(unicode(action), 
                                            unicode(caller_id_name),
                                            unicode(caller_id_number),
                                            unicode(conf_size),
                                            unicode(member_type),
                                            event_date,
                                            confObj.id)
                confObj.actions.append(actionObj)
                Session.add(actionObj)

                Session.commit()
                log.debug('Conference %s has been destroyed.' % conf_name)
                continue
                

        
        log.debug('Action taken on %s. %s' % (conf_name, action))

        # This action comes after the conference has ended.
        if action == 'stop-recording':
            confObj.recording = os.path.join(config.get('mp3', 'store_to'), getUnquotedHeader(e, 'Path').split('/').pop()[:-3] + 'mp3')
            host = unicode(getUnquotedHeader(e, 'FreeSWITCH-IPv4'))
            try:
                owner_obj = Session.query(ModeratorPin).get(confObj.owner)
            except Exception, e:
                log.critical('Could not find owner. Db corruption? %s' % e)
                continue
                
            t = Thread(target=postRecordingThread, args=(config, getUnquotedHeader(e, 'Path'), confObj, host, owner_obj))
            log.debug('Starting thread %s' % t)
            t.start()
            log.debug('Thread started...')
            
            
        try:                
            actionObj = ConferenceAction(unicode(action), 
                                        unicode(caller_id_name),
                                        unicode(caller_id_number),
                                        unicode(conf_size),
                                        unicode(member_type),
                                        event_date,
                                        confObj.id)
            confObj.actions.append(actionObj)
            Session.add(actionObj)

            Session.commit()
        except Exception, err:
            log.critical('Could not create action because %s. Event is %s' % (err, e.serialize()))
                
    log.info('Disconnected...')

if __name__ == '__main__':    
    config = ConfigParser.RawConfigParser()

    try:
        f = open(sys.argv[1])
        config.readfp(f)
    except Exception:
        f = open('conf_tracker.cfg')
        config.readfp(f)
        
    if config.getboolean('daemon', 'daemon'):
        try:
            import daemon
            import lockfile
        except ImportError, e:
            print 'We cant do daemon because daemon is not installed.'
            sys.exit(0)
        
        context = daemon.DaemonContext()
        context.files_preserve = [f,]
        with context:
            main(config)
        
    main(config)

