""" 
FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
Copyright (C) 2005/2006, Anthony Minessale II <anthmct@yahoo.com>

Version: MPL 1.1

The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the
License.

The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application

The Initial Developer of the Original Code is
Anthony Minessale II <anthmct@yahoo.com>
Portions created by the Initial Developer are Copyright (C)
the Initial Developer. All Rights Reserved.

Contributor(s): Traun Leyden <tleyden@branchcut.com>
"""

import sys

from twisted.internet import reactor, defer
from twisted.protocols.basic import LineReceiver
from twisted.internet.protocol import Protocol, ClientFactory
from twisted.python import failure
import time, re
from time import strftime
from Queue import Queue
from freepy import request

"""
This class connects to freeswitch and listens for
events and calls callback with the events.

Example messages
=================

Content-Length: 675
Content-Type: text/event-xml

<event>
  <header name="force-contact" value="nat-connectile-dysfunction"></header>
  etc..
</event>
Content-Length: 875
Content-Type: text/event-xml

<event>
...
</event>

"""

class FreeswitchEventListener(LineReceiver):

    def __init__(self, conncb, discocb=None):
        self.delimiter='\n' # parent class uses this 
        self.conncb=conncb
        self.discocb=discocb
        self.bufferlines = []
        self.receiving_event = False # state to track if in <event>..</event>
        self.requestq = Queue() # queue of pending requests
        self.active_request = None # the current active (de-queued) request

    def connectionMade(self):
        self.conncb(self)
        
    def connectionLost(self, reason):
        if self.discocb:
            self.discocb(reason)        
        print "connectionLost: %s" % reason


    def login(self, passwd):
        """
        send login request
        """
        msg = "auth %s" % passwd
        req = request.LoginRequest()
        self.active_request = req
        self.transport.write("%s\n\n" % str(msg))
        return req.getDeferred()

    def sniff_events(self, output_type, events):
        """
        @param output_type - eg, xml or plain
        @param events - list of events, eg ['all']
        """
        event_list = " ".join(events)
        msg = "event %s %s" % (output_type, event_list)
        self.transport.write("%s\n\n" % str(msg))

    def sniff_custom_events(self, output_type, events):
        """
        when sniffing custom events, the CUSTOM keyword
        must be present in message
        http://wiki.freeswitch.org/wiki/Event_Socket#event
        
        @param output_type - eg, xml or plain
        @param events - list of events, eg ['all']
        """
        event_list = " ".join(events)
        msg = "event %s CUSTOM %s" % (output_type, event_list)
        self.transport.write("%s\n\n" % msg)

    def sniff_all_events(self, output_type):
        """
        @param output_type - eg, xml or plain
        """
        msg = "event %s all" % output_type
        self.transport.write("%s\n\n" % str(msg))

    def lineReceived(self, line):
        if not self.active_request:
            if line.find("<event>") != -1:
                self.receiving_event = True
            if self.receiving_event:
                self.bufferlines.append(line)
            if line.find("</event>") != -1:
                event_xml_str = "\n".join(self.bufferlines)
                self.eventReceived(event_xml_str)
                self.bufferlines = []
                self.receiving_event = False
        else:
            # we have an active request (seperate state machine)
            # tell the request to process the line, and tell us
            # if its finished or not.  if its finished, we remove it
            # as the active request so that a new active request will
            # be de-queued.
            finished = self.active_request.process(line)
            if finished == True:
                self.active_request = None

    def eventReceived(self, event_xml_str):
        """
        should be overridden by subclasses
        """
        raise Exception("This is an abstract class, should be overridden "
                        "in a subclass")

class FreeswitchEventListenerFactory(ClientFactory):

    def __init__(self, protoclass, host=None, passwd=None, port=None):
        """
        @param protoclass - a class (not instance) of the protocol
                            should be a subclass of a FreeswitchEventListener
        """

        self.protoclass=protoclass
        
        if host:
            self.host = host
        if passwd:
            self.passwd = passwd
        if port:
            self.port = port        
            
        self.protocol = None
        self.connection_deferred = None
        self.num_attempts = 0

    def reset(self):
        self.protocol = None
        self.connection_deferred = None
        
    def connect(self):
            
        if self.protocol:
            # if we have a protocol object, we are connected (since we always
            # null it upon any disconnection)
            return defer.succeed(self.protocol)

        #if self.connection_deferred:
            # we are already connecting, return existing dfrd
        #    return self.connection_deferred

        # connect and automatically login after connection
        if not self.connection_deferred:
            self.connection_deferred = defer.Deferred()
            self.connection_deferred.addCallback(self.dologin)
            self.connection_deferred.addErrback(self.generalError)
        reactor.connectTCP(self.host, self.port, self)
        return self.connection_deferred

    def conncb(self, protocol):
        self.protocol = protocol
        deferred2callback = self.connection_deferred
        self.connection_deferred = None
        deferred2callback.callback(self.protocol)


    def generalError(self, failure):
        print "General error: %s" % failure
        return failure

    def startedConnecting(self, connector):
        pass
    
    def buildProtocol(self, addr):
        return self.protoclass(self.conncb, self.discocb)
    
    def clientConnectionLost(self, connector, reason):
        print "clientConnectionLost! conn=%s, reason=%s" % (connector,
                                                            reason)
        self.connection_deferred = None        
        self.protocol = None
    
    def clientConnectionFailed(self, connector, reason):

        if self.num_attempts < 10000:
            self.num_attempts += 1            
            print "Connection to %s:%s refused, retrying attempt #%s in " \
                  "5 seconds" % (self.host, self.port, self.num_attempts)
            return reactor.callLater(5, self.connect)
        else:
            print "clientConnectionFailed! conn=%s, reason=%s" % (connector,
                                                                  reason)
            print ("Retry attempts exhausted, total attempts: %s" % 
                  self.num_attempts)
            deferred2callback = self.connection_deferred
            deferred2callback.errback(reason)

    def discocb(self, reason):
        print "disconnected.  reason: %s" % reason
        self.protocol = None

    def dologin(self, connectmsg):
        return self.protocol.login(self.passwd)
        

def test1():
    fel = FreeswitchEventListener
    factory = FreeswitchEventListenerFactory(protoclass=fel,
                                             host="127.0.0.1",
                                             port=8021,
                                             passwd="ClueCon")

    def connected(result):
        print "We connected, result: %s" % result
        events=['sofia::register','sofia::expire']
        factory.protocol.sniff_custom_events(output_type="xml", events=events)
        #factory.protocol.sniff_all_events(output_type="xml")
    
    def failure(failure):
        print "Failed to connect: %s" % failure
        
    d = factory.connect()
    d.addCallbacks(connected, failure)
    d.addErrback(failure)

    reactor.run()    
    
if __name__=="__main__":
    test1()

