diff --git a/scripts/socket/freepy/INSTALL b/scripts/socket/freepy/INSTALL new file mode 100644 index 0000000000..9e5075dec4 --- /dev/null +++ b/scripts/socket/freepy/INSTALL @@ -0,0 +1,4 @@ + +- Add /path/to/freeswitch/scripts/socket to your PYTHONPATH +- Copy/Paste the code from test1() in fshelper.py to your own test module +- Adapt code as needed and run \ No newline at end of file diff --git a/scripts/socket/freepy/README b/scripts/socket/freepy/README new file mode 100644 index 0000000000..9782c4b868 --- /dev/null +++ b/scripts/socket/freepy/README @@ -0,0 +1,3 @@ + +Socket library to interface w/ freeswitch mod_event_socket from Twisted python applications. + diff --git a/scripts/socket/freepy/__init__.py b/scripts/socket/freepy/__init__.py new file mode 100644 index 0000000000..0d1cea37a1 --- /dev/null +++ b/scripts/socket/freepy/__init__.py @@ -0,0 +1,336 @@ +""" +FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +Copyright (C) 2005/2006, Anthony Minessale II + +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 +Portions created by the Initial Developer are Copyright (C) +the Initial Developer. All Rights Reserved. + +Contributor(s): Traun Leyden +""" + +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 + +""" +freepy library -- connect to freeswitch mod_socket_event via python/twisted + +All commands currently use api instead of bgapi. For the networking model +used (twisted), this seems to work well and is simpler. + +""" + +class FreepyDispatcher(LineReceiver): + + def __init__(self, conncb, discocb=None): + self.delimiter='\n' # parent class uses this + self.conncb=conncb + self.discocb=discocb + 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.requestq.put(req) + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def confdialout(self, conf_name, sofia_url, bgapi=True): + """ + Instruct conference to join a particular user via dialout + @param conf_name - the name of the conference (arbitrary) + @param party2dial - a freeswitch sofia url, eg, sofia/mydomain.com/foo@bar.com + @return - a deferred that will be called back with a string like: + Reply-Text: +OK Job-UUID: 4d410a8e-2409-11dc-99bf-a5e17fab9c65 + + + """ + if bgapi == True: + msg = "bgapi conference %s dial %s" % (conf_name, + sofia_url) + req = request.BgDialoutRequest() + else: + msg = "api conference %s dial %s" % (conf_name, + sofia_url) + req = request.DialoutRequest() + self.requestq.put(req) + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def originate(self, party2dial, dest_ext_app, bgapi=True): + + if bgapi == True: + msg = "bgapi originate %s %s" % (party2dial, + dest_ext_app) + req = request.BgDialoutRequest() + else: + msg = "api originate %s %s" % (party2dial, + dest_ext_app) + req = request.DialoutRequest() + self.requestq.put(req) + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def listconf(self, conf_name): + """ + List users in a conf + @param conf_name - the name of the conference (arbitrary) + @return - a deferred that will be called back with an array + of models.ConfMember instances + """ + msg = "api conference %s list" % (conf_name) + req = request.ListConfRequest() + self.requestq.put(req) + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + + def confkick(self, member_id, conf_name, bgapi=False): + """ + Kick member_id from conf + conf_name - name of conf + member_id - member id of user to kick, eg, "7" + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + if bgapi == True: + msg = "bgapi conference %s kick %s" % (conf_name, member_id) + req = request.BgConfKickRequest() + else: + msg = "api conference %s kick %s" % (conf_name, member_id) + req = request.ConfKickRequest() + self.requestq.put(req) + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def confdtmf(self, member_id, conf_name, dtmf, bgapi=False): + """ + Send dtmf to member_id or to all members + conf_name - name of conf + member_id - member id of user to kick, eg, "7" + dtmf - a single dtmf or a string of dtms, eg "1" or "123" + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + print "confdtmf called" + if bgapi == True: + msg = "bgapi conference %s dtmf %s %s" % \ + (conf_name, member_id, dtmf) + req = request.BgApiRequest() + else: + msg = "api conference %s dtmf %s %s" % \ + (conf_name, member_id, dtmf) + req = request.ApiRequest() + self.requestq.put(req) + print "sending to fs: %s" % msg + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def confsay(self, conf_name, text2speak, bgapi=False): + """ + Speak text all members + conf_name - name of conf + dtmf - text to speak + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + if bgapi == True: + msg = "bgapi conference %s say %s" % \ + (conf_name, text2speak) + req = request.BgApiRequest() + else: + msg = "api conference %s say %s" % \ + (conf_name, text2speak) + req = request.ApiRequest() + self.requestq.put(req) + print "sending to fs: %s" % msg + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def confplay(self, conf_name, snd_url, bgapi=False): + """ + Play a file to all members + conf_name - name of conf + dtmf - text to speak + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + if bgapi == True: + msg = "bgapi conference %s play %s" % \ + (conf_name, snd_url) + req = request.BgApiRequest() + else: + msg = "api conference %s play %s" % \ + (conf_name, snd_url) + req = request.ApiRequest() + self.requestq.put(req) + print "sending to fs: %s" % msg + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def confstop(self, conf_name, bgapi=False): + """ + Stop playback of all sound files + conf_name - name of conf + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + if bgapi == True: + msg = "bgapi conference %s stop" % \ + (conf_name) + req = request.BgApiRequest() + else: + msg = "api conference %s stop" % \ + (conf_name) + req = request.ApiRequest() + self.requestq.put(req) + print "sending to fs: %s" % msg + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + + def showchannels(self, bgapi=False): + """ + Get a list of all live channels on switch + + returns - a deferred that will be called back with a result + + + + 21524b8c-6d19-11dc-9380-357de4a7a612 + 2007-09-27 11:46:01 + sofia/test/4761 + CS_LOOPBACK + FreeSWITCH + 0000000000 + + outgoing2endpoint-6207463 + echo + + PCMU + 8000 + PCMU + 8000 + + ... + + """ + if bgapi == True: + msg = "bgapi show channels as xml" + req = request.BgApiRequest() + else: + msg = "api show channels as xml" + req = request.ApiRequest() + self.requestq.put(req) + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def sofia_status_profile(self, profile_name, bgapi=False): + # DO NOT USE - TOTALLY BROKEN + # FS DOES NOT RETURN XML IN THIS CASE + if bgapi == True: + msg = "bgapi sofia status profile %s as xml" % (profile_name) + req = request.BgApiRequest() + else: + msg = "api sofia status profile %s as xml" % (profile_name) + req = request.ApiRequest() + self.requestq.put(req) + print "sending to fs: %s" % msg + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + def sofia_profile_restart(self, sofia_profile_name, bgapi = False): + if bgapi == True: + msg = "bgapi sofia profile %s restart" % \ + (sofia_profile_name) + req = request.BgApiRequest() + else: + msg = "api sofia profile %s restart" % \ + (sofia_profile_name) + req = request.ApiRequest() + self.requestq.put(req) + print "sending to fs: %s" % msg + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + + def killchan(self, uuid, bgapi = False): + if bgapi == True: + msg = "bgapi killchan %s" % (uuid) + req = request.BgApiRequest() + else: + msg = "api killchan %s" % (uuid) + req = request.ApiRequest() + self.requestq.put(req) + print "sending to fs: %s" % msg + self.transport.write("%s\n\n" % msg) + return req.getDeferred() + + + + + def lineReceived(self, line): + if not self.active_request: + # if no active request, dequeue a new one + if self.requestq.empty(): + # we are receiving data from fs without an + # active request pending. that means that + # there is a bug in the protocol handler + # (or possibly in fs) + raise Exception("Received line: %s w/ no pending requests" % line) + self.active_request = self.requestq.get() + + # 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 + diff --git a/scripts/socket/freepy/apirequest.sm b/scripts/socket/freepy/apirequest.sm new file mode 100644 index 0000000000..b8575aa92d --- /dev/null +++ b/scripts/socket/freepy/apirequest.sm @@ -0,0 +1,96 @@ +%start MainMap::Startup +%class ApiRequest + +%map MainMap +%% + +Startup +{ + ApiResponse + ApiResponseStarted + { + } +} + +ApiResponseStarted +{ + ContentLength + ContentPreStarted + { + } + +} + +ContentPreStarted +{ + BlankLine + ContentStarted + { + } + +} + +ContentStarted +{ + ProcessLine(line) + [ctxt.add_content(line) == True] + Startup + { + setRequestFinished(); callbackDeferred(ctxt.getResponse()); + } + + ProcessLine(line) + nil + { + // for some reason, have to add doNothing() here or + // importing smc will fail. looks like smc bug. + doNothing(); + } + + + +} + + + + + +Default +{ + BlankLine + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + ContentFinished + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + ContentLength + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + ApiResponse + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + ProcessLine(line) + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } +} + +%% \ No newline at end of file diff --git a/scripts/socket/freepy/apirequest_sm.py b/scripts/socket/freepy/apirequest_sm.py new file mode 100644 index 0000000000..4be3787f4e --- /dev/null +++ b/scripts/socket/freepy/apirequest_sm.py @@ -0,0 +1,206 @@ + +# DO NOT MODIFY THIS CODE - AUTOMATICALLY GENERATED BY SMC + +import statemap + +class ApiRequestState(statemap.State): + + def Entry(self, fsm): + pass + + def Exit(self, fsm): + pass + + def ApiResponse(self, fsm): + self.Default(fsm) + + def BlankLine(self, fsm): + self.Default(fsm) + + def ContentFinished(self, fsm): + self.Default(fsm) + + def ContentLength(self, fsm): + self.Default(fsm) + + def ProcessLine(self, fsm, line): + self.Default(fsm) + + def Default(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write('TRANSITION : Default\n') + msg = "\n\tState: %s\n\tTransition: %s" % ( + fsm.getState().getName(), fsm.getTransition()) + raise TransitionUndefinedException, msg + +class MainMap_Default(ApiRequestState): + + def BlankLine(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.BlankLine()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def ContentFinished(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.ContentFinished()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def ContentLength(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.ContentLength()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def ApiResponse(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.ApiResponse()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def ProcessLine(self, fsm, line): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.ProcessLine(line)\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + +class MainMap_Startup(MainMap_Default): + + def ApiResponse(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Startup.ApiResponse()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.ApiResponseStarted) + fsm.getState().Entry(fsm) + +class MainMap_ApiResponseStarted(MainMap_Default): + + def ContentLength(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.ApiResponseStarted.ContentLength()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.ContentPreStarted) + fsm.getState().Entry(fsm) + +class MainMap_ContentPreStarted(MainMap_Default): + + def BlankLine(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.ContentPreStarted.BlankLine()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.ContentStarted) + fsm.getState().Entry(fsm) + +class MainMap_ContentStarted(MainMap_Default): + + def ProcessLine(self, fsm, line): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.ContentStarted.ProcessLine(line)\n") + + if ctxt.add_content(line) == True : + fsm.getState().Exit(fsm) + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.callbackDeferred(ctxt.getResponse()) + finally: + fsm.setState(MainMap.Startup) + fsm.getState().Entry(fsm) + else: + endState = fsm.getState() + fsm.clearState() + try: + ctxt.doNothing() + finally: + fsm.setState(endState) + + +class MainMap: + + Startup = MainMap_Startup('MainMap.Startup', 0) + ApiResponseStarted = MainMap_ApiResponseStarted('MainMap.ApiResponseStarted', 1) + ContentPreStarted = MainMap_ContentPreStarted('MainMap.ContentPreStarted', 2) + ContentStarted = MainMap_ContentStarted('MainMap.ContentStarted', 3) + Default = MainMap_Default('MainMap.Default', -1) + +class ApiRequest_sm(statemap.FSMContext): + + def __init__(self, owner): + statemap.FSMContext.__init__(self) + self._owner = owner + self.setState(MainMap.Startup) + MainMap.Startup.Entry(self) + + def ApiResponse(self): + self._transition = 'ApiResponse' + self.getState().ApiResponse(self) + self._transition = None + + def BlankLine(self): + self._transition = 'BlankLine' + self.getState().BlankLine(self) + self._transition = None + + def ContentFinished(self): + self._transition = 'ContentFinished' + self.getState().ContentFinished(self) + self._transition = None + + def ContentLength(self): + self._transition = 'ContentLength' + self.getState().ContentLength(self) + self._transition = None + + def ProcessLine(self, *arglist): + self._transition = 'ProcessLine' + self.getState().ProcessLine(self, *arglist) + self._transition = None + + def getState(self): + if self._state == None: + raise statemap.StateUndefinedException + return self._state + + def getOwner(self): + return self._owner + diff --git a/scripts/socket/freepy/bgapirequest.sm b/scripts/socket/freepy/bgapirequest.sm new file mode 100644 index 0000000000..5a8d4c17e6 --- /dev/null +++ b/scripts/socket/freepy/bgapirequest.sm @@ -0,0 +1,72 @@ +%start MainMap::Startup +%class BgApiRequest + +%map MainMap +%% + +Startup +{ + CommandReply + ApiResponseStarted + { + } +} + +ApiResponseStarted +{ + ReplyText + GotReplyText + { + } + +} + + +GotReplyText +{ + BlankLine + Startup + { + setRequestFinished(); callOrErrback(); + } + + +} + + + + + +Default +{ + BlankLine + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + CommandReply + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + ReplyText + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + ProcessLine(line) + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + +} + +%% \ No newline at end of file diff --git a/scripts/socket/freepy/bgapirequest_sm.py b/scripts/socket/freepy/bgapirequest_sm.py new file mode 100644 index 0000000000..1efde2f45b --- /dev/null +++ b/scripts/socket/freepy/bgapirequest_sm.py @@ -0,0 +1,165 @@ + +# DO NOT MODIFY THIS CODE - AUTOMATICALLY GENERATED BY SMC + +import statemap + +class BgApiRequestState(statemap.State): + + def Entry(self, fsm): + pass + + def Exit(self, fsm): + pass + + def BlankLine(self, fsm): + self.Default(fsm) + + def CommandReply(self, fsm): + self.Default(fsm) + + def ProcessLine(self, fsm, line): + self.Default(fsm) + + def ReplyText(self, fsm): + self.Default(fsm) + + def Default(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write('TRANSITION : Default\n') + msg = "\n\tState: %s\n\tTransition: %s" % ( + fsm.getState().getName(), fsm.getTransition()) + raise TransitionUndefinedException, msg + +class MainMap_Default(BgApiRequestState): + + def BlankLine(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.BlankLine()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def CommandReply(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.CommandReply()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def ReplyText(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.ReplyText()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def ProcessLine(self, fsm, line): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.ProcessLine(line)\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + +class MainMap_Startup(MainMap_Default): + + def CommandReply(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Startup.CommandReply()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.ApiResponseStarted) + fsm.getState().Entry(fsm) + +class MainMap_ApiResponseStarted(MainMap_Default): + + def ReplyText(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.ApiResponseStarted.ReplyText()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.GotReplyText) + fsm.getState().Entry(fsm) + +class MainMap_GotReplyText(MainMap_Default): + + def BlankLine(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.GotReplyText.BlankLine()\n") + + fsm.getState().Exit(fsm) + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.callOrErrback() + finally: + fsm.setState(MainMap.Startup) + fsm.getState().Entry(fsm) + +class MainMap: + + Startup = MainMap_Startup('MainMap.Startup', 0) + ApiResponseStarted = MainMap_ApiResponseStarted('MainMap.ApiResponseStarted', 1) + GotReplyText = MainMap_GotReplyText('MainMap.GotReplyText', 2) + Default = MainMap_Default('MainMap.Default', -1) + +class BgApiRequest_sm(statemap.FSMContext): + + def __init__(self, owner): + statemap.FSMContext.__init__(self) + self._owner = owner + self.setState(MainMap.Startup) + MainMap.Startup.Entry(self) + + def BlankLine(self): + self._transition = 'BlankLine' + self.getState().BlankLine(self) + self._transition = None + + def CommandReply(self): + self._transition = 'CommandReply' + self.getState().CommandReply(self) + self._transition = None + + def ProcessLine(self, *arglist): + self._transition = 'ProcessLine' + self.getState().ProcessLine(self, *arglist) + self._transition = None + + def ReplyText(self): + self._transition = 'ReplyText' + self.getState().ReplyText(self) + self._transition = None + + def getState(self): + if self._state == None: + raise statemap.StateUndefinedException + return self._state + + def getOwner(self): + return self._owner + diff --git a/scripts/socket/freepy/fseventlistener.py b/scripts/socket/freepy/fseventlistener.py new file mode 100644 index 0000000000..7ab72ff86d --- /dev/null +++ b/scripts/socket/freepy/fseventlistener.py @@ -0,0 +1,258 @@ +""" +FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +Copyright (C) 2005/2006, Anthony Minessale II + +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 +Portions created by the Initial Developer are Copyright (C) +the Initial Developer. All Rights Reserved. + +Contributor(s): Traun Leyden +""" + +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 + + +
+ etc.. +
+Content-Length: 875 +Content-Type: text/event-xml + + +... + + +""" + +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 .. + 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" % 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" % 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" % msg) + + def lineReceived(self, line): + if not self.active_request: + if line.find("") != -1: + self.receiving_event = True + if self.receiving_event: + self.bufferlines.append(line) + if line.find("") != -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): + print "clientConnectionFailed! conn=%s, reason=%s" % (connector, + reason) + #self.protocol = None + if self.num_attempts < 100: + self.num_attempts += 1 + return reactor.callLater(5, self.connect) + else: + 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() + diff --git a/scripts/socket/freepy/fshelper.py b/scripts/socket/freepy/fshelper.py new file mode 100644 index 0000000000..9a7cc98211 --- /dev/null +++ b/scripts/socket/freepy/fshelper.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python + +""" +FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +Copyright (C) 2005/2006, Anthony Minessale II + +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 +Portions created by the Initial Developer are Copyright (C) +the Initial Developer. All Rights Reserved. + +Contributor(s): Traun Leyden +""" + + +from twisted.internet import reactor, defer +from twisted.internet.protocol import ClientFactory +import freepy + +class FsHelper(ClientFactory): + + def __init__(self, host=None, passwd=None, port=None): + if host: + self.host = host + if passwd: + self.passwd = passwd + if port: + self.port = port + + self.freepyd = None + self.connection_deferred = None + + def reset(self): + self.freepyd = None + self.connection_deferred = None + + def connect(self): + + if self.freepyd: + # if we have a protocol object, we are connected (since we always + # null it upon any disconnection) + return defer.succeed("Connected") + + if self.connection_deferred: + # we are already connecting, return existing dfrd + return self.connection_deferred + + self.connection_deferred = defer.Deferred() + self.connection_deferred.addCallback(self.dologin) + self.connection_deferred.addErrback(self.generalError) + print "freepy connecting to %s:%s" % (self.host, self.port) + reactor.connectTCP(self.host, self.port, self) + return self.connection_deferred + + def conncb(self, freepyd): + self.freepyd = freepyd + deferred2callback = self.connection_deferred + self.connection_deferred = None + deferred2callback.callback("Connected") + + + def generalError(self, failure): + print "General error: %s" % failure + return failure + + def startedConnecting(self, connector): + pass + + def buildProtocol(self, addr): + return freepy.FreepyDispatcher(self.conncb, self.discocb) + + def clientConnectionLost(self, connector, reason): + print "clientConnectionLost! conn=%s, reason=%s" % (connector, + reason) + self.connection_deferred = None + self.freepyd = None + + def clientConnectionFailed(self, connector, reason): + print "clientConnectionFailed! conn=%s, reason=%s" % (connector, + reason) + self.freepyd = None + deferred2callback = self.connection_deferred + self.connection_deferred = None + deferred2callback.errback(reason) + + def discocb(self, reason): + print "disconnected. reason: %s" % reason + self.freepyd = None + + def dologin(self, connectmsg): + return self.freepyd.login(self.passwd) + + def originate(self, party2dial, dest_ext_app, bgapi=True): + """ + party2dial - the first argument to the originate command, + eg, sofia/profile_name/1234@domain.com + dest_ext_app - the second argument to the originate command, + eg, &park() or 4761 + returns - a deferred that will be called back with a result + like: + + ([(True, 'Reply-Text: +OK Job-UUID: d07ad7de-2406-11dc-aea3-e3b2e56b7a2c')],) + + """ + + def originate_inner(ignored): + deferreds = [] + deferred = self.freepyd.originate(party2dial, + dest_ext_app, + bgapi) + return deferred + + d = self.connect() + d.addCallback(originate_inner) + return d + + + + + def dialconf(self, people2dial, conf_name, bgapi=True): + """ + conf_name - name of conf TODO: change to match db + people2dial - an array of dictionaries: + 'name': name + 'number': number + returns - a deferred that will be called back with a result + like: + + ([(True, 'Reply-Text: +OK Job-UUID: d07ad7de-2406-11dc-aea3-e3b2e56b7a2c')],) + + Its a bit ugly because its a deferred list callback. + + """ + + def dialconf_inner(ignored): + deferreds = [] + for person2dial in people2dial: + sofia_url = person2dial['number'] + deferred = self.freepyd.confdialout(conf_name, + sofia_url, + bgapi) + deferreds.append(deferred) + return defer.DeferredList(deferreds) + + d = self.connect() + d.addCallback(dialconf_inner) + return d + + def listconf(self, conf_name): + """ + conf_name - name of conf + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + def listconf_inner(ignored): + deferred = self.freepyd.listconf(conf_name) + return deferred + + d = self.connect() + d.addCallback(listconf_inner) + return d + + def confkick(self, member_id, conf_name, bgapi=True): + """ + conf_name - name of conf + member_id - member id of user to kick, eg, "7" + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + def confkick_inner(ignored): + #if type(member_id) == type(""): + # member_id = int(member_id) + deferred = self.freepyd.confkick(member_id, conf_name, bgapi) + return deferred + + d = self.connect() + d.addCallback(confkick_inner) + return d + + def confdtmf(self, member_id, conf_name, dtmf, bgapi=True): + """ + Send dtmf(s) to a conference + conf_name - name of conf + member_id - member id of user to kick, eg, "7", or "all" + dtmf - a single dtmf or a string of dtms, eg "1" or "123" + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + def confdtmf_inner(ignored): + print "confdtmf_inner called" + deferred = self.freepyd.confdtmf(member_id, conf_name, dtmf, bgapi) + return deferred + + d = self.connect() + d.addCallback(confdtmf_inner) + return d + + + def confsay(self, conf_name, text2speak, bgapi=True): + """ + conf_name - name of conf + text2speak - text to speak + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + def confsay_inner(ignored): + deferred = self.freepyd.confsay(conf_name, text2speak, bgapi) + return deferred + + d = self.connect() + d.addCallback(confsay_inner) + return d + + def confplay(self, conf_name, snd_url, bgapi=True): + """ + conf_name - name of conf + snd_url - url to sound file + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + def confplay_inner(ignored): + deferred = self.freepyd.confplay(conf_name, snd_url, bgapi) + return deferred + + d = self.connect() + d.addCallback(confplay_inner) + return d + + def confstop(self, conf_name, bgapi=True): + """ + stop playback of all sounds + + conf_name - name of conf + returns - a deferred that will be called back with a result + like: + + TODO: add this + """ + def confstop_inner(ignored): + deferred = self.freepyd.confstop(conf_name, bgapi) + return deferred + + d = self.connect() + d.addCallback(confstop_inner) + return d + + + def showchannels(self, bgapi=True): + + def showchannels_inner(ignored): + df = self.freepyd.showchannels(bgapi) + return df + + d = self.connect() + d.addCallback(showchannels_inner) + return d + + def killchan(self, uuid, bgapi=True): + + def killchan_inner(ignored): + df = self.freepyd.killchan(uuid, bgapi) + return df + + d = self.connect() + d.addCallback(killchan_inner) + return d + + + def sofia_profile_restart(self, profile_name, bgapi=True): + + def sofia_profile_restart_inner(ignored): + df = self.freepyd.sofia_profile_restart(profile_name, + bgapi) + return df + + d = self.connect() + d.addCallback(sofia_profile_restart_inner) + return d + + + def sofia_status_profile(self, profile_name, bgapi=True): + + def sofia_status_profile_inner(ignored): + df = self.freepyd.sofia_status_profile(profile_name, + bgapi) + return df + + d = self.connect() + d.addCallback(sofia_status_profile_inner) + return d + + +class FsHelperTest: + def __init__(self, fshelper): + self.fshelper=fshelper + pass + + def test_dialconf(self): + + people2dial = [{'name':'freeswitch', + 'number':'888@conference.freeswitch.org'}, + {'name':'mouselike', + 'number':'904@mouselike.org'}] + d = self.fshelper.dialconf(people2dial, "freeswitch", bgapi=False) + def failed(error): + print "Failed to dial users!" + reactor.stop() + return error + d.addErrback(failed) + def worked(*args): + print "Worked! Dialed user result: %s" % str(args) + #reactor.stop() + d.addCallback(worked) + return d + + def test_listconf(self): + + d = self.fshelper.listconf("freeswitch") + def failed(failure): + print "Failed to list users!" + reactor.stop() + return failure + d.addErrback(failed) + def worked(*args): + print "List of users in conf: %s" % str(args) + return args[0] + d.addCallback(worked) + return d + + def test_confkick(self, member_id="6", conf_name="freeswitch"): + + d = self.fshelper.confkick(member_id, conf_name) + def failed(failure): + print "Failed to kick user!" + reactor.stop() + return failure + d.addErrback(failed) + def worked(*args): + print "Kicked user from conf, result: %s" % str(args) + d.addCallback(worked) + + +def test1(): + kick_everyone = False + fshelper = FsHelper("mydomain.com") + fsht = FsHelperTest(fshelper) + fsht.test_dialconf() + d = fsht.test_listconf() + def kickeveryone(members): + print "Kickeveryone called w/ %s (type: %s)" % (members, + type(members)) + for member in members: + fsht.test_confkick(member.member_id) + + def failed(failure): + print "failed: %s" % str(failure) + reactor.stop() + if kick_everyone: + d.addCallback(kickeveryone) + d.addErrback(failed) + #fsht.test_confkick() + #d = fshelper.connect() + #def connected(*args): + # fsht.test_dialconf() + # fsht.test_listconf() + #d.addCallback(connected) + reactor.run() + +def test2(): + fshelper = FsHelper("mydomain.com") + fshelper.sofia_profile_restart("mydomain.com") + reactor.run() + +if __name__=="__main__": + #test1() + test2() diff --git a/scripts/socket/freepy/loginrequest.sm b/scripts/socket/freepy/loginrequest.sm new file mode 100644 index 0000000000..245bbdd99e --- /dev/null +++ b/scripts/socket/freepy/loginrequest.sm @@ -0,0 +1,92 @@ +%start MainMap::Startup +%class LoginRequest + +%map MainMap +%% + +Startup +{ + AuthRequest + AuthRequestStarted + { + } +} + +AuthRequestStarted +{ + BlankLine + AuthRequestFinished + { + } + +} + +AuthRequestFinished +{ + CommandReply + CommandReplyStarted + { + } + +} + +CommandReplyStarted +{ + ReplyText + GotReplyText + { + } + +} + + +GotReplyText +{ + BlankLine + Startup + { + setRequestFinished(); callOrErrback(); + } + +} + + +Default +{ + BlankLine + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + AuthRequest + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + CommandReply + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + ReplyText + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + + ProcessLine(line) + nil + { + setRequestFinished(); + errbackDeferred("Protocol failure"); + } + +} +%% \ No newline at end of file diff --git a/scripts/socket/freepy/loginrequest_sm.py b/scripts/socket/freepy/loginrequest_sm.py new file mode 100644 index 0000000000..70fa16e93c --- /dev/null +++ b/scripts/socket/freepy/loginrequest_sm.py @@ -0,0 +1,209 @@ + +# DO NOT MODIFY THIS CODE - AUTOMATICALLY GENERATED BY SMC + +import statemap + + +class LoginRequestState(statemap.State): + + def Entry(self, fsm): + pass + + def Exit(self, fsm): + pass + + def AuthRequest(self, fsm): + self.Default(fsm) + + def BlankLine(self, fsm): + self.Default(fsm) + + def CommandReply(self, fsm): + self.Default(fsm) + + def ProcessLine(self, fsm, line): + self.Default(fsm) + + def ReplyText(self, fsm): + self.Default(fsm) + + def Default(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write('TRANSITION : Default\n') + msg = "\n\tState: %s\n\tTransition: %s" % ( + fsm.getState().getName(), fsm.getTransition()) + raise TransitionUndefinedException, msg + +class MainMap_Default(LoginRequestState): + + def BlankLine(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.BlankLine()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def AuthRequest(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.AuthRequest()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def CommandReply(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.CommandReply()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def ReplyText(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.ReplyText()\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + + def ProcessLine(self, fsm, line): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Default.ProcessLine(line)\n") + + endState = fsm.getState() + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.errbackDeferred("Protocol failure") + finally: + fsm.setState(endState) + +class MainMap_Startup(MainMap_Default): + + def AuthRequest(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.Startup.AuthRequest()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.AuthRequestStarted) + fsm.getState().Entry(fsm) + +class MainMap_AuthRequestStarted(MainMap_Default): + + def BlankLine(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.AuthRequestStarted.BlankLine()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.AuthRequestFinished) + fsm.getState().Entry(fsm) + +class MainMap_AuthRequestFinished(MainMap_Default): + + def CommandReply(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.AuthRequestFinished.CommandReply()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.CommandReplyStarted) + fsm.getState().Entry(fsm) + +class MainMap_CommandReplyStarted(MainMap_Default): + + def ReplyText(self, fsm): + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.CommandReplyStarted.ReplyText()\n") + + fsm.getState().Exit(fsm) + fsm.setState(MainMap.GotReplyText) + fsm.getState().Entry(fsm) + +class MainMap_GotReplyText(MainMap_Default): + + def BlankLine(self, fsm): + ctxt = fsm.getOwner() + if fsm.getDebugFlag() == True: + fsm.getDebugStream().write("TRANSITION : MainMap.GotReplyText.BlankLine()\n") + + fsm.getState().Exit(fsm) + fsm.clearState() + try: + ctxt.setRequestFinished() + ctxt.callOrErrback() + finally: + fsm.setState(MainMap.Startup) + fsm.getState().Entry(fsm) + +class MainMap: + + Startup = MainMap_Startup('MainMap.Startup', 0) + AuthRequestStarted = MainMap_AuthRequestStarted('MainMap.AuthRequestStarted', 1) + AuthRequestFinished = MainMap_AuthRequestFinished('MainMap.AuthRequestFinished', 2) + CommandReplyStarted = MainMap_CommandReplyStarted('MainMap.CommandReplyStarted', 3) + GotReplyText = MainMap_GotReplyText('MainMap.GotReplyText', 4) + Default = MainMap_Default('MainMap.Default', -1) + +class LoginRequest_sm(statemap.FSMContext): + + def __init__(self, owner): + statemap.FSMContext.__init__(self) + self._owner = owner + self.setState(MainMap.Startup) + MainMap.Startup.Entry(self) + + def AuthRequest(self): + self._transition = 'AuthRequest' + self.getState().AuthRequest(self) + self._transition = None + + def BlankLine(self): + self._transition = 'BlankLine' + self.getState().BlankLine(self) + self._transition = None + + def CommandReply(self): + self._transition = 'CommandReply' + self.getState().CommandReply(self) + self._transition = None + + def ProcessLine(self, *arglist): + self._transition = 'ProcessLine' + self.getState().ProcessLine(self, *arglist) + self._transition = None + + def ReplyText(self): + self._transition = 'ReplyText' + self.getState().ReplyText(self) + self._transition = None + + def getState(self): + if self._state == None: + raise statemap.StateUndefinedException + return self._state + + def getOwner(self): + return self._owner + diff --git a/scripts/socket/freepy/models.py b/scripts/socket/freepy/models.py new file mode 100644 index 0000000000..2b9de74510 --- /dev/null +++ b/scripts/socket/freepy/models.py @@ -0,0 +1,81 @@ +""" +FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +Copyright (C) 2005/2006, Anthony Minessale II + +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 +Portions created by the Initial Developer are Copyright (C) +the Initial Developer. All Rights Reserved. + +Contributor(s): Traun Leyden +""" + +""" +Data models for objects inside freeswitch +""" + +import re + +class ConfMember: + + def __init__(self, rawstring): + self.rawstring = rawstring + self.member_id = None + self.member_uri = None + self.uuid = None + self.caller_id_name = None + self.caller_id_number = None + self.flags = None + self.volume_in = None + self.volume_out = None + self.energy_level = None + + self.parse(self.rawstring) + + def parse(self, rawstring): + """ + 1;sofia/mydomain.com/user@somewhere.com;898e6552-24ab-11dc-9df7-9fccd4095451;FreeSWITCH;0000000000;hear|speak;0;0;300 + """ + fields = rawstring.split(";") + self.member_id = fields[0] + self.member_uri = fields[1] + self.uuid = fields[2] + self.caller_id_name = fields[3] + self.caller_id_number = fields[4] + self.flags = fields[5] + self.volume_in = fields[6] + self.volume_out = fields[7] + self.energy_level = fields[8] + + def brief_member_uri(self): + """ + if self.member_uri is sofia/mydomain.com/foo@bar.com + return foo@bar.com + """ + if not self.member_uri: + return None + + if self.member_uri.find("/") == -1: + return self.member_uri + r = self.member_uri.split("/")[-1] # tokenize on "/" and return last item + return r + + def __repr__(self): + return self.__str__() + + def __str__(self): + return "%s (%s)" % (self.member_id, self.member_uri) diff --git a/scripts/socket/freepy/request.py b/scripts/socket/freepy/request.py new file mode 100644 index 0000000000..5fcf863aa4 --- /dev/null +++ b/scripts/socket/freepy/request.py @@ -0,0 +1,425 @@ +""" +FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application +Copyright (C) 2005/2006, Anthony Minessale II + +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 +Portions created by the Initial Developer are Copyright (C) +the Initial Developer. All Rights Reserved. + +Contributor(s): Traun Leyden +""" + +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 models + +""" +These are response handlers for different types of requests. +It reads the response from freeswitch, and calls back +self.deferred with the result. + +The naming could be improved, but here is the translation: + +LoginRequest - Response handler for a login request + +""" + +class FreepyRequest(object): + + def __init__(self): + self.deferred = defer.Deferred() + self.response_content = "" + self.finished = False + + def isRequestFinished(self): + return self.finished + + def setRequestFinished(self): + self.finished = True + + def getDeferred(self): + return self.deferred + + def callbackDeferred(self, cbval): + self.deferred.callback(cbval) + + def errbackDeferred(self, result): + self.deferred.errback(result) + + def process(self, line): + """ + processs a line from the fs response. if the fs response has been + detected to be finished, then: + + * create an appropriate response based on request type + * callback deferred with response + * rturn True to indicate we are finished + + otherwise, if the fs response is incomplete, just buffer the data + """ + if not line or len(line) == 0: + self._fsm.BlankLine() + return self.isRequestFinished() + + matchstr = re.compile("auth/request", re.I) + result = matchstr.search(line) + if (result != None): + self._fsm.AuthRequest() + return self.isRequestFinished() + + + matchstr = re.compile("command/reply", re.I) + result = matchstr.search(line) + if (result != None): + self._fsm.CommandReply() + return self.isRequestFinished() + + + matchstr = re.compile("Reply-Text", re.I) + result = matchstr.search(line) + if (result != None): + fields = line.split(":") # eg, ['Reply-Text','+OK Job-UUID', '882'] + endfields = fields[1:] + self.response_content = "".join(endfields) + self._fsm.ReplyText() + return self.isRequestFinished() + + + matchstr = re.compile("api/response", re.I) + result = matchstr.search(line) + if (result != None): + self._fsm.ApiResponse() + return self.isRequestFinished() + + matchstr = re.compile("Content-Length", re.I) + result = matchstr.search(line) + if (result != None): + # line: Content-Length: 34 + self.content_length = int(line.split(":")[1].strip()) + self._fsm.ContentLength() + return self.isRequestFinished() + + + self._fsm.ProcessLine(line) + return self.isRequestFinished() + + + + + def callOrErrback(self): + matchstr = re.compile("OK", re.I) + result = matchstr.search(self.response_content) + if (result != None): + self.callbackDeferred(self.response_content) + return + + self.errbackDeferred(self.response_content) + + + def doNothing(self): + # weird smc issue workaround attempt + pass + + +class LoginRequest(FreepyRequest): + """ + Example success response + ======================== + + lineReceived: Content-Type: auth/request + lineReceived: + lineReceived: Content-Type: command/reply + lineReceived: Reply-Text: +OK accepted + lineReceived: + + Example failure response + ======================== + + lineReceived: Content-Type: auth/request + lineReceived: + lineReceived: Content-Type: command/reply + lineReceived: Reply-Text: -ERR invalid + lineReceived: + + """ + + def __init__(self): + super(LoginRequest, self).__init__() + import loginrequest_sm + self._fsm = loginrequest_sm.LoginRequest_sm(self) + + def processOLD(self, line): + + if not line or len(line) == 0: + self._fsm.BlankLine() + return self.isRequestFinished() + + matchstr = re.compile("auth/request", re.I) + result = matchstr.search(line) + if (result != None): + self._fsm.AuthRequest() + return self.isRequestFinished() + + + matchstr = re.compile("command/reply", re.I) + result = matchstr.search(line) + if (result != None): + self._fsm.CommandReply() + return self.isRequestFinished() + + + matchstr = re.compile("Reply-Text", re.I) + result = matchstr.search(line) + if (result != None): + fields = line.split(":") # eg, ['Reply-Text','+OK Job-UUID', '882'] + endfields = fields[1:] + self.response_content = "".join(endfields) + self._fsm.ReplyText() + return self.isRequestFinished() + + + self._fsm.ProcessLine(line) + return self.isRequestFinished() + + + def getReplyText(self): + self.response_content + + +class BgApiRequest(FreepyRequest): + + """ + Here is one of the 'bgapi requests' this class + supports: + + + linereceived: Content-Type: command/reply + linereceived: Reply-Text: +OK Job-UUID: 788da080-24e0-11dc-85f6-3d7b12.. + linereceived: + + """ + + def __init__(self): + super(BgApiRequest, self).__init__() + import bgapirequest_sm + self._fsm = bgapirequest_sm.BgApiRequest_sm(self) + + + def processOLD(self, line): + + if not line or len(line) == 0: + self._fsm.BlankLine() + return self.isRequestFinished() + + matchstr = re.compile("command/reply", re.I) + result = matchstr.search(line) + if (result != None): + self._fsm.CommandReply() + return self.isRequestFinished() + + matchstr = re.compile("Reply-Text", re.I) + result = matchstr.search(line) + if (result != None): + self.response_content = line.split(":")[1] + self._fsm.ReplyText() + return self.isRequestFinished() + + self._fsm.ProcessLine(line) + return self.isRequestFinished() + + + + def getResponse(self): + + # subclasses may want to parse this into a meaningful + # object or set of objects (eg, see ListConfRequest) + # By default, just return accumulated string + return self.response_content + + + +class ApiRequest(FreepyRequest): + + """ + Here is one of the 'api requests' this class + supports: + + lineReceived: Content-Type: api/response + lineReceived: Content-Length: 34 + lineReceived: + lineReceived: Call Requested: result: [SUCCESS] + """ + + def __init__(self): + super(ApiRequest, self).__init__() + import apirequest_sm + self._fsm = apirequest_sm.ApiRequest_sm(self) + self.response_content = "" + + def processOLD(self, line): + + if not line or len(line) == 0: + self._fsm.BlankLine() + return self.isRequestFinished() + + matchstr = re.compile("api/response", re.I) + result = matchstr.search(line) + if (result != None): + self._fsm.ApiResponse() + return self.isRequestFinished() + + matchstr = re.compile("Content-Length", re.I) + result = matchstr.search(line) + if (result != None): + # line: Content-Length: 34 + self.content_length = int(line.split(":")[1].strip()) + self._fsm.ContentLength() + return self.isRequestFinished() + + self._fsm.ProcessLine(line) + return self.isRequestFinished() + + def doNothing(self): + # weird smc issue workaround attempt + pass + + def add_content(self, line): + """ + Add content to local buffer + return - True if finished adding content, False otherwise + """ + + # since the twisted LineReceiver strips off the newline, + # we need to add it back .. otherwise the Content-length + # will be off by one + line += "\n" + + self.response_content += line + if len(self.response_content) == self.content_length: + return True + elif len(self.response_content) > self.content_length: + return True + else: + return False + + + def getResponse(self): + + # subclasses may want to parse this into a meaningful + # object or set of objects (eg, see ListConfRequest) + # By default, just return accumulated string + return self.response_content + + +class DialoutRequest(ApiRequest): + """ + Example raw dialout response + ============================ + + lineReceived: Content-Type: api/response + lineReceived: Content-Length: 34 + lineReceived: + lineReceived: Call Requested: result: [SUCCESS] + """ + + def __init__(self): + super(DialoutRequest, self).__init__() + + +class BgDialoutRequest(BgApiRequest): + def __init__(self): + super(BgDialoutRequest, self).__init__() + + +class ConfKickRequest(ApiRequest): + """ + Example response + ================ + + + """ + + def __init__(self): + super(ConfKickRequest, self).__init__() + +class BgConfKickRequest(BgApiRequest): + """ + Example response + ================ + + + """ + + def __init__(self): + super(BgConfKickRequest, self).__init__() + + +class ListConfRequest(ApiRequest): + """ + Response to request to list conferences: + ======================================== + + lineReceived: Content-Type: api/response + lineReceived: Content-Length: 233 + lineReceived: + lineReceived: 2;sofia/mydomain.com/foo@bar.com;e9be6e72-2410-11dc-8daf-7bcec6dda2ae;FreeSWITCH;0000000000;hear|speak;0;0;300 + lineReceived: 1;sofia/mydomain.com/foo2@bar.com;e9be5fcc-2410-11dc-8daf-7bcec6dda2ae;FreeSWITCH;0000000000;hear|speak;0;0;300 + + """ + + def __init__(self): + super(ListConfRequest, self).__init__() + self.conf_members = [] + + def add_content(self, line): + """ + conf not empty example + ====================== + 1;sofia/mydomain.com/888@conference.freeswitch.org;898e6552-24ab-11dc-9df7-9fccd4095451;FreeSWITCH;0000000000;hear|speak;0;0;300 + + conf empty example + ================== + Conference foo not found + """ + + matchstr = re.compile("not found", re.I) + result = matchstr.search(line) + if (result != None): + # no conf found.. + pass + else: + confmember = models.ConfMember(line) + self.conf_members.append(confmember) + + return super(ListConfRequest, self).add_content(line) + + def getResponse(self): + + # TODO: parse this content into a meaningful + # 'object' .. though, not sure this is really + # necessary. wait till there's a need + return self.conf_members +