Advertisement
Guest User

inboundsocket.py

a guest
May 3rd, 2013
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 45.68 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2. # Copyright (c) 2011 Plivo Team. See LICENSE for details.
  3.  
  4. from gevent import monkey
  5. monkey.patch_all()
  6.  
  7. import os.path
  8. import uuid
  9. try:
  10.     import xml.etree.cElementTree as etree
  11. except ImportError:
  12.     from xml.etree.elementtree import ElementTree as etree
  13.  
  14. from gevent import spawn_raw
  15. from gevent import pool
  16. import gevent.event
  17.  
  18. from plivo.core.freeswitch.inboundsocket import InboundEventSocket
  19. from plivo.rest.freeswitch.helpers import HTTPRequest, get_substring, \
  20.                                         is_valid_url, \
  21.                                         file_exists, normalize_url_space, \
  22.                                         get_resource, \
  23.                                         is_valid_sound_proto
  24.  
  25.  
  26. EVENT_FILTER = "BACKGROUND_JOB CHANNEL_PROGRESS CHANNEL_PROGRESS_MEDIA CHANNEL_HANGUP_COMPLETE CHANNEL_STATE SESSION_HEARTBEAT CALL_UPDATE RECORD_STOP CUSTOM conference::maintenance"
  27.  
  28.  
  29. class RESTInboundSocket(InboundEventSocket):
  30.     """
  31.    Interface between REST API and the InboundSocket
  32.    """
  33.     def __init__(self, server):
  34.         self.server = server
  35.         self.log = self.server.log
  36.         self.cache = self.server.get_cache()
  37.  
  38.         InboundEventSocket.__init__(self, self.get_server().fs_host,
  39.                                     self.get_server().fs_port,
  40.                                     self.get_server().fs_password,
  41.                                     filter=EVENT_FILTER,
  42.                                     trace=self.get_server()._trace)
  43.         # Mapping of Key: job-uuid - Value: request_uuid
  44.         self.bk_jobs = {}
  45.         # Transfer jobs: call_uuid - Value: inline dptools to execute
  46.         self.xfer_jobs = {}
  47.         # Conference sync jobs
  48.         self.conf_sync_jobs = {}
  49.         # Call Requests
  50.         self.call_requests = {}
  51.  
  52.     def get_server(self):
  53.         return self.server
  54.  
  55.     def reload_config(self):
  56.         self.get_server().load_config(reload=True)
  57.         self.log = self.server.log
  58.         self.cache = self.server.get_cache()
  59.  
  60.     def get_extra_fs_vars(self, event):
  61.         params = {}
  62.         if not event or not self.get_server().extra_fs_vars:
  63.             return params
  64.         for var in self.get_server().extra_fs_vars.split(','):
  65.             var = var.strip()
  66.             if var:
  67.                 val = event.get_header(var)
  68.                 if val is None:
  69.                     val = ''
  70.                 params[var] = val
  71.         return params
  72.  
  73.     def on_record_stop(self, event):
  74.         if not self.get_server().record_url:
  75.             return
  76.         rpath = event['Record-File-Path'] or ''
  77.         if not rpath or rpath == 'all':
  78.             return
  79.         calluuid = event['Unique-ID'] or ''
  80.         rms = event['variable_record_seconds'] or ''
  81.         params = {'CallUUID': calluuid,
  82.                   'RecordFile': rpath,
  83.                   'RecordDuration': rms}
  84.         self.log.info("Record Stop event %s"  % str(params))
  85.         self.send_to_url(self.get_server().record_url, params)
  86.  
  87.     def on_custom(self, event):
  88.         if event['Event-Subclass'] == 'conference::maintenance' \
  89.             and event['Action'] == 'stop-recording':
  90.             if not self.get_server().record_url:
  91.                 return
  92.             # special case to manage record files
  93.             rpath = event['Path']
  94.             # if filename is empty or 'all', skip upload
  95.             if not rpath or rpath == 'all':
  96.                 return
  97.             # get room name
  98.             room = event["Conference-Name"]
  99.             rms = event['variable_record_seconds'] or ''
  100.             params = {'ConferenceName': room,
  101.                       'RecordFile': rpath,
  102.                       'RecordDuration': rms}
  103.             self.log.info("Conference Record Stop event %s"  % str(params))
  104.             self.send_to_url(self.get_server().record_url, params)
  105.  
  106.     def on_background_job(self, event):
  107.         """
  108.        Capture Job Event
  109.        Capture background job only for originate and conference,
  110.        and ignore all other jobs
  111.        """
  112.         job_cmd = event['Job-Command']
  113.         job_uuid = event['Job-UUID']
  114.         # TEST MIKE
  115.         if job_cmd == 'originate' and job_uuid:
  116.             try:
  117.                 status, reason = event.get_body().split(' ', 1)
  118.             except ValueError:
  119.                 return
  120.             request_uuid = self.bk_jobs.pop(job_uuid, None)
  121.             if not request_uuid:
  122.                 return
  123.  
  124.             # case GroupCall
  125.             if event['variable_plivo_group_call'] == 'true':
  126.                 status = status.strip()
  127.                 reason = reason.strip()
  128.                 if status[:3] != '+OK':
  129.                     self.log.info("GroupCall Attempt Done for RequestUUID %s (%s)" \
  130.                                                     % (request_uuid, reason))
  131.                     return
  132.                 self.log.warn("GroupCall Attempt Failed for RequestUUID %s (%s)" \
  133.                                                     % (request_uuid, reason))
  134.                 return
  135.  
  136.             # case Call and BulkCall
  137.             try:
  138.                 call_req = self.call_requests[request_uuid]
  139.             except KeyError:
  140.                 return
  141.             # Handle failure case of originate
  142.             # This case does not raise a on_channel_hangup event.
  143.             # All other failures will be captured by on_channel_hangup
  144.             status = status.strip()
  145.             reason = reason.strip()
  146.             if status[:3] != '+OK':
  147.                 # In case ring/early state done, just warn
  148.                 # releasing call request will be done in hangup event
  149.                 if call_req.state_flag in ('Ringing', 'EarlyMedia'):
  150.                     self.log.warn("Call Attempt Done (%s) for RequestUUID %s but Failed (%s)" \
  151.                                                     % (call_req.state_flag, request_uuid, reason))
  152.                     # notify end
  153.                     self.log.debug("Notify Call success for RequestUUID %s" % request_uuid)
  154.                     call_req.notify_call_end()
  155.                     return
  156.                 # If no more gateways, release call request
  157.                 elif not call_req.gateways:
  158.                     self.log.warn("Call Failed for RequestUUID %s but No More Gateways (%s)" \
  159.                                                     % (request_uuid, reason))
  160.                     # notify end
  161.                     self.log.debug("Notify Call success for RequestUUID %s" % request_uuid)
  162.                     call_req.notify_call_end()
  163.                     # set an empty call_uuid
  164.                     call_uuid = ''
  165.                     hangup_url = call_req.hangup_url
  166.                     self.set_hangup_complete(request_uuid, call_uuid,
  167.                                              reason, event, hangup_url)
  168.                     return
  169.                 # If there are gateways and call request state_flag is not set
  170.                 # try again a call
  171.                 elif call_req.gateways:
  172.                     self.log.warn("Call Failed without Ringing/EarlyMedia for RequestUUID %s - Retrying Now (%s)" \
  173.                                                     % (request_uuid, reason))
  174.                     # notify try a new call
  175.                     self.log.debug("Notify Call retry for RequestUUID %s" % request_uuid)
  176.                     call_req.notify_call_try()
  177.         elif job_cmd == 'conference' and job_uuid:
  178.             result = event.get_body().strip() or ''
  179.             async_res = self.conf_sync_jobs.pop(job_uuid, None)
  180.             if async_res is None:
  181.                 return
  182.             elif async_res is True:
  183.                 self.log.info("Conference Api (async) Response for JobUUID %s -- %s" % (job_uuid, result))
  184.                 return
  185.             async_res.set(result)
  186.             self.log.info("Conference Api (sync) Response for JobUUID %s -- %s" % (job_uuid, result))
  187.  
  188.     def on_channel_progress(self, event):
  189.         request_uuid = event['variable_plivo_request_uuid']
  190.         direction = event['Call-Direction']
  191.         # Detect ringing state
  192.         if request_uuid and direction == 'outbound':
  193.             accountsid = event['variable_plivo_accountsid']
  194.             # case GroupCall
  195.             if event['variable_plivo_group_call'] == 'true':
  196.                 # get ring_url
  197.                 ring_url = event['variable_plivo_ring_url']
  198.             # case BulkCall and Call
  199.             else:
  200.                 try:
  201.                     call_req = self.call_requests[request_uuid]
  202.                 except (KeyError, AttributeError):
  203.                     return
  204.                 # notify call and
  205.                 self.log.debug("Notify Call success (Ringing) for RequestUUID %s" % request_uuid)
  206.                 call_req.notify_call_end()
  207.                 # only send if not already ringing/early state
  208.                 if not call_req.state_flag:
  209.                     # set state flag to 'Ringing'
  210.                     call_req.state_flag = 'Ringing'
  211.                     # clear gateways to avoid retry
  212.                     call_req.gateways = []
  213.                     # get ring_url
  214.                     ring_url = call_req.ring_url
  215.                 else:
  216.                     return
  217.  
  218.             # send ring if ring_url found
  219.             if ring_url:
  220.                 called_num = event['variable_plivo_destination_number']
  221.                 if not called_num or called_num == '_undef_':
  222.                     called_num = event['Caller-Destination-Number'] or ''
  223.                 called_num = called_num.lstrip('+')
  224.                 caller_num = event['Caller-Caller-ID-Number'] or ''
  225.                 call_uuid = event['Unique-ID'] or ''
  226.                 self.log.info("Call from %s to %s Ringing for RequestUUID %s" \
  227.                                 % (caller_num, called_num, request_uuid))
  228.                 params = {'To': called_num,
  229.                           'RequestUUID': request_uuid,
  230.                           'Direction': direction,
  231.                           'CallStatus': 'ringing',
  232.                           'From': caller_num,
  233.                           'CallUUID': call_uuid
  234.                          }
  235.                 # add extra params
  236.                 extra_params = self.get_extra_fs_vars(event)
  237.                 if extra_params:
  238.                     params.update(extra_params)
  239.                 if accountsid:
  240.                     params['AccountSID'] = accountsid
  241.                 spawn_raw(self.send_to_url, ring_url, params)
  242.  
  243.     def on_channel_progress_media(self, event):
  244.         request_uuid = event['variable_plivo_request_uuid']
  245.         direction = event['Call-Direction']
  246.         # Detect early media state
  247.         # See http://wiki.freeswitch.org/wiki/Early_media#Early_Media_And_Dialing_Out
  248.         if request_uuid and direction == 'outbound':
  249.             accountsid = event['variable_plivo_accountsid']
  250.             # case BulkCall and Call
  251.             try:
  252.                 call_req = self.call_requests[request_uuid]
  253.             except (KeyError, AttributeError):
  254.                 return
  255.             # notify call end
  256.             self.log.debug("Notify Call success (EarlyMedia) for RequestUUID %s" % request_uuid)
  257.             call_req.notify_call_end()
  258.             # only send if not already ringing/early state
  259.             if not call_req.state_flag:
  260.                 # set state flag to 'EarlyMedia'
  261.                 call_req.state_flag = 'EarlyMedia'
  262.                 # clear gateways to avoid retry
  263.                 call_req.gateways = []
  264.                 # get ring_url
  265.                 ring_url = call_req.ring_url
  266.             else:
  267.                 return
  268.  
  269.             # send ring if ring_url found
  270.             if ring_url:
  271.                 called_num = event['variable_plivo_destination_number']
  272.                 if not called_num or called_num == '_undef_':
  273.                     called_num = event['Caller-Destination-Number'] or ''
  274.                 called_num = called_num.lstrip('+')
  275.                 caller_num = event['Caller-Caller-ID-Number'] or ''
  276.                 call_uuid = event['Unique-ID'] or ''
  277.                 self.log.info("Call from %s to %s in EarlyMedia for RequestUUID %s" \
  278.                                 % (caller_num, called_num, request_uuid))
  279.                 params = {'To': called_num,
  280.                           'RequestUUID': request_uuid,
  281.                           'Direction': direction,
  282.                           'CallStatus': 'ringing',
  283.                           'From': caller_num,
  284.                           'CallUUID': call_uuid
  285.                          }
  286.                 # add extra params
  287.                 extra_params = self.get_extra_fs_vars(event)
  288.                 if extra_params:
  289.                     params.update(extra_params)
  290.                 if accountsid:
  291.                     params['AccountSID'] = accountsid
  292.                 spawn_raw(self.send_to_url, ring_url, params)
  293.  
  294.     def on_call_update(self, event):
  295.         """A Leg from API outbound call answered
  296.        """
  297.         # if plivo_app != 'true', check b leg Dial callback
  298.         plivo_app_flag = event['variable_plivo_app'] == 'true'
  299.         if not plivo_app_flag:
  300.             # request Dial callbackUrl if needed
  301.             aleg_uuid = event['Bridged-To']
  302.             if not aleg_uuid:
  303.                 return
  304.             bleg_uuid = event['Unique-ID']
  305.             if not bleg_uuid:
  306.                 return
  307.             disposition = event['variable_endpoint_disposition']
  308.             if disposition != 'ANSWER':
  309.                 return
  310.             ck_url = event['variable_plivo_dial_callback_url']
  311.             if not ck_url:
  312.                 return
  313.             ck_method = event['variable_plivo_dial_callback_method']
  314.             if not ck_method:
  315.                 return
  316.             params = {'DialBLegUUID': bleg_uuid,
  317.                       'DialALegUUID': aleg_uuid,
  318.                       'DialBLegStatus': 'answer',
  319.                       'CallUUID': aleg_uuid
  320.                      }
  321.             # add extra params
  322.             extra_params = self.get_extra_fs_vars(event)
  323.             if extra_params:
  324.                 params.update(extra_params)
  325.             spawn_raw(self.send_to_url, ck_url, params, ck_method)
  326.             return
  327.  
  328.     def on_channel_bridge(self, event):
  329.         """B Leg from Dial element answered
  330.        """
  331.         # if plivo_app != 'true', check b leg Dial callback
  332.         # request Dial callbackUrl if needed
  333.         aleg_uuid = event['Bridge-A-Unique-ID']
  334.         if not aleg_uuid:
  335.             return
  336.         bleg_uuid = event['Bridge-B-Unique-ID']
  337.         if not bleg_uuid:
  338.             return
  339.         disposition = event['variable_endpoint_disposition']
  340.         if disposition != 'ANSWER':
  341.             return
  342.         app_vars = event['variable_current_application_data']
  343.         if not 'plivo_dial_callback_url' in app_vars:
  344.             return
  345.         ck_url = app_vars.split('plivo_dial_callback_url=')[1].split(',')[0]
  346.         if not 'plivo_dial_callback_method' in app_vars:
  347.             return
  348.         ck_method = app_vars.split('plivo_dial_callback_method=')[1].split(',')[0]
  349.         params = {'DialBLegUUID': bleg_uuid,
  350.                   'DialALegUUID': aleg_uuid,
  351.                   'DialBLegStatus': 'answer',
  352.                   'CallUUID': aleg_uuid
  353.                  }
  354.         spawn_raw(self.send_to_url, ck_url, params, ck_method)
  355.         return
  356.  
  357.     def on_channel_hangup_complete(self, event):
  358.         """Capture Channel Hangup Complete
  359.        """
  360.         # if plivo_app != 'true', check b leg Dial callback
  361.  
  362.         plivo_app_flag = event['variable_plivo_app'] == 'true'
  363.         if not plivo_app_flag:
  364.             # request Dial callbackUrl if needed
  365.             ck_url = event['variable_plivo_dial_callback_url']
  366.             if not ck_url:
  367.                 return
  368.             ck_method = event['variable_plivo_dial_callback_method']
  369.             if not ck_method:
  370.                 return
  371.             aleg_uuid = event['variable_plivo_dial_callback_aleg']
  372.             if not aleg_uuid:
  373.                 return
  374.             hangup_cause = event['Hangup-Cause'] or ''
  375.             # don't send http request for B legs losing bridge race
  376.             if hangup_cause == 'LOSE_RACE':
  377.                 return
  378.             bleg_uuid = event['Unique-ID']
  379.            
  380.             params = {'DialBLegUUID': bleg_uuid,
  381.                       'DialALegUUID': aleg_uuid,
  382.                       'DialBLegStatus': 'hangup',
  383.                       'DialBLegHangupCause': hangup_cause,
  384.                       'CallUUID': aleg_uuid
  385.                      }
  386.             # add extra params
  387.             extra_params = self.get_extra_fs_vars(event)
  388.             if extra_params:
  389.                 params.update(extra_params)
  390.             spawn_raw(self.send_to_url, ck_url, params, ck_method)
  391.             return
  392.  
  393.         # Get call direction
  394.         direction = event['Call-Direction']
  395.  
  396.         # Handle incoming call hangup
  397.         if direction == 'inbound':
  398.             call_uuid = event['Unique-ID']
  399.             reason = event['Hangup-Cause']
  400.             # send hangup
  401.             try:
  402.                 self.set_hangup_complete(None, call_uuid, reason, event, None)
  403.             except Exception, e:
  404.                 self.log.error(str(e))
  405.         # Handle outgoing call hangup
  406.         else:
  407.             # check if found a request uuid
  408.             # if not, ignore hangup event
  409.             request_uuid = event['variable_plivo_request_uuid']
  410.             if not request_uuid and direction != 'outbound':
  411.                 return
  412.  
  413.             call_uuid = event['Unique-ID']
  414.             reason = event['Hangup-Cause']
  415.  
  416.             # case GroupCall
  417.             if event['variable_plivo_group_call'] == 'true':
  418.                 hangup_url = event['variable_plivo_hangup_url']
  419.             # case BulkCall and Call
  420.             else:
  421.                 try:
  422.                     call_req = self.call_requests[request_uuid]
  423.                 except KeyError:
  424.                     return
  425.                 # If there are gateways to try again, spawn originate
  426.                 if call_req.gateways:
  427.                     self.log.debug("Call Failed for RequestUUID %s - Retrying (%s)" \
  428.                                     % (request_uuid, reason))
  429.                     # notify try call
  430.                     self.log.debug("Notify Call retry for RequestUUID %s" % request_uuid)
  431.                     call_req.notify_call_try()
  432.                     return
  433.                 # else clean call request
  434.                 hangup_url = call_req.hangup_url
  435.                 # notify call end
  436.                 self.log.debug("Notify Call success for RequestUUID %s" % request_uuid)
  437.                 call_req.notify_call_end()
  438.  
  439.             # send hangup
  440.             try:
  441.                 self.set_hangup_complete(request_uuid, call_uuid, reason, event, hangup_url)
  442.             except Exception, e:
  443.                 self.log.error(str(e))
  444.  
  445.     def on_channel_state(self, event):
  446.         # When transfer is ready to start,
  447.         # channel goes in state CS_RESET
  448.         if event['Channel-State'] == 'CS_RESET':
  449.             call_uuid = event['Unique-ID']
  450.             xfer = self.xfer_jobs.pop(call_uuid, None)
  451.             if not xfer:
  452.                 return
  453.             self.log.info("TransferCall In Progress for %s" % call_uuid)
  454.             # unset transfer progress flag
  455.             self.set_var("plivo_transfer_progress", "false", uuid=call_uuid)
  456.             # really transfer now
  457.             res = self.api("uuid_transfer %s '%s' inline" % (call_uuid, xfer))
  458.             if res.is_success():
  459.                 self.log.info("TransferCall Done for %s" % call_uuid)
  460.             else:
  461.                 self.log.info("TransferCall Failed for %s: %s" \
  462.                                % (call_uuid, res.get_response()))
  463.         # On state CS_HANGUP, remove transfer job linked to call_uuid
  464.         elif event['Channel-State'] == 'CS_HANGUP':
  465.             call_uuid = event['Unique-ID']
  466.             # try to clean transfer call
  467.             xfer = self.xfer_jobs.pop(call_uuid, None)
  468.             if xfer:
  469.                 self.log.warn("TransferCall Aborted (hangup) for %s" % call_uuid)
  470.  
  471.     def on_session_heartbeat(self, event):
  472.         """Capture every heartbeat event in a session and post info
  473.        """
  474.         params = {}
  475.         answer_seconds_since_epoch = float(event['Caller-Channel-Answered-Time'])/1000000
  476.         # using UTC here .. make sure FS is using UTC also
  477.         params['AnsweredTime'] = str(answer_seconds_since_epoch)
  478.         heartbeat_seconds_since_epoch = float(event['Event-Date-Timestamp'])/1000000
  479.         # using UTC here .. make sure FS is using UTC also
  480.         params['HeartbeatTime'] = str(heartbeat_seconds_since_epoch)
  481.         params['ElapsedTime'] = str(heartbeat_seconds_since_epoch - answer_seconds_since_epoch)
  482.         called_num = event['variable_plivo_destination_number']
  483.         if not called_num or called_num == '_undef_':
  484.             called_num = event['Caller-Destination-Number'] or ''
  485.         called_num = called_num.lstrip('+')
  486.         params['To'] = called_num
  487.         params['From'] = event['Caller-Caller-ID-Number'].lstrip('+')
  488.         params['CallUUID'] = event['Unique-ID']
  489.         params['Direction'] = event['Call-Direction']
  490.         forwarded_from = get_substring(':', '@',
  491.                             event['variable_sip_h_Diversion'])
  492.         if forwarded_from:
  493.             params['ForwardedFrom'] = forwarded_from.lstrip('+')
  494.         if event['Channel-State'] == 'CS_EXECUTE':
  495.             params['CallStatus'] = 'in-progress'
  496.         # RequestUUID through which this call was initiated if outbound
  497.         request_uuid = event['variable_plivo_request_uuid']
  498.         if request_uuid:
  499.             params['RequestUUID'] = request_uuid
  500.         accountsid = event['variable_plivo_accountsid']
  501.         if accountsid:
  502.             params['AccountSID'] = accountsid
  503.  
  504.         self.log.debug("Got Session Heartbeat from Freeswitch: %s" % params)
  505.  
  506.         if self.get_server().call_heartbeat_url:
  507.             self.log.debug("Sending heartbeat to callback: %s" % self.get_server().call_heartbeat_url)
  508.             spawn_raw(self.send_to_url, self.get_server().call_heartbeat_url, params)
  509.  
  510.     def set_hangup_complete(self, request_uuid, call_uuid, reason, event, hangup_url):
  511.         params = {}
  512.         # add extra params
  513.         params = self.get_extra_fs_vars(event)
  514.  
  515.         # case incoming call
  516.         if not request_uuid:
  517.             self.log.info("Hangup for Incoming CallUUID %s Completed, HangupCause %s" \
  518.                                                         % (call_uuid, reason))
  519.             # get hangup url
  520.             hangup_url = event['variable_plivo_hangup_url']
  521.             if hangup_url:
  522.                 self.log.debug("Using HangupUrl for CallUUID %s" \
  523.                                                         % call_uuid)
  524.             else:
  525.                 if self.get_server().default_hangup_url:
  526.                     hangup_url = self.get_server().default_hangup_url
  527.                     self.log.debug("Using HangupUrl from DefaultHangupUrl for CallUUID %s" \
  528.                                                         % call_uuid)
  529.                 elif event['variable_plivo_answer_url']:
  530.                     hangup_url = event['variable_plivo_answer_url']
  531.                     self.log.debug("Using HangupUrl from AnswerUrl for CallUUID %s" \
  532.                                                         % call_uuid)
  533.                 elif self.get_server().default_answer_url:
  534.                     hangup_url = self.get_server().default_answer_url
  535.                     self.log.debug("Using HangupUrl from DefaultAnswerUrl for CallUUID %s" \
  536.                                                         % call_uuid)
  537.             if not hangup_url:
  538.                 self.log.debug("No HangupUrl for Incoming CallUUID %s" % call_uuid)
  539.                 return
  540.             called_num = event['variable_plivo_destination_number']
  541.             if not called_num or called_num == '_undef_':
  542.                 called_num = event['Caller-Destination-Number'] or ''
  543.             called_num = called_num.lstrip('+')
  544.             caller_num = event['Caller-Caller-ID-Number']
  545.             direction = event['Call-Direction']
  546.         # case outgoing call, add params
  547.         else:
  548.             self.log.info("Hangup for Outgoing CallUUID %s Completed, HangupCause %s, RequestUUID %s"
  549.                                         % (call_uuid, reason, request_uuid))
  550.             try:
  551.                 call_req = self.call_requests[request_uuid]
  552.                 called_num = call_req.to.lstrip('+')
  553.                 caller_num = call_req._from
  554.                 if call_req._accountsid:
  555.                     params['AccountSID'] = call_req._accountsid
  556.                 direction = "outbound"
  557.                 self.call_requests[request_uuid] = None
  558.                 del self.call_requests[request_uuid]
  559.             except (KeyError, AttributeError):
  560.                 called_num = ''
  561.                 caller_num = ''
  562.                 direction = "outbound"
  563.  
  564.             self.log.debug("Call Cleaned up for RequestUUID %s" % request_uuid)
  565.  
  566.             if not hangup_url:
  567.                 self.log.debug("No HangupUrl for Outgoing Call %s, RequestUUID %s" % (call_uuid, request_uuid))
  568.                 return
  569.  
  570.             forwarded_from = get_substring(':', '@', event['variable_sip_h_Diversion'])
  571.             aleg_uuid = event['Caller-Unique-ID']
  572.             aleg_request_uuid = event['variable_plivo_request_uuid']
  573.             sched_hangup_id = event['variable_plivo_sched_hangup_id']
  574.             params['RequestUUID'] = request_uuid
  575.             if forwarded_from:
  576.                 params['ForwardedFrom'] = forwarded_from.lstrip('+')
  577.             if aleg_uuid:
  578.                 params['ALegUUID'] = aleg_uuid
  579.             if aleg_request_uuid:
  580.                 params['ALegRequestUUID'] = aleg_request_uuid
  581.             if sched_hangup_id:
  582.                 params['ScheduledHangupId'] = sched_hangup_id
  583.         # if hangup url, handle http request
  584.         if hangup_url:
  585.             sip_uri = event['variable_plivo_sip_transfer_uri'] or ''
  586.             if sip_uri:
  587.                 params['SIPTransfer'] = 'true'
  588.                 params['SIPTransferURI'] = sip_uri
  589.             params['CallUUID'] = call_uuid or ''
  590.             params['HangupCause'] = reason
  591.             params['To'] = called_num or ''
  592.             params['From'] = caller_num or ''
  593.             params['Direction'] = direction or ''
  594.             params['CallStatus'] = 'completed'
  595.             spawn_raw(self.send_to_url, hangup_url, params)
  596.  
  597.     def send_to_url(self, url=None, params={}, method=None):
  598.         if method is None:
  599.             method = self.get_server().default_http_method
  600.  
  601.         if not url:
  602.             self.log.warn("Cannot send %s, no url !" % method)
  603.             return None
  604.         try:
  605.             http_obj = HTTPRequest(self.get_server().key, self.get_server().secret, self.get_server().proxy_url)
  606.             data = http_obj.fetch_response(url, params, method, log=self.log)
  607.             return data
  608.         except Exception, e:
  609.             self.log.error("Sending to %s %s with %s -- Error: %s"
  610.                                         % (method, url, params, e))
  611.         return None
  612.  
  613.     def spawn_originate(self, request_uuid):
  614.         try:
  615.             call_req = self.call_requests[request_uuid]
  616.         except KeyError:
  617.             self.log.warn("Call Request not found for RequestUUID %s" % request_uuid)
  618.             return False
  619.         spawn_raw(self._spawn_originate, call_req)
  620.         self.log.warn("Call Request Spawned for RequestUUID %s" % request_uuid)
  621.         return True
  622.  
  623.     def _spawn_originate(self, call_req):
  624.         try:
  625.             request_uuid = call_req.request_uuid
  626.             gw_count = len(call_req.gateways)
  627.             for x in range(gw_count):
  628.                 try:
  629.                     gw = call_req.gateways.pop(0)
  630.                 except IndexError:
  631.                     self.log.warn("No more Gateways to call for RequestUUID %s" % request_uuid)
  632.                     try:
  633.                         self.call_requests[request_uuid] = None
  634.                         del self.call_requests[request_uuid]
  635.                     except KeyError:
  636.                         pass
  637.                     return
  638.  
  639.                 _options = []
  640.                 # Set plivo app flag
  641.                 _options.append("plivo_app=true")
  642.                 # Add codecs option
  643.                 if gw.codecs:
  644.                     _options.append("absolute_codec_string=%s" % gw.codecs)
  645.                 # Add timeout option
  646.                 if gw.timeout:
  647.                     _options.append("originate_timeout=%s" % gw.timeout)
  648.                 # Set early media
  649.                 _options.append("instant_ringback=false")
  650.                 _options.append("bridge_early_media=true")
  651.                 _options.append("ignore_early_media=false")
  652.                 # Build originate dial string
  653.                 options = ','.join(_options)
  654.                 outbound_str = "&socket('%s async full')" \
  655.                                 % self.get_server().fs_out_address
  656.  
  657.                 dial_str = "originate {%s,%s}%s%s %s" \
  658.                     % (call_req.extra_dial_string, options, gw.gw, gw.to, outbound_str)
  659.                 self.log.debug("Call try for RequestUUID %s with Gateway %s" \
  660.                             % (request_uuid, gw.gw))
  661.                 # Execute originate on background
  662.                 self.log.debug("spawn_originate: %s" % str(dial_str))
  663.                 bg_api_response = self.bgapi(dial_str)
  664.                 job_uuid = bg_api_response.get_job_uuid()
  665.                 self.bk_jobs[job_uuid] = request_uuid
  666.                 if not job_uuid:
  667.                     self.log.error("Call Failed for RequestUUID %s -- JobUUID not received" \
  668.                                                                     % request_uuid)
  669.                     continue
  670.                 # wait for current call attempt to finish
  671.                 self.log.debug("Waiting Call attempt for RequestUUID %s ..." % request_uuid)
  672.                 success = call_req.wait_call_attempt()
  673.                 if success is True:
  674.                     self.log.info("Call Attempt OK for RequestUUID %s" % request_uuid)
  675.                     return
  676.                 self.log.info("Call Attempt Failed for RequestUUID %s, retrying next gateway ..." % request_uuid)
  677.                 continue
  678.         except Exception, e:
  679.             self.log.error(str(e))
  680.  
  681.     def group_originate(self, request_uuid, group_list, group_options=[], reject_causes=''):
  682.         self.log.debug("GroupCall => %s %s" % (str(request_uuid), str(group_options)))
  683.  
  684.         outbound_str = "&socket('%s async full')" % self.get_server().fs_out_address
  685.         # Set plivo app flag and request uuid
  686.         group_options.append('plivo_request_uuid=%s' % request_uuid)
  687.         group_options.append("plivo_app=true")
  688.         group_options.append("plivo_group_call=true")
  689.  
  690.         dial_calls = []
  691.  
  692.         for call_req in group_list:
  693.             extras = []
  694.             dial_gws = []
  695.             for gw in call.gateways:
  696.                 _options = []
  697.                 # Add codecs option
  698.                 if gw.codecs:
  699.                     _options.append("absolute_codec_string=%s" % gw.codecs)
  700.                 # Add timeout option
  701.                 if gw.timeout:
  702.                     _options.append("originate_timeout=%s" % gw.timeout)
  703.                 # Set early media
  704.                 _options.append("instant_ringback=false")
  705.                 _options.append("bridge_early_media=true")
  706.                 _options.append("ignore_early_media=false")
  707.                 # Build gateway dial string
  708.                 options = ','.join(_options)
  709.                 gw_str = '[%s]%s%s' % (options, gw.gw, gw.to)
  710.                 dial_gws.append(gw_str)
  711.             # Build call dial string
  712.             dial_call_str = ",".join(dial_gws)
  713.  
  714.             if call_req.extra_dial_string:
  715.                 extras.append(call_req.extra_dial_string)
  716.             if reject_causes:
  717.                 extras.append("fail_on_single_reject='%s'" % reject_causes)
  718.             # set extra options
  719.             extra_opts = ','.join(extras)
  720.             # set dial string and append to global dial string
  721.             dial_call_str = "{%s}%s" % (extra_opts, dial_call_str)
  722.             dial_calls.append(dial_call_str)
  723.  
  724.         # Build global dial string
  725.         dial_str = ":_:".join(dial_calls)
  726.         global_options = ",".join(group_options)
  727.         if global_options:
  728.             if len(dial_calls) > 1:
  729.                 dial_str = "<%s>%s" % (global_options, dial_str)
  730.             else:
  731.                 if dial_str[0] == '{':
  732.                     dial_str = "{%s,%s" % (global_options, dial_str[1:])
  733.                 else:
  734.                     dial_str = "{%s}%s" % (global_options, dial_str)
  735.  
  736.         # Execute originate on background
  737.         dial_str = "originate %s %s" \
  738.                 % (dial_str, outbound_str)
  739.         self.log.debug("GroupCall : %s" % str(dial_str))
  740.  
  741.         bg_api_response = self.bgapi(dial_str)
  742.         job_uuid = bg_api_response.get_job_uuid()
  743.         self.bk_jobs[job_uuid] = request_uuid
  744.         self.log.debug(str(bg_api_response))
  745.         if not job_uuid:
  746.             self.log.error("GroupCall Failed for RequestUUID %s -- JobUUID not received" \
  747.                                                             % request_uuid)
  748.             return False
  749.         return True
  750.  
  751.     def bulk_originate(self, request_uuid_list):
  752.         if request_uuid_list:
  753.             self.log.info("BulkCall for RequestUUIDs %s" % str(request_uuid_list))
  754.             job_pool = pool.Pool(len(request_uuid_list))
  755.             [ job_pool.spawn(self.spawn_originate, request_uuid)
  756.                                         for request_uuid in request_uuid_list ]
  757.             return True
  758.         self.log.error("BulkCall Failed -- No RequestUUID !")
  759.         return False
  760.  
  761.     def transfer_call(self, new_xml_url, call_uuid):
  762.         # Set transfer progress flag to prevent hangup
  763.         # when the current outbound_socket flow will end
  764.         self.set_var("plivo_transfer_progress", "true", uuid=call_uuid)
  765.         # set original destination number
  766.         called_num = self.get_var("plivo_destination_number", uuid=call_uuid)
  767.         if not called_num:
  768.             called_num = self.get_var("destination_number", uuid=call_uuid)
  769.             self.set_var("plivo_destination_number", called_num, uuid=call_uuid)
  770.         # Set transfer url
  771.         self.set_var("plivo_transfer_url", new_xml_url, uuid=call_uuid)
  772.         # Link inline dptools (will be run when ready to start transfer)
  773.         # to the call_uuid job
  774.         outbound_str = "socket:%s async full" \
  775.                         % (self.get_server().fs_out_address)
  776.         self.xfer_jobs[call_uuid] = outbound_str
  777.         # Transfer into sleep state a little waiting for real transfer
  778.         res = self.api("uuid_transfer %s 'sleep:5000' inline" % call_uuid)
  779.         if res.is_success():
  780.             self.log.info("TransferCall Spawned for %s" % call_uuid)
  781.             return True
  782.         # On failure, remove the job and log error
  783.         try:
  784.             del self.xfer_jobs[call_uuid]
  785.         except KeyError:
  786.             pass
  787.         self.log.error("TransferCall Spawning Failed for %s : %s" \
  788.                         % (call_uuid, str(res.get_response())))
  789.         return False
  790.  
  791.     def hangup_call(self, call_uuid="", request_uuid=""):
  792.         if not call_uuid and not request_uuid:
  793.             self.log.error("Call Hangup Failed -- Missing CallUUID or RequestUUID")
  794.             return False
  795.         if call_uuid:
  796.             callid = "CallUUID %s" % call_uuid
  797.             cmd = "uuid_kill %s NORMAL_CLEARING" % call_uuid
  798.         else:  # Use request uuid
  799.             callid = "RequestUUID %s" % request_uuid
  800.             try:
  801.                 call_req = self.call_requests[request_uuid]
  802.             except (KeyError, AttributeError):
  803.                 self.log.error("Call Hangup Failed -- %s not found" \
  804.                             % (callid))
  805.                 return False
  806.             cmd = "hupall NORMAL_CLEARING plivo_request_uuid %s" % request_uuid
  807.         res = self.api(cmd)
  808.         if not res.is_success():
  809.             self.log.error("Call Hangup Failed for %s -- %s" \
  810.                 % (callid, res.get_response()))
  811.             return False
  812.         self.log.info("Executed Call Hangup for %s" % callid)
  813.         return True
  814.  
  815.     def hangup_all_calls(self):
  816.         bg_api_response = self.bgapi("hupall NORMAL_CLEARING")
  817.         job_uuid = bg_api_response.get_job_uuid()
  818.         if not job_uuid:
  819.             self.log.error("Hangup All Calls Failed -- JobUUID not received")
  820.             return False
  821.         self.log.info("Executed Hangup for all calls")
  822.         return True
  823.  
  824.     def conference_api(self, room=None, command=None, async=True):
  825.         if not command:
  826.             self.log.error("Conference Api Failed -- 'command' is empty")
  827.             return False
  828.         if room:
  829.             cmd = "conference '%s' %s" % (room, command)
  830.         else:
  831.             cmd = "conference %s" % command
  832.         # async mode
  833.         if async:
  834.             bg_api_response = self.bgapi(cmd)
  835.             job_uuid = bg_api_response.get_job_uuid()
  836.             if not job_uuid:
  837.                 self.log.error("Conference Api (async) Failed '%s' -- JobUUID not received" \
  838.                                         % (cmd))
  839.                 return False
  840.             self.conf_sync_jobs[job_uuid] = True
  841.             self.log.info("Conference Api (async) '%s' with JobUUID %s" \
  842.                                     % (cmd, job_uuid))
  843.             return True
  844.         # sync mode
  845.         else:
  846.             res = gevent.event.AsyncResult()
  847.             bg_api_response = self.bgapi(cmd)
  848.             job_uuid = bg_api_response.get_job_uuid()
  849.             if not job_uuid:
  850.                 self.log.error("Conference Api (async) Failed '%s' -- JobUUID not received" \
  851.                                         % (cmd))
  852.                 return False
  853.             self.log.info("Conference Api (sync) '%s' with JobUUID %s" \
  854.                                     % (cmd, job_uuid))
  855.             self.conf_sync_jobs[job_uuid] = res
  856.             try:
  857.                 result = res.wait(timeout=120)
  858.                 return result
  859.             except gevent.timeout.Timeout:
  860.                 self.log.error("Conference Api (sync) '%s' with JobUUID %s -- timeout getting response" \
  861.                                     % (cmd, job_uuid))
  862.                 return False
  863.         return False
  864.  
  865.     def play_on_call(self, call_uuid="", sounds_list=[], legs="aleg", length=3600, schedule=0, mix=True, loop=False):
  866.         cmds = []
  867.         error_count = 0
  868.         bleg = None
  869.  
  870.         # set flags
  871.         if loop:
  872.             aflags = "l"
  873.             bflags = "l"
  874.         else:
  875.             aflags = ""
  876.             bflags = ""
  877.         if mix:
  878.             aflags += "m"
  879.             bflags += "mr"
  880.         else:
  881.             bflags += "r"
  882.  
  883.         if schedule <= 0:
  884.             name = "Call Play"
  885.         else:
  886.             name = "Call SchedulePlay"
  887.         if not call_uuid:
  888.             self.log.error("%s Failed -- Missing CallUUID" % name)
  889.             return False
  890.         if not sounds_list:
  891.             self.log.error("%s Failed -- Missing Sounds" % name)
  892.             return False
  893.         if not legs in ('aleg', 'bleg', 'both'):
  894.             self.log.error("%s Failed -- Invalid legs arg '%s'" % (name, str(legs)))
  895.             return False
  896.  
  897.         # get sound files
  898.         sounds_to_play = []
  899.         for sound in sounds_list:
  900.             if is_valid_sound_proto(sound):
  901.                 sounds_to_play.append(sound)
  902.             elif not is_valid_url(sound):
  903.                 if file_exists(sound):
  904.                     sounds_to_play.append(sound)
  905.                 else:
  906.                     self.log.warn("%s -- File %s not found" % (name, sound))
  907.             else:
  908.                 url = normalize_url_space(sound)
  909.                 sound_file_path = get_resource(self, url)
  910.                 if sound_file_path:
  911.                     sounds_to_play.append(sound_file_path)
  912.                 else:
  913.                     self.log.warn("%s -- Url %s not found" % (name, url))
  914.         if not sounds_to_play:
  915.             self.log.error("%s Failed -- Sound files not found" % name)
  916.             return False
  917.  
  918.         # build command
  919.         play_str = '!'.join(sounds_to_play)
  920.         play_aleg = 'file_string://%s' % play_str
  921.         play_bleg = 'file_string://silence_stream://1!%s' % play_str
  922.  
  923.         # aleg case
  924.         if legs == 'aleg':
  925.             # add displace command
  926.             for displace in self._get_displace_media_list(call_uuid):
  927.                 cmd = "uuid_displace %s stop %s" % (call_uuid, displace)
  928.                 cmds.append(cmd)
  929.             cmd = "uuid_displace %s start %s %d %s" % (call_uuid, play_aleg, length, aflags)
  930.             cmds.append(cmd)
  931.         # bleg case
  932.         elif legs  == 'bleg':
  933.             # get bleg
  934.             bleg = self.get_var("bridge_uuid", uuid=call_uuid)
  935.             # add displace command
  936.             if bleg:
  937.                 for displace in self._get_displace_media_list(call_uuid):
  938.                     cmd = "uuid_displace %s stop %s" % (call_uuid, displace)
  939.                     cmds.append(cmd)
  940.                 cmd = "uuid_displace %s start %s %d %s" % (call_uuid, play_bleg, length, bflags)
  941.                 cmds.append(cmd)
  942.             else:
  943.                 self.log.error("%s Failed -- No BLeg found" % name)
  944.                 return False
  945.         # both legs case
  946.         elif legs == 'both':
  947.             # get bleg
  948.             bleg = self.get_var("bridge_uuid", uuid=call_uuid)
  949.             # add displace commands
  950.             for displace in self._get_displace_media_list(call_uuid):
  951.                 cmd = "uuid_displace %s stop %s" % (call_uuid, displace)
  952.                 cmds.append(cmd)
  953.             cmd = "uuid_displace %s start %s %d %s" % (call_uuid, play_aleg, length, aflags)
  954.             cmds.append(cmd)
  955.             # get the bleg
  956.             if bleg:
  957.                 cmd = "uuid_displace %s start %s %d %s" % (call_uuid, play_bleg, length, bflags)
  958.                 cmds.append(cmd)
  959.             else:
  960.                 self.log.warn("%s -- No BLeg found" % name)
  961.         else:
  962.             self.log.error("%s Failed -- Invalid Legs '%s'" % (name, legs))
  963.             return False
  964.  
  965.         # case no schedule
  966.         if schedule <= 0:
  967.             for cmd in cmds:
  968.                 res = self.api(cmd)
  969.                 if not res.is_success():
  970.                     self.log.error("%s Failed '%s' -- %s" % (name, cmd, res.get_response()))
  971.                     error_count += 1
  972.             if error_count > 0:
  973.                 return False
  974.             return True
  975.  
  976.         # case schedule
  977.         sched_id = str(uuid.uuid1())
  978.         for cmd in cmds:
  979.             sched_cmd = "sched_api +%d %s %s" % (schedule, sched_id, cmd)
  980.             res = self.api(sched_cmd)
  981.             if res.is_success():
  982.                 self.log.info("%s '%s' with SchedPlayId %s" % (name, sched_cmd, sched_id))
  983.             else:
  984.                 self.log.error("%s Failed '%s' -- %s" % (name, sched_cmd, res.get_response()))
  985.                 error_count += 1
  986.         if error_count > 0:
  987.             return False
  988.         return sched_id
  989.  
  990.     def play_stop_on_call(self, call_uuid=""):
  991.         cmds = []
  992.         error_count = 0
  993.  
  994.         # get bleg
  995.         bleg = self.get_var("bridge_uuid", uuid=call_uuid)
  996.  
  997.         for displace in self._get_displace_media_list(call_uuid):
  998.             cmd = "uuid_displace %s stop %s" % (call_uuid, displace)
  999.             cmds.append(cmd)
  1000.  
  1001.         if not cmds:
  1002.             self.log.warn("PlayStop -- Nothing to stop")
  1003.             return True
  1004.  
  1005.         for cmd in cmds:
  1006.             bg_api_response = self.bgapi(cmd)
  1007.             job_uuid = bg_api_response.get_job_uuid()
  1008.             if not job_uuid:
  1009.                 self.log.error("PlayStop Failed '%s' -- JobUUID not received" % cmd)
  1010.                 error_count += 1
  1011.         if error_count > 0:
  1012.             return False
  1013.         return True
  1014.  
  1015.     def _get_displace_media_list(self, uuid=''):
  1016.         if not uuid:
  1017.             return []
  1018.         result = []
  1019.         cmd = "uuid_buglist %s" % uuid
  1020.         res = self.api(cmd)
  1021.         if not res.get_response():
  1022.             self.log.warn("cannot get displace_media_list: no list" % str(e))
  1023.             return result
  1024.         try:
  1025.             doc = etree.fromstring(res.get_response())
  1026.             if doc.tag != 'media-bugs':
  1027.                 return result
  1028.             for node in doc:
  1029.                 if node.tag == 'media-bug':
  1030.                     try:
  1031.                         func = node.find('function').text
  1032.                         if func == 'displace':
  1033.                             target = node.find('target').text
  1034.                             result.append(target)
  1035.                     except:
  1036.                         continue
  1037.             return result
  1038.         except Exception, e:
  1039.             self.log.warn("cannot get displace_media_list: %s" % str(e))
  1040.             return result
  1041.  
  1042.     def sound_touch(self, call_uuid="", direction='out', s=None, o=None,
  1043.                     p=None, r=None, t=None):
  1044.         stop_cmd = "soundtouch %s stop" % call_uuid
  1045.         cmd = "soundtouch %s start " % call_uuid
  1046.         if direction == "in":
  1047.             cmd += "send_leg "
  1048.         if s:
  1049.             cmd += "%ss " % str(s)
  1050.         if o:
  1051.             cmd += "%so " % str(o)
  1052.         if p:
  1053.             cmd += "%sp " % str(p)
  1054.         if r:
  1055.             cmd += "%sr " % str(r)
  1056.         if t:
  1057.             cmd += "%st " % str(t)
  1058.         self.api(stop_cmd)
  1059.         res = self.api(cmd)
  1060.         if res.is_success():
  1061.             return True
  1062.         self.log.error("SoundTouch Failed '%s' -- %s" % (cmd, res.get_response()))
  1063.         return False
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement