freeswitch/scripts/python/freepy/fseventlistener.py

288 lines
9.6 KiB
Python

"""
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
"""
# dictionary of observers. key: event name, value: list of observers
self.event2observer = {}
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 addobserver(self, event_name, observer):
"""
@param event_name, eg "CHANNEL_ANSWER"
@param observer (instance of object that has an eventReceived() method
"""
observers = self.event2observer.get(event_name, [])
observers.append(observer)
self.event2observer[event_name] = observers
def dispatch2observers(self, event_name, event_xml_str, event_dom):
"""
called back by the underlying protocol upon receiving an
event from freeswitch. Currently subclasses must explicitly
call this method from their eventReceived method for observers
to get the message. TODO: move this call to FreeswitchEventListener
and use observer pattern instead of any subclassing.
"""
observers = self.event2observer.get(event_name, [])
for observer in observers:
observer.eventReceived(event_name, event_xml_str, event_dom)
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
self.protocol.__dict__["factory"] = self
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()