288 lines
9.6 KiB
Python
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()
|
|
|