From b6428ac148c39b57912a620123036fcbc8fe00b9 Mon Sep 17 00:00:00 2001 From: karl anderson Date: Wed, 3 Apr 2013 03:47:18 +0000 Subject: [PATCH 1/2] resolve KAZOO-661: create ringback settings on the number or callflow --- ecallmgr/src/ecallmgr.hrl | 1 - ecallmgr/src/ecallmgr_call_command.erl | 550 ++++++++++-------- ecallmgr/src/ecallmgr_fs_xml.erl | 46 +- lib/whistle-1.0.0/src/api/wapi_route.erl | 30 +- .../src/wh_number_manager.erl | 22 +- .../apps/callflow/src/cf_route_req.erl | 36 +- .../stepswitch/src/stepswitch_inbound.erl | 16 +- 7 files changed, 412 insertions(+), 289 deletions(-) diff --git a/ecallmgr/src/ecallmgr.hrl b/ecallmgr/src/ecallmgr.hrl index 6730d2b36d6..006afb216f2 100644 --- a/ecallmgr/src/ecallmgr.hrl +++ b/ecallmgr/src/ecallmgr.hrl @@ -146,7 +146,6 @@ ,{<<"Origination-UUID">>, <<"origination_uuid">>} ,{<<"Ignore-Display-Updates">>, <<"ignore_display_updates">>} ,{<<"Eavesdrop-Group-ID">>, <<"eavesdrop_group">>} - %% ,{<<"Hold-Media">>, <<"hold_music">>} ,{<<"Loopback-Bowout">>, <<"loopback_bowout_on_execute">>} ,{<<"tts_engine">>, <<"tts_engine">>} ,{<<"tts_voice">>, <<"tts_voice">>} diff --git a/ecallmgr/src/ecallmgr_call_command.erl b/ecallmgr/src/ecallmgr_call_command.erl index 7598fe6d579..129657f78a6 100644 --- a/ecallmgr/src/ecallmgr_call_command.erl +++ b/ecallmgr/src/ecallmgr_call_command.erl @@ -410,119 +410,22 @@ get_fs_app(Node, UUID, JObj, <<"bridge">>) -> %% execute ring_ready so we dont leave the caller hanging with dead air. %% this does not test how many are ACTUALLY dialed (registered) %% since that is one of the things we want to be ringing during - DialSeparator = case wh_json:get_value(<<"Dial-Endpoint-Method">>, JObj, <<"single">>) of - <<"simultaneous">> when length(Endpoints) > 1 -> - lager:debug("bridge is simultaneous to multiple endpoints, starting local ringing"), - %% we don't really care if this succeeds, the call will fail later on - _ = ecallmgr_util:send_cmd(Node, UUID, <<"ring_ready">>, ""), - <<",">>; - _Else -> - <<"|">> - end, - - DialStrings = ecallmgr_util:build_bridge_string(Endpoints, DialSeparator), - - Routines = [fun(DP) -> - case wh_json:get_value(<<"Ringback">>, JObj) of - 'undefined' -> DP; - Ringback -> - Stream = ecallmgr_util:media_path(Ringback, extant, UUID, JObj), - lager:debug("bridge has custom ringback: ~s", [Stream]), - [{"application", <<"set ringback=", Stream/binary>>}, - {"application", "set instant_ringback=true"} - |DP - ] - end - end - ,fun(DP) -> - case wh_json:get_value(<<"Hold-Media">>, JObj) of - 'undefined' -> - case wh_json:get_value([<<"Custom-Channel-Vars">>, <<"Hold-Media">>], JObj) of - 'undefined' -> - case ecallmgr_fs_channel:import_moh(UUID) of - 'true' -> - [{"application", "export hold_music=${hold_music}"} - ,{"application", "set import=hold_music"} - |DP - ]; - 'false' -> DP - end; - Media -> - Stream = ecallmgr_util:media_path(Media, extant, UUID, JObj), - lager:debug("bridge has custom music-on-hold in channel vars: ~s", [Stream]), - [{"application", <<"set hold_music=", Stream/binary>>}|DP] - end; - Media -> - Stream = ecallmgr_util:media_path(Media, extant, UUID, JObj), - lager:debug("bridge has custom music-on-hold: ~s", [Stream]), - [{"application", <<"set hold_music=", Stream/binary>>}|DP] - end - end - ,fun(DP) -> - case wh_json:is_true(<<"Secure-RTP">>, JObj, 'false') of - 'true' -> [{"application", "set sip_secure_media=true"}|DP]; - 'false' -> DP - end - end - ,fun(DP) -> - case wh_json:get_value(<<"Media">>, JObj) of - <<"process">> -> - lager:debug("bridge will process media through host switch"), - [{"application", "set bypass_media=false"}|DP]; - <<"bypass">> -> - lager:debug("bridge will connect the media peer-to-peer"), - [{"application", "set bypass_media=true"}|DP]; - _ -> - maybe_bypass_endpoint_media(Endpoints, DP) - end - end - ,fun(DP) -> - CCVs = wh_json:get_value(<<"Custom-Channel-Vars">>, JObj), - case wh_json:is_json_object(CCVs) of - 'true' -> - [{"application", <<"set ", Var/binary, "=", (wh_util:to_binary(V))/binary>>} - || {K, V} <- wh_json:to_proplist(CCVs), - (Var = props:get_value(K, ?SPECIAL_CHANNEL_VARS)) =/= 'undefined' - ] ++ DP; - _ -> - DP - end - end - ,fun(DP) -> - [{"application", "set failure_causes=NORMAL_CLEARING,ORIGINATOR_CANCEL,CRASH"} - ,{"application", "set continue_on_fail=true"} - |DP - ] - end - ,fun(DP) -> - Props = case wh_json:find(<<"Force-Fax">>, Endpoints, wh_json:get_value(<<"Force-Fax">>, JObj)) of - 'undefined' -> - [{[<<"Custom-Channel-Vars">>, <<"Bridge-ID">>], wh_util:rand_hex_binary(16)}]; - Direction -> - [{[<<"Custom-Channel-Vars">>, <<"Bridge-ID">>], wh_util:rand_hex_binary(16)} - ,{[<<"Custom-Channel-Vars">>, <<"Force-Fax">>], Direction} - ] - end, - BridgeCmd = list_to_binary(["bridge " - ,ecallmgr_fs_xml:get_channel_vars(wh_json:set_values(Props, JObj)) - ,DialStrings - ]), - [{"application", BridgeCmd}|DP] - end - ,fun(DP) -> - [{"application", ecallmgr_util:create_masquerade_event(<<"bridge">>, <<"CHANNEL_EXECUTE_COMPLETE">>)} - ,{"application", "park "} - |DP - ] - end + _ = bridge_handle_ringback(Node, UUID, JObj), + _ = bridge_maybe_early_media(Node, UUID, JObj), + lager:debug("creating bridge dialplan"), + Routines = [fun bridge_handle_hold_media/4 + ,fun bridge_handle_secure_rtp/4 + ,fun bridge_handle_bypass_media/4 + ,fun bridge_handle_ccvs/4 + ,fun bridge_pre_exec/4 + ,fun bridge_create_command/4 + ,fun bridge_post_exec/4 ], - case DialStrings of - <<>> -> - {'error', <<"registrar returned no endpoints">>}; - _ -> - lager:debug("creating bridge dialplan"), - {<<"xferext">>, lists:foldr(fun(F, DP) -> F(DP) end, [], Routines)} - end + lager:debug("creating bridge dialplan"), + XferExt = lists:foldr(fun(F, DP) -> + F(DP, Node, UUID, JObj) end + ,[], Routines), + {<<"xferext">>, XferExt} end; get_fs_app(Node, UUID, JObj, <<"call_pickup">>) -> @@ -553,40 +456,16 @@ get_fs_app(Node, UUID, JObj, <<"execute_extension">>) -> case wapi_dialplan:execute_extension_v(JObj) of 'false' -> {'error', <<"execute extension failed to execute as JObj did not validate">>}; 'true' -> - Generators = [fun(DP) -> - case wh_json:is_true(<<"Reset">>, JObj) of - 'false' -> ok; - 'true' -> - create_dialplan_move_ccvs(<<"Execute-Extension-Original-">>, Node, UUID, DP) - end - end - ,fun(DP) -> - CCVs = wh_json:get_value(<<"Custom-Channel-Vars">>, JObj, wh_json:new()), - case wh_json:is_empty(CCVs) of - 'true' -> DP; - 'false' -> - ChannelVars = wh_json:to_proplist(CCVs), - [{"application", <<"set ", (ecallmgr_util:get_fs_kv(K, V, UUID))/binary>>} - || {K, V} <- ChannelVars] ++ DP - end - end - ,fun(DP) -> - [{"application", <<"set ", ?CHANNEL_VAR_PREFIX, "Executing-Extension=true">>} - ,{"application", <<"execute_extension ", (wh_json:get_value(<<"Extension">>, JObj))/binary>>} - |DP - ] - end - ,fun(DP) -> - [{"application", <<"unset ", ?CHANNEL_VAR_PREFIX, "Executing-Extension">>} - ,{"application", ecallmgr_util:create_masquerade_event(<<"execute_extension">> - ,<<"CHANNEL_EXECUTE_COMPLETE">> - )} - ,{"application", "park "} - |DP - ] - end - ], - {<<"xferext">>, lists:foldr(fun(F, DP) -> F(DP) end, [], Generators)} + Routines = [fun execute_exten_handle_reset/4 + ,fun execute_exten_handle_ccvs/4 + ,fun execute_exten_pre_exec/4 + ,fun execute_exten_create_command/4 + ,fun execute_exten_post_exec/4 + ], + Extension = lists:foldr(fun(F, DP) -> + F(DP, Node, UUID, JObj) + end, [], Routines), + {<<"xferext">>, Extension} end; get_fs_app(Node, UUID, JObj, <<"tone_detect">>) -> @@ -669,6 +548,7 @@ get_fs_app(Node, UUID, JObj, <<"redirect">>) -> {<<"redirect">>, wh_json:get_value(<<"Redirect-Contact">>, JObj, <<>>)} end; +%% TODO: can we depreciate this command? It was prior to ecallmgr_fs_query....dont think anything is using it. get_fs_app(Node, UUID, JObj, <<"fetch">>) -> spawn(fun() -> send_fetch_call_event(Node, UUID, JObj) @@ -685,6 +565,42 @@ get_fs_app(_Node, _UUID, _JObj, _App) -> lager:debug("unknown application ~s", [_App]), {'error', <<"application unknown">>}. +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Call pickup command helpers +%% @end +%%-------------------------------------------------------------------- +-spec get_call_pickup_app(atom(), ne_binary(), wh_json:object(), ne_binary()) -> + {ne_binary(), ne_binary()}. +get_call_pickup_app(Node, UUID, JObj, Target) -> + _ = case wh_json:is_true(<<"Park-After-Pickup">>, JObj, 'false') of + 'false' -> ecallmgr_util:export(Node, UUID, <<"park_after_bridge=false">>); + 'true' -> + _ = ecallmgr_util:set(Node, Target, <<"park_after_bridge=true">>), + ecallmgr_util:set(Node, UUID, <<"park_after_bridge=true">>) + end, + + _ = case wh_json:is_true(<<"Continue-On-Fail">>, JObj, 'true') of + 'false' -> ecallmgr_util:export(Node, UUID, <<"continue_on_fail=false">>); + 'true' -> ecallmgr_util:export(Node, UUID, <<"continue_on_fail=true">>) + end, + + _ = case wh_json:is_true(<<"Continue-On-Cancel">>, JObj, 'true') of + 'false' -> ecallmgr_util:export(Node, UUID, <<"continue_on_cancel=false">>); + 'true' -> ecallmgr_util:export(Node, UUID, <<"continue_on_cancel=true">>) + end, + + _ = ecallmgr_util:export(Node, UUID, <<"failure_causes=NORMAL_CLEARING,ORIGINATOR_CANCEL,CRASH">>), + + {<<"call_pickup">>, <>}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Conference command helpers +%% @end +%%-------------------------------------------------------------------- get_conference_app(ChanNode, UUID, JObj, 'true') -> ConfName = wh_json:get_value(<<"Conference-ID">>, JObj), ConferenceConfig = wh_json:get_value(<<"Conference-Config">>, JObj, <<"default">>), @@ -719,7 +635,6 @@ get_conference_app(_ChanNode, _UUID, JObj, 'false') -> ConfName = wh_json:get_value(<<"Conference-ID">>, JObj), {<<"conference">>, list_to_binary([ConfName, "@default", get_conference_flags(JObj)])}. - %% [{FreeSWITCH-Flag-Name, Kazoo-Flag-Name}] %% Conference-related entry flags %% convert from FS conference flags to Kazoo conference flags @@ -753,9 +668,79 @@ wait_for_conference(ConfName) -> %%-------------------------------------------------------------------- %% @private %% @doc -%% +%% Bridge command helpers %% @end %%-------------------------------------------------------------------- +bridge_handle_ringback(Node, UUID, JObj) -> + case wh_json:get_value(<<"Ringback">>, JObj) of + 'undefined' -> + case wh_json:get_value([<<"Custom-Channel-Vars">>, <<"Ringback">>], JObj) of + 'undefined' -> 'ok'; + Media -> + Stream = ecallmgr_util:media_path(Media, extant, UUID, JObj), + lager:debug("bridge has custom ringback in channel vars: ~s", [Stream]), + ecallmgr_util:send_cmd(Node, UUID, <<"set">>, <<"ringback=", Stream/binary>>) + end; + Media -> + Stream = ecallmgr_util:media_path(Media, extant, UUID, JObj), + lager:debug("bridge has custom ringback: ~s", [Stream]), + ecallmgr_util:send_cmd(Node, UUID, <<"set">>, <<"ringback=", Stream/binary>>) + end. + +bridge_maybe_early_media(Node, UUID, JObj) -> + case wh_json:get_value(<<"Dial-Endpoint-Method">>, JObj, <<"single">>) of + <<"simultaneous">> -> + case length(wh_json:get_ne_value(<<"Endpoints">>, JObj, [])) > 1 of + 'false' -> 'ok'; + 'true' -> + lager:debug("bridge is simultaneous to multiple endpoints, starting local ringing"), + %% we don't really care if this succeeds, the call will fail later on + ecallmgr_util:send_cmd(Node, UUID, <<"ring_ready">>, "") + end; + _Else -> 'ok' + end. + +bridge_handle_hold_media(DP, Node, UUID, JObj) -> + case wh_json:get_value(<<"Hold-Media">>, JObj) of + 'undefined' -> + case wh_json:get_value([<<"Custom-Channel-Vars">>, <<"Hold-Media">>], JObj) of + 'undefined' -> DP; + Media -> + Stream = ecallmgr_util:media_path(Media, extant, UUID, JObj), + lager:debug("bridge has custom music-on-hold in channel vars: ~s", [Stream]), + [{"application", <<"set hold_music=", Stream/binary>>} + ,{"application", <<"set transfer_ringback=", Stream/binary>>} + |DP + ] + end; + Media -> + Stream = ecallmgr_util:media_path(Media, extant, UUID, JObj), + lager:debug("bridge has custom music-on-hold: ~s", [Stream]), + [{"application", <<"set hold_music=", Stream/binary>>} + ,{"application", <<"set transfer_ringback=", Stream/binary>>} + |DP + ] + end. + +bridge_handle_secure_rtp(DP, Node, UUID, JObj) -> + case wh_json:is_true(<<"Secure-RTP">>, JObj, 'false') of + 'true' -> [{"application", "set sip_secure_media=true"}|DP]; + 'false' -> DP + end. + +bridge_handle_bypass_media(DP, Node, UUID, JObj) -> + case wh_json:get_value(<<"Media">>, JObj) of + <<"process">> -> + lager:debug("bridge will process media through host switch"), + [{"application", "set bypass_media=false"}|DP]; + <<"bypass">> -> + lager:debug("bridge will connect the media peer-to-peer"), + [{"application", "set bypass_media=true"}|DP]; + _ -> + Endpoints = wh_json:get_ne_value(<<"Endpoints">>, JObj, []), + maybe_bypass_endpoint_media(Endpoints, DP) + end. + -spec maybe_bypass_endpoint_media(wh_json:objects(), wh_proplist()) -> wh_proplist(). maybe_bypass_endpoint_media([Endpoint], DP) -> case wh_json:is_true(<<"Bypass-Media">>, Endpoint) of @@ -767,39 +752,70 @@ maybe_bypass_endpoint_media([Endpoint], DP) -> maybe_bypass_endpoint_media(_, DP) -> DP. -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% -%% @end -%%-------------------------------------------------------------------- --spec get_call_pickup_app(atom(), ne_binary(), wh_json:object(), ne_binary()) -> - {ne_binary(), ne_binary()}. -get_call_pickup_app(Node, UUID, JObj, Target) -> - _ = case wh_json:is_true(<<"Park-After-Pickup">>, JObj, 'false') of - 'false' -> ecallmgr_util:export(Node, UUID, <<"park_after_bridge=false">>); - 'true' -> - _ = ecallmgr_util:set(Node, Target, <<"park_after_bridge=true">>), - ecallmgr_util:set(Node, UUID, <<"park_after_bridge=true">>) - end, +bridge_handle_ccvs(DP, Node, UUID, JObj) -> + CCVs = wh_json:get_value(<<"Custom-Channel-Vars">>, JObj), + case wh_json:is_json_object(CCVs) of + 'true' -> + [{"application", <<"set ", Var/binary, "=", (wh_util:to_binary(V))/binary>>} + || {K, V} <- wh_json:to_proplist(CCVs), + (Var = props:get_value(K, ?SPECIAL_CHANNEL_VARS)) =/= 'undefined' + ] ++ DP; + _ -> + DP + end. - _ = case wh_json:is_true(<<"Continue-On-Fail">>, JObj, 'true') of - 'false' -> ecallmgr_util:export(Node, UUID, <<"continue_on_fail=false">>); - 'true' -> ecallmgr_util:export(Node, UUID, <<"continue_on_fail=true">>) - end, +bridge_pre_exec(DP, _, _, _) -> + [{"application", "set failure_causes=NORMAL_CLEARING,ORIGINATOR_CANCEL,CRASH"} + ,{"application", "set continue_on_fail=true"} + |DP + ]. - _ = case wh_json:is_true(<<"Continue-On-Cancel">>, JObj, 'true') of - 'false' -> ecallmgr_util:export(Node, UUID, <<"continue_on_cancel=false">>); - 'true' -> ecallmgr_util:export(Node, UUID, <<"continue_on_cancel=true">>) - end, +bridge_create_command(DP, Node, UUID, JObj) -> + Endpoints = wh_json:get_ne_value(<<"Endpoints">>, JObj, []), + BridgeCmd = list_to_binary(["bridge " + ,bridge_build_channels_vars(Endpoints, JObj) + ,try_create_bridge_string(Endpoints, JObj) + ]), + [{"application", BridgeCmd}|DP]. + +try_create_bridge_string(Endpoints, JObj) -> + DialSeparator = bridge_determine_dial_separator(Endpoints, JObj), + case ecallmgr_util:build_bridge_string(Endpoints, DialSeparator) of + <<>> -> + lager:warning("bridge string resulted in no enpoints", []), + throw(<<"registrar returned no endpoints">>); + BridgeString -> BridgeString + end. - _ = ecallmgr_util:export(Node, UUID, <<"failure_causes=NORMAL_CLEARING,ORIGINATOR_CANCEL,CRASH">>), +bridge_build_channels_vars(Endpoints, JObj) -> + BridgeId = wh_util:rand_hex_binary(16), + Props = case wh_json:find(<<"Force-Fax">>, Endpoints, wh_json:get_value(<<"Force-Fax">>, JObj)) of + 'undefined' -> + [{[<<"Custom-Channel-Vars">>, <<"Bridge-ID">>], BridgeId}]; + Direction -> + [{[<<"Custom-Channel-Vars">>, <<"Bridge-ID">>], BridgeId} + ,{[<<"Custom-Channel-Vars">>, <<"Force-Fax">>], Direction} + ] + end, + ecallmgr_fs_xml:get_channel_vars(wh_json:set_values(Props, JObj)). - {<<"call_pickup">>, <>}. +bridge_determine_dial_separator(Endpoints, JObj) -> + case wh_json:get_value(<<"Dial-Endpoint-Method">>, JObj, <<"single">>) of + <<"simultaneous">> when length(Endpoints) > 1 -> <<",">>; + _Else -> <<"|">> + end. + +bridge_post_exec(DP, _, _, _) -> + Event = ecallmgr_util:create_masquerade_event(<<"bridge">>, <<"CHANNEL_EXECUTE_COMPLETE">>), + [{"application", Event} + ,{"application", "park "} + |DP + ]. %%-------------------------------------------------------------------- %% @private %% @doc +%% Store command helpers %% @end %%-------------------------------------------------------------------- -spec stream_over_http(atom(), ne_binary(), ne_binary(), 'put' | 'post', 'store' | 'fax', wh_json:object()) -> any(). @@ -846,40 +862,46 @@ send_fs_store(Node, Args, 'put') -> send_fs_store(Node, Args, 'post') -> freeswitch:api(Node, 'http_post', wh_util:to_list(Args)). -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% @end -%%-------------------------------------------------------------------- --spec get_terminators(api_binary() | ne_binaries() | wh_json:object()) -> - {ne_binary(), ne_binary()}. -get_terminators('undefined') -> 'undefined'; -get_terminators(Ts) when is_binary(Ts) -> - get_terminators([Ts]); -get_terminators([_|_]=Ts) -> - case Ts =:= get('$prior_terminators') of - 'true' -> 'undefined'; - 'false' -> - put('$prior_terminators', Ts), - case wh_util:is_empty(Ts) of - 'true' -> {<<"playback_terminators">>, <<"none">>}; - 'false' -> {<<"playback_terminators">>, wh_util:to_binary(Ts)} - end - end; -get_terminators(JObj) -> get_terminators(wh_json:get_ne_value(<<"Terminators">>, JObj)). +-spec send_store_call_event(atom(), ne_binary(), wh_json:object() | ne_binary()) -> 'ok'. +send_store_call_event(Node, UUID, MediaTransResults) -> + Timestamp = wh_util:to_binary(wh_util:current_tstamp()), + Prop = try freeswitch:api(Node, 'uuid_dump', wh_util:to_list(UUID)) of + {'ok', Dump} -> ecallmgr_util:eventstr_to_proplist(Dump); + {'error', _Err} -> []; + 'timeout' -> [] + catch + _E:_R -> + lager:debug("failed get params from uuid_dump"), + lager:debug("~p : ~p", [_E, _R]), + lager:debug("sending less interesting call_event message"), + [] + end, + EvtProp1 = [{<<"Msg-ID">>, props:get_value(<<"Event-Date-Timestamp">>, Prop, Timestamp)} + ,{<<"Call-ID">>, UUID} + ,{<<"Call-Direction">>, props:get_value(<<"Call-Direction">>, Prop, <<>>)} + ,{<<"Channel-Call-State">>, props:get_value(<<"Channel-Call-State">>, Prop, <<"HANGUP">>)} + ,{<<"Application-Name">>, <<"store">>} + ,{<<"Application-Response">>, MediaTransResults} + | wh_api:default_headers(<<"call_event">>, <<"CHANNEL_EXECUTE_COMPLETE">>, ?APP_NAME, ?APP_VERSION) + ], + EvtProp2 = case ecallmgr_util:custom_channel_vars(Prop) of + [] -> EvtProp1; + CustomProp -> [{<<"Custom-Channel-Vars">>, wh_json:from_list(CustomProp)} + | EvtProp1 + ] + end, + wapi_call:publish_event(UUID, EvtProp2). -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% @end -%%-------------------------------------------------------------------- --spec set_terminators(atom(), ne_binary(), api_binary() | ne_binaries()) -> - ecallmgr_util:send_cmd_ret(). -set_terminators(Node, UUID, Ts) -> - case get_terminators(Ts) of - 'undefined' -> 'ok'; - {K, V} -> ecallmgr_util:set(Node, UUID, <>, Timestamp} + ,{<<"Call-ID">>, UUID} + ,{<<"Application-Name">>, <<"store_fax">>} + ,{<<"Application-Response">>, Results} + | wh_api:default_headers(<<"call_event">>, <<"CHANNEL_EXECUTE_COMPLETE">>, ?APP_NAME, ?APP_VERSION) + ], + wapi_call:publish_event(UUID, Prop). %%-------------------------------------------------------------------- %% @private @@ -929,48 +951,44 @@ send_fetch_call_event(Node, UUID, JObj) -> %%-------------------------------------------------------------------- %% @private %% @doc +%% Execute extension helpers %% @end %%-------------------------------------------------------------------- --spec send_store_call_event(atom(), ne_binary(), wh_json:object() | ne_binary()) -> 'ok'. -send_store_call_event(Node, UUID, MediaTransResults) -> - Timestamp = wh_util:to_binary(wh_util:current_tstamp()), - Prop = try freeswitch:api(Node, 'uuid_dump', wh_util:to_list(UUID)) of - {'ok', Dump} -> ecallmgr_util:eventstr_to_proplist(Dump); - {'error', _Err} -> []; - 'timeout' -> [] - catch - _E:_R -> - lager:debug("failed get params from uuid_dump"), - lager:debug("~p : ~p", [_E, _R]), - lager:debug("sending less interesting call_event message"), - [] - end, - EvtProp1 = [{<<"Msg-ID">>, props:get_value(<<"Event-Date-Timestamp">>, Prop, Timestamp)} - ,{<<"Call-ID">>, UUID} - ,{<<"Call-Direction">>, props:get_value(<<"Call-Direction">>, Prop, <<>>)} - ,{<<"Channel-Call-State">>, props:get_value(<<"Channel-Call-State">>, Prop, <<"HANGUP">>)} - ,{<<"Application-Name">>, <<"store">>} - ,{<<"Application-Response">>, MediaTransResults} - | wh_api:default_headers(<<"call_event">>, <<"CHANNEL_EXECUTE_COMPLETE">>, ?APP_NAME, ?APP_VERSION) - ], - EvtProp2 = case ecallmgr_util:custom_channel_vars(Prop) of - [] -> EvtProp1; - CustomProp -> [{<<"Custom-Channel-Vars">>, wh_json:from_list(CustomProp)} - | EvtProp1 - ] - end, - wapi_call:publish_event(UUID, EvtProp2). +execute_exten_handle_reset(DP, Node, UUID, JObj) -> + case wh_json:is_true(<<"Reset">>, JObj) of + 'false' -> ok; + 'true' -> + create_dialplan_move_ccvs(<<"Execute-Extension-Original-">>, Node, UUID, DP) + end. --spec send_store_fax_call_event(ne_binary(), ne_binary()) -> 'ok'. -send_store_fax_call_event(UUID, Results) -> - Timestamp = wh_util:to_binary(wh_util:current_tstamp()), - Prop = [{<<"Msg-ID">>, Timestamp} - ,{<<"Call-ID">>, UUID} - ,{<<"Application-Name">>, <<"store_fax">>} - ,{<<"Application-Response">>, Results} - | wh_api:default_headers(<<"call_event">>, <<"CHANNEL_EXECUTE_COMPLETE">>, ?APP_NAME, ?APP_VERSION) - ], - wapi_call:publish_event(UUID, Prop). +execute_exten_handle_ccvs(DP, Node, UUID, JObj) -> + CCVs = wh_json:get_value(<<"Custom-Channel-Vars">>, JObj, wh_json:new()), + case wh_json:is_empty(CCVs) of + 'true' -> DP; + 'false' -> + ChannelVars = wh_json:to_proplist(CCVs), + [{"application", <<"set ", (ecallmgr_util:get_fs_kv(K, V, UUID))/binary>>} + || {K, V} <- ChannelVars] ++ DP + end. + +execute_exten_pre_exec(DP, Node, UUID, JObj) -> + [{"application", <<"set ", ?CHANNEL_VAR_PREFIX, "Executing-Extension=true">>} + | DP + ]. + +execute_exten_create_command(DP, Node, UUID, JObj) -> + [{"application", <<"execute_extension ", (wh_json:get_value(<<"Extension">>, JObj))/binary>>} + |DP + ]. + +execute_exten_post_exec(DP, Node, UUID, JObj) -> + [{"application", <<"unset ", ?CHANNEL_VAR_PREFIX, "Executing-Extension">>} + ,{"application", ecallmgr_util:create_masquerade_event(<<"execute_extension">> + ,<<"CHANNEL_EXECUTE_COMPLETE">> + )} + ,{"application", "park "} + |DP + ]. -spec create_dialplan_move_ccvs(ne_binary(), atom(), ne_binary(), wh_proplist()) -> wh_proplist(). create_dialplan_move_ccvs(Root, Node, UUID, DP) -> @@ -995,6 +1013,12 @@ create_dialplan_move_ccvs(Root, Node, UUID, DP) -> DP end. +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Playback command helpers +%% @end +%%-------------------------------------------------------------------- -spec play(atom(), ne_binary(), wh_json:object()) -> {ne_binary(), ne_binary()}. play(Node, UUID, JObj) -> @@ -1033,6 +1057,12 @@ play_bridged(UUID, JObj, F) -> 'undefined' -> {<<"broadcast">>, list_to_binary([UUID, <<" ">>, F, <<" both">>])} end. +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% TTS command helpers +%% @end +%%-------------------------------------------------------------------- -spec tts_flite(atom(), ne_binary(), wh_json:object()) -> {ne_binary(), ne_binary()}. tts_flite(Node, UUID, JObj) -> @@ -1050,3 +1080,33 @@ tts_flite_voice(JObj) -> 'true' -> tts_flite_voice(wh_json:get_value(<<"Voice">>, JObj)); 'false' -> tts_flite_voice(<<"female">>) end. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% @end +%%-------------------------------------------------------------------- +-spec get_terminators(api_binary() | ne_binaries() | wh_json:object()) -> + {ne_binary(), ne_binary()}. +get_terminators('undefined') -> 'undefined'; +get_terminators(Ts) when is_binary(Ts) -> + get_terminators([Ts]); +get_terminators([_|_]=Ts) -> + case Ts =:= get('$prior_terminators') of + 'true' -> 'undefined'; + 'false' -> + put('$prior_terminators', Ts), + case wh_util:is_empty(Ts) of + 'true' -> {<<"playback_terminators">>, <<"none">>}; + 'false' -> {<<"playback_terminators">>, wh_util:to_binary(Ts)} + end + end; +get_terminators(JObj) -> get_terminators(wh_json:get_ne_value(<<"Terminators">>, JObj)). + +-spec set_terminators(atom(), ne_binary(), api_binary() | ne_binaries()) -> + ecallmgr_util:send_cmd_ret(). +set_terminators(Node, UUID, Ts) -> + case get_terminators(Ts) of + 'undefined' -> 'ok'; + {K, V} -> ecallmgr_util:set(Node, UUID, <>, Routes, _JObj) -> +route_resp_xml(<<"bridge">>, Routes, JObj) -> lager:debug("creating a bridge XML response"), LogEl = route_resp_log_winning_node(), - RingbackEl = route_resp_ringback(), + RingbackEl = route_resp_ringback(JObj), + TransferEl = route_resp_transfer_ringback(JObj), %% format the Route based on protocol {_Idx, Extensions} = lists:foldr( fun(RouteJObj, {Idx, Acc}) -> @@ -170,20 +171,21 @@ route_resp_xml(<<"bridge">>, Routes, _JObj) -> FailConditionEl = condition_el(FailRespondEl), FailExtEl = extension_el(<<"failed_bridge">>, <<"false">>, [FailConditionEl]), - ContextEl = context_el(?WHISTLE_CONTEXT, [LogEl, RingbackEl] ++ Extensions ++ [FailExtEl]), + ContextEl = context_el(?WHISTLE_CONTEXT, [LogEl, RingbackEl, TransferEl] ++ Extensions ++ [FailExtEl]), SectionEl = section_el(<<"dialplan">>, <<"Route Bridge Response">>, ContextEl), {ok, xmerl:export([SectionEl], fs_xml)}; route_resp_xml(<<"park">>, _Routes, JObj) -> LogEl = route_resp_log_winning_node(), - RingbackEl = route_resp_ringback(), + RingbackEl = route_resp_ringback(JObj), + TransferEl = route_resp_transfer_ringback(JObj), ParkEl = action_el(<<"park">>), ParkConditionEl = case route_resp_pre_park_action(JObj) of undefined -> - condition_el([LogEl, RingbackEl, ParkEl]); + condition_el([LogEl, RingbackEl, TransferEl, ParkEl]); PreParkEl -> - condition_el([LogEl, RingbackEl, PreParkEl, ParkEl]) + condition_el([LogEl, RingbackEl, TransferEl, PreParkEl, ParkEl]) end, ParkExtEl = extension_el(<<"park">>, undefined, [ParkConditionEl]), @@ -195,13 +197,14 @@ route_resp_xml(<<"park">>, _Routes, JObj) -> route_resp_xml(<<"error">>, _Routes, JObj) -> LogEl = route_resp_log_winning_node(), - RingbackEl = route_resp_ringback(), + RingbackEl = route_resp_ringback(JObj), + TransferEl = route_resp_transfer_ringback(JObj), ErrCode = wh_json:get_value(<<"Route-Error-Code">>, JObj), ErrMsg = [" ", wh_json:get_value(<<"Route-Error-Message">>, JObj, <<"">>)], ErrEl = action_el(<<"respond">>, [ErrCode, ErrMsg]), - ErrCondEl = condition_el([LogEl, RingbackEl, ErrEl]), + ErrCondEl = condition_el([LogEl, RingbackEl, TransferEl, ErrEl]), ErrExtEl = extension_el([ErrCondEl]), ContextEl = context_el(?WHISTLE_CONTEXT, [ErrExtEl]), @@ -218,10 +221,29 @@ not_found() -> route_resp_log_winning_node() -> action_el(<<"log">>, [<<"NOTICE log|${uuid}|", (wh_util:to_binary(node()))/binary, " won call control">>]). --spec route_resp_ringback() -> xml_el(). -route_resp_ringback() -> - {ok, RBSetting} = ecallmgr_util:get_setting(<<"default_ringback">>, <<"%(2000,4000,440,480)">>), - action_el(<<"set">>, <<"ringback=", (wh_util:to_binary(RBSetting))/binary>>). +-spec route_resp_ringback(wh_json:object()) -> xml_el(). +route_resp_ringback(JObj) -> + case wh_json:get_value(<<"Ringback-Media">>, JObj) of + 'undefined' -> + {ok, RBSetting} = ecallmgr_util:get_setting(<<"default_ringback">>, <<"%(2000,4000,440,480)">>), + action_el(<<"set">>, <<"ringback=", (wh_util:to_binary(RBSetting))/binary>>); + Media -> + MsgId = wh_json:get_value(<<"Msg-ID">>, JObj), + Stream = ecallmgr_util:media_path(Media, extant, MsgId, JObj), + action_el(<<"set">>, <<"ringback=", (wh_util:to_binary(Stream))/binary>>) + end. + +-spec route_resp_transfer_ringback(wh_json:object()) -> xml_el(). +route_resp_transfer_ringback(JObj) -> + case wh_json:get_value(<<"Transfer-Media">>, JObj) of + 'undefined' -> + {ok, RBSetting} = ecallmgr_util:get_setting(<<"default_ringback">>, <<"%(2000,4000,440,480)">>), + action_el(<<"set">>, <<"transfer_ringback=", (wh_util:to_binary(RBSetting))/binary>>); + Media -> + MsgId = wh_json:get_value(<<"Msg-ID">>, JObj), + Stream = ecallmgr_util:media_path(Media, extant, MsgId, JObj), + action_el(<<"set">>, <<"transfer_ringback=", (wh_util:to_binary(Stream))/binary>>) + end. -spec route_resp_pre_park_action(wh_json:object()) -> 'undefined' | xml_el(). route_resp_pre_park_action(JObj) -> diff --git a/lib/whistle-1.0.0/src/api/wapi_route.erl b/lib/whistle-1.0.0/src/api/wapi_route.erl index 0273598c119..3550ff92dd7 100644 --- a/lib/whistle-1.0.0/src/api/wapi_route.erl +++ b/lib/whistle-1.0.0/src/api/wapi_route.erl @@ -35,12 +35,13 @@ ,<<"Caller-ID-Name">>, <<"Caller-ID-Number">> ]). -define(OPTIONAL_ROUTE_REQ_HEADERS, [<<"Geo-Location">>, <<"Orig-IP">> - ,<<"Max-Call-Length">>, <<"Media">> - ,<<"Transcode">>, <<"Codecs">> - ,<<"Custom-Channel-Vars">> - ,<<"Resource-Type">>, <<"Cost-Parameters">> - ,<<"From-Network-Addr">> - ,<<"Switch-Hostname">>, <<"Switch-Nodename">> + ,<<"Max-Call-Length">>, <<"Media">> + ,<<"Transcode">>, <<"Codecs">> + ,<<"Custom-Channel-Vars">> + ,<<"Resource-Type">>, <<"Cost-Parameters">> + ,<<"From-Network-Addr">> + ,<<"Switch-Hostname">>, <<"Switch-Nodename">> + ,<<"Ringback-Media">>, <<"Transfer-Media">> ]). -define(ROUTE_REQ_VALUES, [{<<"Event-Category">>, ?EVENT_CATEGORY} ,{<<"Event-Name">>, ?ROUTE_REQ_EVENT_NAME} @@ -72,12 +73,12 @@ %% Route Responses -define(ROUTE_RESP_ROUTE_HEADERS, [<<"Invite-Format">>]). -define(OPTIONAL_ROUTE_RESP_ROUTE_HEADERS, [<<"Route">>, <<"To-User">>, <<"To-Realm">>, <<"To-DID">> - ,<<"Proxy-Via">>, <<"Media">>, <<"Auth-User">> - ,<<"Auth-Password">>, <<"Codecs">>, <<"Progress-Timeout">> - ,<<"Caller-ID-Name">>, <<"Caller-ID-Number">>, <<"Caller-ID-Type">> - ,<<"Rate">>, <<"Rate-Increment">>, <<"Rate-Minimum">>, <<"Surcharge">> - ,<<"SIP-Headers">>, <<"Custom-Channel-Vars">> - ,<<"Weight-Cost">>, <<"Weight-Location">> + ,<<"Proxy-Via">>, <<"Media">>, <<"Auth-User">> + ,<<"Auth-Password">>, <<"Codecs">>, <<"Progress-Timeout">> + ,<<"Caller-ID-Name">>, <<"Caller-ID-Number">>, <<"Caller-ID-Type">> + ,<<"Rate">>, <<"Rate-Increment">>, <<"Rate-Minimum">>, <<"Surcharge">> + ,<<"SIP-Headers">>, <<"Custom-Channel-Vars">> + ,<<"Weight-Cost">>, <<"Weight-Location">> ]). -define(ROUTE_RESP_ROUTE_VALUES, [{<<"Media">>, [<<"process">>, <<"bypass">>, <<"auto">>]} ,{<<"Caller-ID-Type">>, [<<"from">>, <<"rpid">>, <<"pid">>]} @@ -94,8 +95,9 @@ %% Route Responses -define(ROUTE_RESP_HEADERS, [<<"Method">>]). -define(OPTIONAL_ROUTE_RESP_HEADERS, [<<"Custom-Channel-Vars">>, <<"Routes">> - ,<<"Route-Error-Code">>, <<"Route-Error-Message">> - ,<<"Pre-Park">> + ,<<"Route-Error-Code">>, <<"Route-Error-Message">> + ,<<"Ringback-Media">>, <<"Transfer-Media">> + ,<<"Pre-Park">> ]). -define(ROUTE_RESP_VALUES, [{<<"Event-Category">>, ?EVENT_CATEGORY} ,{<<"Event-Name">>, <<"route_resp">>} diff --git a/lib/whistle_number_manager-1.0.0/src/wh_number_manager.erl b/lib/whistle_number_manager-1.0.0/src/wh_number_manager.erl index f966c3b35cd..e16fca3c14e 100644 --- a/lib/whistle_number_manager-1.0.0/src/wh_number_manager.erl +++ b/lib/whistle_number_manager-1.0.0/src/wh_number_manager.erl @@ -63,19 +63,19 @@ lookup_account_by_number(undefined) -> {error, not_reconcilable}; lookup_account_by_number(Number) -> try wnm_number:get(Number) of - #number{assigned_to=undefined} -> + #number{assigned_to=undefined} -> lager:debug("number ~s not assigned to an account", [Number]), - {error, unassigned}; - #number{assigned_to=AssignedTo, state = <<"port_in">>}=N -> + {error, unassigned}; + #number{assigned_to=AssignedTo, state = <<"port_in">>}=N -> lager:debug("number ~s is assigned to ~s in state port_in", [Number, AssignedTo]), {ok, AssignedTo, number_options(N)}; - #number{assigned_to=AssignedTo, state = <<"in_service">>}=N -> + #number{assigned_to=AssignedTo, state = <<"in_service">>}=N -> lager:debug("number ~s is assigned to ~s in state in_service", [Number, AssignedTo]), {ok, AssignedTo, number_options(N)}; - #number{assigned_to=AssignedTo, state = <<"port_out">>}=N -> + #number{assigned_to=AssignedTo, state = <<"port_out">>}=N -> lager:debug("number ~s is assigned to ~s in state port_in", [Number, AssignedTo]), {ok, AssignedTo, number_options(N)}; - #number{assigned_to=AssignedTo, state=State} -> + #number{assigned_to=AssignedTo, state=State} -> lager:debug("number ~s assigned to acccount id ~s but in state ~s", [Number, AssignedTo, State]), {error, {not_in_service, AssignedTo}} catch @@ -88,14 +88,22 @@ number_options(#number{state=State, features=Features, module_name=Module}=Numbe ,{pending_port, State =:= <<"port_in">>} ,{local, Module =:= wnm_local} ,{inbound_cnam, sets:is_element(<<"inbound_cnam">>, Features)} + ,{ringback_media, find_early_ringback(Number)} + ,{transfer_media, find_transfer_ringback(Number)} ]. should_force_outbound(#number{module_name=wnm_local}) -> true; should_force_outbound(#number{state = <<"port_in">>}) -> true; should_force_outbound(#number{state = <<"port_out">>}) -> true; -should_force_outbound(#number{number_doc=JObj}) -> +should_force_outbound(#number{number_doc=JObj}) -> wh_json:is_true(<<"force_outbound">>, JObj, false). +find_early_ringback(#number{number_doc=JObj}) -> + wh_json:get_ne_value([<<"ringback">>, <<"early">>], JObj). + +find_transfer_ringback(#number{number_doc=JObj}) -> + wh_json:get_ne_value([<<"ringback">>, <<"transfer">>], JObj). + %%-------------------------------------------------------------------- %% @public %% @doc diff --git a/whistle_apps/apps/callflow/src/cf_route_req.erl b/whistle_apps/apps/callflow/src/cf_route_req.erl index ae302ac33e4..6f5679ba3a3 100644 --- a/whistle_apps/apps/callflow/src/cf_route_req.erl +++ b/whistle_apps/apps/callflow/src/cf_route_req.erl @@ -28,7 +28,7 @@ handle_req(JObj, Options) -> ,whapps_call:account_id(Call) ]), cache_call(Flow, NoMatch, ControllerQ, Call), - send_route_response(JObj, ControllerQ, Call); + send_route_response(Flow, JObj, ControllerQ, Call); {ok, _, true} -> lager:info("only available callflow is a nomatch for a unauthorized call", []); {error, R} -> @@ -77,19 +77,37 @@ callflow_should_respond(Call) -> %% process %% @end %%----------------------------------------------------------------------------- --spec send_route_response(wh_json:object(), ne_binary(), whapps_call:call()) -> 'ok'. -send_route_response(JObj, Q, Call) -> - Resp = [{<<"Msg-ID">>, wh_json:get_value(<<"Msg-ID">>, JObj)} - ,{<<"Routes">>, []} - ,{<<"Method">>, <<"park">>} - ,{<<"Pre-Park">>, pre_park_action(Call)} - | wh_api:default_headers(Q, ?APP_NAME, ?APP_VERSION) - ], +-spec send_route_response(wh_json:object(), wh_json:object(), ne_binary(), whapps_call:call()) -> 'ok'. +send_route_response(Flow, JObj, Q, Call) -> + Resp = props:filter_undefined([{<<"Msg-ID">>, wh_json:get_value(<<"Msg-ID">>, JObj)} + ,{<<"Routes">>, []} + ,{<<"Method">>, <<"park">>} + ,{<<"Transfer-Media">>, get_transfer_media(Flow, JObj)} + ,{<<"Ringback-Media">>, get_ringback_media(Flow, JObj)} + ,{<<"Pre-Park">>, pre_park_action(Call)} + | wh_api:default_headers(Q, ?APP_NAME, ?APP_VERSION) + ]), ServerId = wh_json:get_value(<<"Server-ID">>, JObj), Publisher = fun(P) -> wapi_route:publish_resp(ServerId, P) end, whapps_util:amqp_pool_send(Resp, Publisher), lager:info("callflows knows how to route the call! sent park response"). +-spec get_transfer_media(wh_json:object(), wh_json:object()) -> api_binary(). +get_transfer_media(Flow, JObj) -> + case wh_json:get_value([<<"ringback">>, <<"transfer">>], Flow) of + 'undefined' -> + wh_json:get_value(<<"Transfer-Media">>, JObj); + MediaId -> MediaId + end. + +-spec get_ringback_media(wh_json:object(), wh_json:object()) -> api_binary(). +get_ringback_media(Flow, JObj) -> + case wh_json:get_value([<<"ringback">>, <<"early">>], Flow) of + 'undefined' -> + wh_json:get_value(<<"Ringback-Media">>, JObj); + MediaId -> MediaId + end. + %%----------------------------------------------------------------------------- %% @private %% @doc diff --git a/whistle_apps/apps/stepswitch/src/stepswitch_inbound.erl b/whistle_apps/apps/stepswitch/src/stepswitch_inbound.erl index 097474c7067..e1210715a44 100644 --- a/whistle_apps/apps/stepswitch/src/stepswitch_inbound.erl +++ b/whistle_apps/apps/stepswitch/src/stepswitch_inbound.erl @@ -105,12 +105,26 @@ custom_channel_vars(AccountId, JObj) -> -spec relay_route_req(ne_binary(), proplist(), wh_json:json_object()) -> 'ok'. relay_route_req(AccountId, Props, JObj) -> Routines = [fun(J) -> custom_channel_vars(AccountId, J) end - ,fun(J) -> + ,fun(J) -> case props:get_value(inbound_cnam, Props) of false -> J; true -> stepswitch_cnam:lookup(J) end end + ,fun(J) -> + case props:get_value(ringback_media, Props) of + undefined -> J; + MediaId -> + wh_json:set_value(<<"Ringback-Media">>, MediaId, J) + end + end + ,fun(J) -> + case props:get_value(transfer_media, Props) of + undefined -> J; + MediaId -> + wh_json:set_value(<<"Transfer-Media">>, MediaId, J) + end + end ], wapi_route:publish_req(lists:foldl(fun(F, J) -> F(J) end, JObj, Routines)), lager:debug("relayed route request"). From 75913f3076be27649080d81b242a0570aa231a95 Mon Sep 17 00:00:00 2001 From: karl anderson Date: Wed, 3 Apr 2013 18:06:43 +0000 Subject: [PATCH 2/2] KAZOO-661: continue to conditionally import hold_music as the ramifications of not doing so are unclear --- ecallmgr/src/ecallmgr_call_command.erl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ecallmgr/src/ecallmgr_call_command.erl b/ecallmgr/src/ecallmgr_call_command.erl index 129657f78a6..dc4eef0e814 100644 --- a/ecallmgr/src/ecallmgr_call_command.erl +++ b/ecallmgr/src/ecallmgr_call_command.erl @@ -675,7 +675,15 @@ bridge_handle_ringback(Node, UUID, JObj) -> case wh_json:get_value(<<"Ringback">>, JObj) of 'undefined' -> case wh_json:get_value([<<"Custom-Channel-Vars">>, <<"Ringback">>], JObj) of - 'undefined' -> 'ok'; + 'undefined' -> + case ecallmgr_fs_channel:import_moh(UUID) of + 'true' -> + [{"application", "export hold_music=${hold_music}"} + ,{"application", "set import=hold_music"} + |DP + ]; + 'false' -> DP + end; Media -> Stream = ecallmgr_util:media_path(Media, extant, UUID, JObj), lager:debug("bridge has custom ringback in channel vars: ~s", [Stream]),