diff --git a/applications/acdc/priv/couchdb/views/agents.json b/applications/acdc/priv/couchdb/views/agents.json index f3d349306e6..ab95340e2da 100644 --- a/applications/acdc/priv/couchdb/views/agents.json +++ b/applications/acdc/priv/couchdb/views/agents.json @@ -20,6 +20,7 @@ " 'first_name': doc.first_name,", " 'last_name': doc.last_name,", " 'queues': doc.queues,", + " 'agent_priority': doc.acdc_agent_priority || 0,", " 'skills': doc.acdc_skills || []", " });", "}" diff --git a/applications/acdc/priv/couchdb/views/queues.json b/applications/acdc/priv/couchdb/views/queues.json index dc37c63594b..3d0b681b295 100644 --- a/applications/acdc/priv/couchdb/views/queues.json +++ b/applications/acdc/priv/couchdb/views/queues.json @@ -19,12 +19,13 @@ " for (i in doc.queues) {", " emit(doc.queues[i], {", " 'id': doc._id,", - " 'agent_priority': doc.acdc_agent_priority,", - " 'skills': doc.acdc_skills", + " 'agent_priority': doc.acdc_agent_priority || 0,", + " 'skills': doc.acdc_skills || []", " });", " }", "}" - ] + ], + "reduce": "_count" }, "crossbar_listing": { "map": [ diff --git a/applications/acdc/src/acdc.hrl b/applications/acdc/src/acdc.hrl index a7d2aa628b6..cf42b50c4d9 100644 --- a/applications/acdc/src/acdc.hrl +++ b/applications/acdc/src/acdc.hrl @@ -12,10 +12,11 @@ -define(CACHE_NAME, 'acdc_cache'). --define(ABANDON_TIMEOUT, 'member_timeout'). --define(ABANDON_EXIT, 'member_exit'). --define(ABANDON_HANGUP, 'member_hangup'). --define(ABANDON_EMPTY, 'member_exit_empty'). +-define(ABANDON_TIMEOUT, <<"member_timeout">>). +-define(ABANDON_EXIT, <<"member_exit">>). +-define(ABANDON_HANGUP, <<"member_hangup">>). +-define(ABANDON_EMPTY, <<"member_exit_empty">>). +-define(ABANDON_INTERNAL_ERROR, <<"INTERNAL ERROR">>). -define(PRESENCE_GREEN, <<"terminated">>). -define(PRESENCE_RED_FLASH, <<"early">>). @@ -35,9 +36,6 @@ -define(DESTROYED_CHANNEL_REG(AccountId, User), {'p', 'l', {'destroyed_channel', AccountId, User}}). -define(DESTROYED_CHANNEL(CallId, HangupCause), {'call_down', CallId, HangupCause}). --type abandon_reason() :: ?ABANDON_TIMEOUT | ?ABANDON_EXIT | - ?ABANDON_HANGUP. - -type deliveries() :: [gen_listener:basic_deliver()]. -type announcements_pids() :: #{kz_term:ne_binary() => pid()}. diff --git a/applications/acdc/src/acdc_agent_fsm.erl b/applications/acdc/src/acdc_agent_fsm.erl index 734261add11..3f8b505790f 100644 --- a/applications/acdc/src/acdc_agent_fsm.erl +++ b/applications/acdc/src/acdc_agent_fsm.erl @@ -12,10 +12,10 @@ %%% @end %%%----------------------------------------------------------------------------- -module(acdc_agent_fsm). --behaviour(gen_fsm). +-behaviour(gen_statem). %% API --export([start_link/2, start_link/3, start_link/4, start_link/5 +-export([start_link/3, start_link/4, start_link/5 ,call_event/4 ,member_connect_req/2 ,member_connect_win/3 @@ -45,29 +45,15 @@ -export([wait_for_listener/4]). -%% gen_fsm callbacks +%% gen_statem callbacks -export([init/1 - ,handle_event/3 - ,handle_sync_event/4 - ,handle_info/3 + ,callback_mode/0 ,terminate/3 ,code_change/4 ]). %% Agent states --export([wait/2 - ,sync/2 - ,ready/2 - ,ringing/2 - ,ringing_callback/2 - ,awaiting_callback/2 - ,answered/2 - ,wrapup/2 - ,paused/2 - ,outbound/2 - ,inbound/2 - - ,wait/3 +-export([wait/3 ,sync/3 ,ready/3 ,ringing/3 @@ -159,106 +145,113 @@ %% @end %%------------------------------------------------------------------------------ -spec member_connect_req(pid(), kz_json:object()) -> 'ok'. -member_connect_req(FSM, JObj) -> - gen_fsm:send_event(FSM, {'member_connect_req', JObj}). +member_connect_req(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'member_connect_req', JObj}). %%------------------------------------------------------------------------------ -%% @doc When a queue receives a call and needs an agent, it will send a -%% member_connect_req. The agent will respond (if possible) with a -%% member_connect_resp payload or ignore the request +%% @doc When an agent has been selected to handle the queue call, each process +%% for the agent will receive a `member_connect_win' event. The event will +%% include a flag of whether the winner is on the current node - if true, the +%% agent process will handle call control. Otherwise, the agent process will +%% just follow along through state transitions. %% @end %%------------------------------------------------------------------------------ --spec member_connect_win(pid(), kz_json:object(), 'same_node' | 'different_node') -> 'ok'. -member_connect_win(FSM, JObj, Node) -> - gen_fsm:send_event(FSM, {'member_connect_win', JObj, Node}). +-type member_connect_win_node() :: 'same_node' | 'different_node'. +-spec member_connect_win(pid(), kz_json:object(), member_connect_win_node()) -> 'ok'. +member_connect_win(ServerRef, JObj, Node) -> + gen_statem:cast(ServerRef, {'member_connect_win', JObj, Node}). -spec member_connect_satisfied(pid(), kz_json:object(), 'same_node'|'different_node') -> 'ok'. -member_connect_satisfied(FSM, JObj, Node) -> - gen_fsm:send_event(FSM, {'member_connect_satisfied', JObj, Node}). +member_connect_satisfied(ServerRef, JObj, Node) -> + gen_statem:cast(ServerRef, {'member_connect_satisfied', JObj, Node}). -spec agent_timeout(pid(), kz_json:object()) -> 'ok'. -agent_timeout(FSM, JObj) -> - gen_fsm:send_event(FSM, {'agent_timeout', JObj}). +agent_timeout(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'agent_timeout', JObj}). -spec shared_failure(pid(), kz_json:object()) -> 'ok'. -shared_failure(FSM, JObj) -> - gen_fsm:send_event(FSM, {'shared_failure', JObj}). +shared_failure(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'shared_failure', JObj}). -spec shared_call_id(pid(), kz_json:object()) -> 'ok'. -shared_call_id(FSM, JObj) -> - gen_fsm:send_event(FSM, {'shared_call_id', JObj}). +shared_call_id(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'shared_call_id', JObj}). %%------------------------------------------------------------------------------ %% @doc When an agent is involved in a call, it will receive call events. -%% Pass the call event to the FSM to see if action is needed (usually +%% Pass the call event to the ServerRef to see if action is needed (usually %% for bridge and hangup events). %% @end %%------------------------------------------------------------------------------ -spec call_event(pid(), kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object()) -> 'ok'. -call_event(FSM, <<"call_event">>, <<"CHANNEL_BRIDGE">>, JObj) -> - gen_fsm:send_event(FSM, {'channel_bridged', call_id(JObj)}); -call_event(FSM, <<"call_event">>, <<"CHANNEL_UNBRIDGE">>, JObj) -> - gen_fsm:send_event(FSM, {'channel_unbridged', call_id(JObj)}); -call_event(FSM, <<"call_event">>, <<"usurp_control">>, JObj) -> - gen_fsm:send_event(FSM, {'usurp_control', call_id(JObj)}); -call_event(FSM, <<"call_event">>, <<"CHANNEL_DESTROY">>, JObj) -> - gen_fsm:send_event(FSM, ?DESTROYED_CHANNEL(call_id(JObj), acdc_util:hangup_cause(JObj))); -call_event(FSM, <<"call_event">>, <<"CHANNEL_DISCONNECTED">>, JObj) -> - gen_fsm:send_event(FSM, ?DESTROYED_CHANNEL(call_id(JObj), <<"MEDIA_SERVER_UNREACHABLE">>)); -call_event(FSM, <<"call_event">>, <<"LEG_CREATED">>, JObj) -> +call_event(ServerRef, <<"call_event">>, <<"CHANNEL_BRIDGE">>, JObj) -> + gen_statem:cast(ServerRef, {'channel_bridged', call_id(JObj)}); +call_event(ServerRef, <<"call_event">>, <<"CHANNEL_UNBRIDGE">>, JObj) -> + gen_statem:cast(ServerRef, {'channel_unbridged', call_id(JObj)}); +call_event(ServerRef, <<"call_event">>, <<"usurp_control">>, JObj) -> + gen_statem:cast(ServerRef, {'usurp_control', call_id(JObj)}); +call_event(ServerRef, <<"call_event">>, <<"CHANNEL_DESTROY">>, JObj) -> + gen_statem:cast(ServerRef, ?DESTROYED_CHANNEL(call_id(JObj), acdc_util:hangup_cause(JObj))); +call_event(ServerRef, <<"call_event">>, <<"CHANNEL_DISCONNECTED">>, JObj) -> + gen_statem:cast(ServerRef, ?DESTROYED_CHANNEL(call_id(JObj), <<"MEDIA_SERVER_UNREACHABLE">>)); +call_event(ServerRef, <<"call_event">>, <<"LEG_CREATED">>, JObj) -> %% Due to change in kapi_call to send events based on Origination-Call-ID, %% we do not want to bind to any of the loopback legs case kz_json:get_ne_binary_value(<<"Channel-Loopback-Leg">>, JObj) of 'undefined' -> - gen_fsm:send_event(FSM, {'leg_created' + gen_statem:cast(ServerRef, {'leg_created' ,call_id(JObj) ,kz_call_event:other_leg_call_id(JObj) }); _ -> 'ok' end; -call_event(FSM, <<"call_event">>, <<"LEG_DESTROYED">>, JObj) -> - gen_fsm:send_event(FSM, {'leg_destroyed', call_id(JObj)}); -call_event(FSM, <<"call_event">>, <<"CHANNEL_ANSWER">>, JObj) -> - gen_fsm:send_event(FSM, {'channel_answered', JObj}); -call_event(FSM, <<"call_event">>, <<"DTMF">>, EvtJObj) -> - gen_fsm:send_event(FSM, {'dtmf_pressed', kz_json:get_value(<<"DTMF-Digit">>, EvtJObj)}); -call_event(FSM, <<"call_event">>, <<"CHANNEL_EXECUTE_COMPLETE">>, JObj) -> - maybe_send_execute_complete(FSM, kz_json:get_value(<<"Application-Name">>, JObj), JObj); -call_event(FSM, <<"error">>, <<"dialplan">>, JObj) -> +call_event(ServerRef, <<"call_event">>, <<"LEG_DESTROYED">>, JObj) -> + gen_statem:cast(ServerRef, {'leg_destroyed', call_id(JObj)}); +call_event(ServerRef, <<"call_event">>, <<"CHANNEL_ANSWER">>, JObj) -> + gen_statem:cast(ServerRef, {'channel_answered', JObj}); +call_event(ServerRef, <<"call_event">>, <<"DTMF">>, EvtJObj) -> + gen_statem:cast(ServerRef, {'dtmf_pressed', kz_json:get_value(<<"DTMF-Digit">>, EvtJObj)}); +call_event(ServerRef, <<"call_event">>, <<"CHANNEL_EXECUTE_COMPLETE">>, JObj) -> + maybe_send_execute_complete(ServerRef, kz_json:get_value(<<"Application-Name">>, JObj), JObj); +call_event(ServerRef, <<"error">>, <<"dialplan">>, JObj) -> _ = kz_log:put_callid(JObj), lager:debug("error event: ~s", [kz_json:get_value(<<"Error-Message">>, JObj)]), Req = kz_json:get_value(<<"Request">>, JObj), - gen_fsm:send_event(FSM, {'dialplan_error', kz_json:get_value(<<"Application-Name">>, Req)}); -call_event(FSM, <<"call_event">>, <<"CHANNEL_REPLACED">>, JObj) -> - gen_fsm:send_event(FSM, {'channel_replaced', JObj}); -call_event(FSM, <<"call_event">>, <<"CHANNEL_TRANSFEREE">>, JObj) -> + gen_statem:cast(ServerRef, {'dialplan_error', kz_json:get_value(<<"Application-Name">>, Req)}); +call_event(ServerRef, <<"call_event">>, <<"CHANNEL_REPLACED">>, JObj) -> + gen_statem:cast(ServerRef, {'channel_replaced', JObj}); +call_event(ServerRef, <<"call_event">>, <<"CHANNEL_TRANSFEREE">>, JObj) -> Transferor = kz_call_event:other_leg_call_id(JObj), Transferee = kz_call_event:call_id(JObj), - gen_fsm:send_event(FSM, {'channel_transferee', Transferor, Transferee}); -call_event(FSM, <<"call_event">>, <<"PLAYBACK_STOP">>, JObj) -> - gen_fsm:send_event(FSM, {'playback_stop', JObj}); -call_event(_FSM, <<"call_event">>, <<"CALL_UPDATE">>, _JObj) -> - % lager:debug("unhandled CALL_UPDATE: ~p", [JObj]); - 'ok'; + gen_statem:cast(ServerRef, {'channel_transferee', Transferor, Transferee}); call_event(_, _C, _E, _) -> - lager:info("unhandled combo: ~s/~s", [_C, _E]). + lager:debug("unhandled combo: ~s/~s", [_C, _E]), + 'ok'. %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec maybe_send_execute_complete(pid(), kz_term:ne_binary(), kz_json:object()) -> 'ok'. -maybe_send_execute_complete(FSM, <<"bridge">>, JObj) -> +maybe_send_execute_complete(ServerRef, <<"bridge">>, JObj) -> lager:info("send EXECUTE_COMPLETE,bridge to ~p with ci: ~s, olci: ~s", - [FSM + [ServerRef ,call_id(JObj) ,kz_call_event:other_leg_call_id(JObj) ]), - gen_fsm:send_event(FSM, {'channel_unbridged', call_id(JObj)}); -maybe_send_execute_complete(FSM, <<"call_pickup">>, JObj) -> - gen_fsm:send_event(FSM, {'channel_bridged', call_id(JObj)}); + gen_statem:cast(ServerRef, {'channel_unbridged', call_id(JObj)}); +maybe_send_execute_complete(ServerRef, <<"call_pickup">>, JObj) -> + gen_statem:cast(ServerRef, {'channel_bridged', call_id(JObj)}); +maybe_send_execute_complete(ServerRef, <<"play">>, JObj) -> + case kz_json:get_value(<<"Application-Response">>, JObj) of + <<"FILE PLAYED">> -> + gen_statem:cast(ServerRef, {'playback_stop', call_id(JObj)}); + AR -> + lager:debug("application Response: ~p", [AR]), + gen_statem:cast(ServerRef, {'playback_stop', call_id(JObj)}) + end; maybe_send_execute_complete(_, _, _) -> 'ok'. %%------------------------------------------------------------------------------ @@ -266,20 +259,20 @@ maybe_send_execute_complete(_, _, _) -> 'ok'. %% @end %%------------------------------------------------------------------------------ -spec originate_ready(kz_term:server_ref(), kz_json:object()) -> 'ok'. -originate_ready(FSM, JObj) -> - gen_fsm:send_event(FSM, {'originate_ready', JObj}). +originate_ready(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'originate_ready', JObj}). -spec originate_resp(kz_term:server_ref(), kz_json:object()) -> 'ok'. -originate_resp(FSM, JObj) -> - gen_fsm:send_event(FSM, {'originate_resp', kz_json:get_value(<<"Call-ID">>, JObj)}). +originate_resp(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'originate_resp', kz_json:get_value(<<"Call-ID">>, JObj)}). -spec originate_started(kz_term:server_ref(), kz_json:object()) -> 'ok'. -originate_started(FSM, JObj) -> - gen_fsm:send_event(FSM, {'originate_started', kz_json:get_value(<<"Call-ID">>, JObj)}). +originate_started(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'originate_started', kz_json:get_value(<<"Call-ID">>, JObj)}). -spec originate_uuid(kz_term:server_ref(), kz_json:object()) -> 'ok'. -originate_uuid(FSM, JObj) -> - gen_fsm:send_event(FSM, {'originate_uuid' +originate_uuid(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'originate_uuid' ,kz_json:get_value(<<"Outbound-Call-ID">>, JObj) ,kz_json:get_value(<<"Outbound-Call-Control-Queue">>, JObj) }). @@ -289,48 +282,48 @@ originate_uuid(FSM, JObj) -> %% @end %%------------------------------------------------------------------------------ -spec originate_failed(kz_term:server_ref(), kz_json:object()) -> 'ok'. -originate_failed(FSM, JObj) -> - gen_fsm:send_event(FSM, {'originate_failed', JObj}). +originate_failed(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'originate_failed', JObj}). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec sync_req(kz_term:server_ref(), kz_json:object()) -> 'ok'. -sync_req(FSM, JObj) -> - gen_fsm:send_event(FSM, {'sync_req', JObj}). +sync_req(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'sync_req', JObj}). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec sync_resp(kz_term:server_ref(), kz_json:object()) -> 'ok'. -sync_resp(FSM, JObj) -> - gen_fsm:send_event(FSM, {'sync_resp', JObj}). +sync_resp(ServerRef, JObj) -> + gen_statem:cast(ServerRef, {'sync_resp', JObj}). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec pause(kz_term:server_ref(), kz_term:timeout(), kz_term:api_binary()) -> 'ok'. -pause(FSM, Timeout, Alias) -> - gen_fsm:send_all_state_event(FSM, {'pause', Timeout, Alias}). +pause(ServerRef, Timeout, Alias) -> + gen_statem:cast(ServerRef, {'pause', Timeout, Alias}). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec resume(kz_term:server_ref()) -> 'ok'. -resume(FSM) -> - gen_fsm:send_all_state_event(FSM, {'resume'}). +resume(ServerRef) -> + gen_statem:cast(ServerRef, {'resume'}). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec end_wrapup(kz_term:server_ref()) -> 'ok'. -end_wrapup(FSM) -> - gen_fsm:send_all_state_event(FSM, {'end_wrapup'}). +end_wrapup(ServerRef) -> + gen_statem:cast(ServerRef, {'end_wrapup'}). %%------------------------------------------------------------------------------ %% @doc Request the agent listener bind to queue and conditionally send an @@ -338,8 +331,8 @@ end_wrapup(FSM) -> %% @end %%------------------------------------------------------------------------------ -spec add_acdc_queue(kz_term:server_ref(), kz_term:ne_binary()) -> 'ok'. -add_acdc_queue(FSM, QueueId) -> - gen_fsm:send_all_state_event(FSM, {'add_acdc_queue', QueueId}). +add_acdc_queue(ServerRef, QueueId) -> + gen_statem:cast(ServerRef, {'add_acdc_queue', QueueId}). %%------------------------------------------------------------------------------ %% @doc Request the agent listener unbind from queue and send an @@ -347,55 +340,45 @@ add_acdc_queue(FSM, QueueId) -> %% @end %%------------------------------------------------------------------------------ -spec rm_acdc_queue(kz_term:server_ref(), kz_term:ne_binary()) -> 'ok'. -rm_acdc_queue(FSM, QueueId) -> - gen_fsm:send_all_state_event(FSM, {'rm_acdc_queue', QueueId}). +rm_acdc_queue(ServerRef, QueueId) -> + gen_statem:cast(ServerRef, {'rm_acdc_queue', QueueId}). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec update_presence(kz_term:server_ref(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok'. -update_presence(FSM, PresenceId, PresenceState) -> - gen_fsm:send_all_state_event(FSM, {'update_presence', PresenceId, PresenceState}). +update_presence(ServerRef, PresenceId, PresenceState) -> + gen_statem:cast(ServerRef, {'update_presence', PresenceId, PresenceState}). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec agent_logout(kz_term:server_ref()) -> 'ok'. -agent_logout(FSM) -> - gen_fsm:send_all_state_event(FSM, {'agent_logout'}). +agent_logout(ServerRef) -> + gen_statem:cast(ServerRef, {'agent_logout'}). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec refresh(pid(), kz_json:object()) -> 'ok'. -refresh(FSM, AgentJObj) -> gen_fsm:send_all_state_event(FSM, {'refresh', AgentJObj}). +refresh(ServerRef, AgentJObj) -> gen_statem:cast(ServerRef, {'refresh', AgentJObj}). -spec current_call(pid()) -> kz_term:api_object(). -current_call(FSM) -> gen_fsm:sync_send_event(FSM, 'current_call'). +current_call(ServerRef) -> gen_statem:call(ServerRef, 'current_call'). -spec status(pid()) -> kz_term:proplist(). -status(FSM) -> gen_fsm:sync_send_event(FSM, 'status'). +status(ServerRef) -> gen_statem:call(ServerRef, 'status'). %%------------------------------------------------------------------------------ -%% @doc Creates a gen_fsm process which calls Module:init/1 to +%% @doc Creates a gen_ServerRef process which calls Module:init/1 to %% initialize. To ensure a synchronized start-up procedure, this %% function does not return until Module:init/1 has returned. %% @end %%------------------------------------------------------------------------------ - --spec start_link(pid(), kz_json:object()) -> kz_term:startlink_ret(). -start_link(Supervisor, AgentJObj) when is_pid(Supervisor) -> - pvt_start_link(kz_doc:account_id(AgentJObj) - ,kz_doc:id(AgentJObj) - ,Supervisor - ,[] - ,'false' - ). - --spec start_link(pid(), kapps_call:call(), kz_term:ne_binary()) -> kz_term:startlink_ret(). +-spec start_link(pid(), kapps_call:call(), kz_term:ne_binary()) -> kz_types:startlink_ret(). start_link(Supervisor, ThiefCall, _QueueId) -> pvt_start_link(kapps_call:account_id(ThiefCall) ,kapps_call:owner_id(ThiefCall) @@ -404,55 +387,47 @@ start_link(Supervisor, ThiefCall, _QueueId) -> ,'true' ). --spec start_link(kz_term:ne_binary(), kz_term:ne_binary(), pid(), kz_term:proplist()) -> kz_term:startlink_ret(). -start_link(AccountId, AgentId, Supervisor, Props) -> - pvt_start_link(AccountId, AgentId, Supervisor, Props, 'false'). +-spec start_link(pid(), kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object()) -> kz_types:startlink_ret(). +start_link(Supervisor, AccountId, AgentId, AgentJObj) -> + start_link(Supervisor, AccountId, AgentId, AgentJObj, []). --spec start_link(pid(), any(), kz_term:ne_binary(), kz_term:ne_binary(), any()) -> kz_term:startlink_ret(). -start_link(Supervisor, _AgentJObj, AccountId, AgentId, _Queues) -> +-spec start_link(pid(), kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object(), kz_term:ne_binaries()) -> kz_types:startlink_ret(). +start_link(Supervisor, AccountId, AgentId, _AgentJObj, _Queues) -> pvt_start_link(AccountId, AgentId, Supervisor, [], 'false'). -pvt_start_link('undefined', _AgentId, Supervisor, _, _) -> - lager:debug("agent ~s trying to start with no account id", [_AgentId]), - _ = kz_process:spawn(fun acdc_agent_sup:stop/1, [Supervisor]), - 'ignore'; -pvt_start_link(_AccountId, 'undefined', Supervisor, _, _) -> - lager:debug("undefined agent id trying to start in account ~s", [_AccountId]), - _ = kz_process:spawn(fun acdc_agent_sup:stop/1, [Supervisor]), - 'ignore'; pvt_start_link(AccountId, AgentId, Supervisor, Props, IsThief) -> - gen_fsm:start_link(?SERVER, [AccountId, AgentId, Supervisor, Props, IsThief], []). + gen_statem:start_link(?SERVER, [AccountId, AgentId, Supervisor, Props, IsThief], []). -spec new_endpoint(pid(), kz_json:object()) -> 'ok'. -new_endpoint(FSM, EP) -> - lager:debug("sending EP to ~p: ~p", [FSM, EP]). +new_endpoint(ServerRef, EP) -> + lager:debug("sending EP to ~p: ~p", [ServerRef, EP]). -spec edited_endpoint(pid(), kz_json:object()) -> 'ok'. -edited_endpoint(FSM, EP) -> - lager:debug("sending EP to ~p: ~p", [FSM, EP]), - gen_fsm:send_all_state_event(FSM, {'edited_endpoint', kz_doc:id(EP), EP}). +edited_endpoint(ServerRef, EP) -> + lager:debug("sending EP to ~p: ~p", [ServerRef, EP]), + gen_statem:cast(ServerRef, {'edited_endpoint', kz_doc:id(EP), EP}). -spec deleted_endpoint(pid(), kz_json:object()) -> 'ok'. -deleted_endpoint(FSM, EP) -> - lager:debug("sending EP to ~p: ~p", [FSM, EP]). +deleted_endpoint(ServerRef, EP) -> + lager:debug("sending EP to ~p: ~p", [ServerRef, EP]). %%%============================================================================= -%%% gen_fsm callbacks +%%% gen_ServerRef callbacks %%%============================================================================= %%------------------------------------------------------------------------------ %% @private -%% @doc Whenever a gen_fsm is started using gen_fsm:start/[3,4] or -%% gen_fsm:start_link/[3,4], this function is called by the new +%% @doc Whenever a gen_ServerRef is started using gen_ServerRef:start/[3,4] or +%% gen_ServerRef:start_link/[3,4], this function is called by the new %% process to initialize. %% %% @end %%------------------------------------------------------------------------------ -spec init(list()) -> {'ok', atom(), state()}. init([AccountId, AgentId, Supervisor, Props, IsThief]) -> - FSMCallId = <<"fsm_", AccountId/binary, "_", AgentId/binary>>, - kz_log:put_callid(FSMCallId), - lager:debug("started acdc agent fsm"), + ServerRefCallId = <<"fsm_", AccountId/binary, "_", AgentId/binary>>, + kz_log:put_callid(ServerRefCallId), + lager:debug("started acdc agent ServerRef"), _P = kz_process:spawn(fun wait_for_listener/4, [Supervisor, self(), Props, IsThief]), lager:debug("waiting for listener in ~p", [_P]), @@ -466,7 +441,7 @@ init([AccountId, AgentId, Supervisor, Props, IsThief]) -> ,account_db = AccountDb ,agent_id = AgentId ,agent_name = kz_json:get_value(<<"username">>, UserDoc) - ,fsm_call_id = FSMCallId + ,fsm_call_id = ServerRefCallId ,max_connect_failures = max_failures(AccountId) } }. @@ -485,12 +460,12 @@ max_failures(JObj) -> max_failures(kz_json:get_integer_value(?MAX_CONNECT_FAILURES, JObj, ?MAX_FAILURES)). -spec wait_for_listener(pid(), pid(), kz_term:proplist(), boolean()) -> 'ok'. -wait_for_listener(Supervisor, FSM, Props, IsThief) -> +wait_for_listener(Supervisor, ServerRef, Props, IsThief) -> case acdc_agent_sup:listener(Supervisor) of 'undefined' -> lager:debug("listener not ready yet, waiting"), timer:sleep(100), - wait_for_listener(Supervisor, FSM, Props, IsThief); + wait_for_listener(Supervisor, ServerRef, Props, IsThief); P when is_pid(P) -> lager:debug("listener retrieved: ~p", [P]), @@ -500,64 +475,59 @@ wait_for_listener(Supervisor, FSM, Props, IsThief) -> of 'true' -> {'ready', 'undefined'}; _ -> - gen_fsm:send_event(FSM, 'send_sync_event'), - gen_fsm:send_all_state_event(FSM, 'load_endpoints'), - {'sync', start_sync_timer(FSM)} + gen_statem:cast(ServerRef, 'send_sync_event'), + gen_statem:cast(ServerRef, 'load_endpoints'), + {'sync', start_sync_timer(ServerRef)} end, - gen_fsm:send_event(FSM, {'listener', P, NextState, SyncRef}) + gen_statem:cast(ServerRef, {'listener', P, NextState, SyncRef}) end. %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ --spec wait(any(), state()) -> kz_term:handle_fsm_ret(state()). -wait({'listener', AgentListener, NextState, SyncRef}, State) -> +-spec callback_mode() -> 'state_functions'. +callback_mode() -> + 'state_functions'. + +%%------------------------------------------------------------------------------ +%% @doc +%% @end +%%------------------------------------------------------------------------------ +-spec wait(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +wait('cast', {'listener', AgentListener, NextState, SyncRef}, State) -> lager:debug("setting agent proc to ~p", [AgentListener]), acdc_agent_listener:fsm_started(AgentListener, self()), {'next_state', NextState, State#state{agent_listener=AgentListener ,sync_ref=SyncRef ,agent_listener_id=acdc_util:proc_id() }}; -wait('send_sync_event', State) -> - gen_fsm:send_event(self(), 'send_sync_event'), +wait('cast', 'send_sync_event', State) -> + gen_statem:cast(self(), 'send_sync_event'), {'next_state', 'wait', State}; -wait(_Msg, State) -> - lager:debug("unhandled event in wait: ~p", [_Msg]), - {'next_state', 'wait', State}. - --spec wait(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -wait('status', _, State) -> - {'reply', [{'state', <<"wait">>}], 'wait', State}; -wait('current_call', _, State) -> - {'reply', 'undefined', 'wait', State}. +wait('cast', Evt, State) -> + handle_event(Evt, 'wait', State); +wait({'call', From}, 'status', State) -> + {'next_state', 'wait', State, {'reply', From, [{'state', <<"wait">>}]}}; +wait({'call', From}, 'current_call', State) -> + {'next_state', 'wait', State, {'reply', From, 'undefined'}}; +wait('info', Evt, State) -> + handle_info(Evt, 'wait', State). %%------------------------------------------------------------------------------ %% @private %% @doc %% @end %%------------------------------------------------------------------------------ --spec sync(any(), state()) -> kz_term:handle_fsm_ret(state()). -sync({'timeout', Ref, ?SYNC_RESPONSE_MESSAGE}, #state{sync_ref=Ref - ,agent_listener=AgentListener - }=State) when is_reference(Ref) -> - lager:debug("done waiting for sync responses"), - acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), - - apply_state_updates(State#state{sync_ref=Ref}); -sync({'timeout', Ref, ?RESYNC_RESPONSE_MESSAGE}, #state{sync_ref=Ref}=State) when is_reference(Ref) -> - lager:debug("resync timer expired, lets check with the others again"), - SyncRef = start_sync_timer(), - gen_fsm:send_event(self(), 'send_sync_event'), - {'next_state', 'sync', State#state{sync_ref=SyncRef}}; -sync('send_sync_event', #state{agent_listener=AgentListener +-spec sync(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +sync('cast', 'send_sync_event', #state{agent_listener=AgentListener ,agent_listener_id=_AProcId }=State) -> lager:debug("sending sync_req event to other agent processes: ~s", [_AProcId]), acdc_agent_listener:send_sync_req(AgentListener), {'next_state', 'sync', State}; -sync({'sync_req', JObj}, #state{agent_listener=AgentListener +sync('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener ,agent_listener_id=AProcId }=State) -> case kz_json:get_value(<<"Process-ID">>, JObj) of @@ -569,7 +539,7 @@ sync({'sync_req', JObj}, #state{agent_listener=AgentListener acdc_agent_listener:send_sync_resp(AgentListener, 'sync', JObj), {'next_state', 'sync', State} end; -sync({'sync_resp', JObj}, #state{sync_ref=Ref +sync('cast', {'sync_resp', JObj}, #state{sync_ref=Ref ,agent_listener=AgentListener }=State) -> case catch kz_term:to_atom(kz_json:get_value(<<"Status">>, JObj)) of @@ -591,37 +561,49 @@ sync({'sync_resp', JObj}, #state{sync_ref=Ref _ = erlang:cancel_timer(Ref), {'next_state', 'sync', State#state{sync_ref=start_resync_timer()}} end; -sync({'member_connect_req', _}, State) -> +sync('cast', {'member_connect_req', _}, State) -> lager:debug("member_connect_req recv, not ready"), {'next_state', 'sync', State}; - -sync(?NEW_CHANNEL_FROM(CallId, Number, Name, _), State) -> +sync('cast', Evt, State) -> + handle_event(Evt, 'sync', State); +sync({'call', From}, 'status', State) -> + {'next_state', 'sync', State, {'reply', From, [{'state', <<"sync">>}]}}; +sync({'call', From}, 'current_call', State) -> + {'next_state', 'sync', State, {'reply', From, 'undefined'}}; +sync('info', {'timeout', Ref, ?SYNC_RESPONSE_MESSAGE}, #state{sync_ref=Ref + ,agent_listener=AgentListener + }=State) when is_reference(Ref) -> + lager:debug("done waiting for sync responses"), + acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), + apply_state_updates(State#state{sync_ref=Ref}); +sync('info', {'timeout', Ref, ?RESYNC_RESPONSE_MESSAGE}, #state{sync_ref=Ref}=State) when is_reference(Ref) -> + lager:debug("resync timer expired, lets check with the others again"), + SyncRef = start_sync_timer(), + gen_statem:cast(self(), 'send_sync_event'), + {'next_state', 'sync', State#state{sync_ref=SyncRef}}; +sync('info', ?NEW_CHANNEL_FROM(CallId, Number, Name, _), State) -> lager:debug("sync call_from inbound: ~s", [CallId]), {'next_state', 'inbound', start_inbound_call_handling(CallId, Number, Name, State), 'hibernate'}; -sync(?NEW_CHANNEL_TO(CallId, Number, Name), State) -> +sync('info', ?NEW_CHANNEL_TO(CallId, Number, Name), State) -> lager:debug("sync call_to outbound: ~s", [CallId]), {'next_state', 'outbound', start_outbound_call_handling(CallId, Number, Name, State), 'hibernate'}; -sync(_Evt, State) -> - lager:debug("unhandled event while syncing: ~p", [_Evt]), - {'next_state', 'sync', State}. +sync('info', Evt, State) -> + lager:debug("unhandled event while syncing: ~p", [Evt]), + handle_info(Evt, 'sync', State). + --spec sync(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -sync('status', _, State) -> - {'reply', [{'state', <<"sync">>}], 'sync', State}; -sync('current_call', _, State) -> - {'reply', 'undefined', 'sync', State}. %%------------------------------------------------------------------------------ %% @private %% @doc %% @end %%------------------------------------------------------------------------------ --spec ready(any(), state()) -> kz_term:handle_fsm_ret(state()). -ready({'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> +-spec ready(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +ready('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Server-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'ready', JObj), {'next_state', 'ready', State}; -ready({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener +ready('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener ,endpoints=OrigEPs ,account_id=AccountId ,agent_id=AgentId @@ -641,7 +623,7 @@ ready({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentList lager:debug("trying to ring agent ~s to connect to caller in queue ~s", [AgentId, QueueId]), - case get_endpoints(OrigEPs, AgentListener, Call, AgentId, QueueId) of + case get_endpoints(OrigEPs, Call, AgentId, QueueId) of {'error', 'no_endpoints'} -> lager:info("agent ~s has no endpoints assigned; logging agent out", [AgentId]), acdc_agent_stats:agent_logged_out(AccountId, AgentId), @@ -680,7 +662,7 @@ ready({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentList ,queue_notifications=kz_json:get_value(<<"Notifications">>, JObj) }} end; -ready({'member_connect_win', JObj, 'different_node'}, #state{agent_listener=AgentListener +ready('cast', {'member_connect_win', JObj, 'different_node'}, #state{agent_listener=AgentListener ,endpoints=OrigEPs ,agent_id=AgentId ,connect_failures=CF @@ -697,7 +679,7 @@ ready({'member_connect_win', JObj, 'different_node'}, #state{agent_listener=Agen RecordingUrl = recording_url(JObj), %% Only start monitoring if the agent can actually take the call - case get_endpoints(OrigEPs, AgentListener, Call, AgentId, QueueId) of + case get_endpoints(OrigEPs, Call, AgentId, QueueId) of {'error', 'no_endpoints'} -> lager:info("agent ~s has no endpoints assigned; logging agent out", [AgentId]), {'next_state', 'paused', State}; @@ -725,11 +707,11 @@ ready({'member_connect_win', JObj, 'different_node'}, #state{agent_listener=Agen ,monitoring = 'true' }} end; -ready({'member_connect_satisfied', _, _Node}, State) -> +ready('cast', {'member_connect_satisfied', _, _Node}, State) -> lager:debug("unexpected connect_satisfied in state 'ready'"), {'next_state', 'ready', State}; -ready({'member_connect_req', _}, #state{max_connect_failures=Max +ready('cast', {'member_connect_req', _}, #state{max_connect_failures=Max ,connect_failures=Fails ,account_id=AccountId ,agent_id=AgentId @@ -738,13 +720,13 @@ ready({'member_connect_req', _}, #state{max_connect_failures=Max acdc_agent_stats:agent_logged_out(AccountId, AgentId), agent_logout(self()), {'next_state', 'paused', State}; -ready({'member_connect_req', JObj}, #state{agent_listener=AgentListener}=State) -> +ready('cast', {'member_connect_req', JObj}, #state{agent_listener=AgentListener}=State) -> acdc_agent_listener:member_connect_resp(AgentListener, JObj), {'next_state', 'ready', State}; -ready({'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> +ready('cast', {'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> acdc_agent_listener:originate_uuid(AgentListener, ACallId, ACtrlQ), {'next_state', 'ready', State}; -ready({'channel_answered', JObj}, #state{outbound_call_ids=OutboundCallIds}=State) -> +ready('cast', {'channel_answered', JObj}, #state{outbound_call_ids=OutboundCallIds}=State) -> CallId = call_id(JObj), case lists:member(CallId, OutboundCallIds) of 'true' -> @@ -754,7 +736,27 @@ ready({'channel_answered', JObj}, #state{outbound_call_ids=OutboundCallIds}=Stat lager:debug("unexpected answer of ~s while in ready", [CallId]), {'next_state', 'ready', State} end; -ready(?DESTROYED_CHANNEL(CallId, _Cause), #state{agent_listener=AgentListener +ready('cast', {'channel_unbridged', CallId}, #state{agent_listener=_AgentListener}=State) -> + lager:debug("channel unbridged: ~s", [CallId]), + {'next_state', 'ready', State}; +ready('cast',{'leg_destroyed', CallId}, #state{agent_listener=_AgentListener}=State) -> + lager:debug("channel unbridged: ~s", [CallId]), + {'next_state', 'ready', State}; +ready('cast', {'dtmf_pressed', _}, State) -> + {'next_state', 'ready', State}; +ready('cast', {'originate_failed', _E}, State) -> + {'next_state', 'ready', State}; +ready('cast', {'playback_stop', _CallId}, State) -> + {'next_state', 'ready', State}; +ready('cast', ?NEW_CHANNEL_FROM(CallId, Number, Name, 'undefined'), State) -> + lager:debug("ready call_from inbound: ~s", [CallId]), + {'next_state', 'inbound', start_inbound_call_handling(CallId, Number, Name, State), 'hibernate'}; +ready('cast', ?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), State) -> + cancel_if_failed_originate(CallId, MemberCallId, 'ready', State); +ready('cast', ?NEW_CHANNEL_TO(CallId, Number, Name), State) -> + lager:debug("ready call_to outbound: ~s", [CallId]), + {'next_state', 'outbound', start_outbound_call_handling(CallId, Number, Name, State), 'hibernate'}; +ready('cast', ?DESTROYED_CHANNEL(CallId, _Cause), #state{agent_listener=AgentListener ,outbound_call_ids=OutboundCallIds }=State) -> case lists:member(CallId, OutboundCallIds) of @@ -767,59 +769,39 @@ ready(?DESTROYED_CHANNEL(CallId, _Cause), #state{agent_listener=AgentListener acdc_agent_listener:channel_hungup(AgentListener, CallId), {'next_state', 'ready', State} end; -ready({'channel_unbridged', CallId}, #state{agent_listener=_AgentListener}=State) -> - lager:debug("channel unbridged: ~s", [CallId]), - {'next_state', 'ready', State}; -ready({'leg_destroyed', CallId}, #state{agent_listener=_AgentListener}=State) -> - lager:debug("channel unbridged: ~s", [CallId]), - {'next_state', 'ready', State}; -ready({'dtmf_pressed', _}, State) -> - {'next_state', 'ready', State}; -ready({'originate_failed', _E}, State) -> - {'next_state', 'ready', State}; -ready(?NEW_CHANNEL_FROM(CallId, Number, Name, 'undefined'), State) -> - lager:debug("ready call_from inbound: ~s", [CallId]), - {'next_state', 'inbound', start_inbound_call_handling(CallId, Number, Name, State), 'hibernate'}; -ready(?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), State) -> - cancel_if_failed_originate(CallId, MemberCallId, 'ready', State); -ready(?NEW_CHANNEL_TO(CallId,Number,Name), State) -> - lager:debug("ready call_to outbound: ~s", [CallId]), - {'next_state', 'outbound', start_outbound_call_handling(CallId, Number, Name, State), 'hibernate'}; -ready({'playback_stop', _JObj}, State) -> - {'next_state', 'ready', State}; -ready(_Evt, State) -> - lager:debug("unhandled event while ready: ~p", [_Evt]), - {'next_state', 'ready', State}. +ready('cast', Evt, State) -> + handle_event(Evt, 'ready', State); +ready({'call', From}, 'status', State) -> + {'next_state', 'ready', State, {'reply', From, [{'state', <<"ready">>}]}}; +ready({'call', From}, 'current_call', State) -> + {'next_state', 'ready', State, {'reply', From, 'undefined'}}; +ready('info', Evt, State) -> + handle_info(Evt, 'ready', State). --spec ready(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -ready('status', _, State) -> - {'reply', [{'state', <<"ready">>}], 'ready', State}; -ready('current_call', _, State) -> - {'reply', 'undefined', 'ready', State}. %%------------------------------------------------------------------------------ %% @private %% @doc %% @end %%------------------------------------------------------------------------------ --spec ringing(any(), state()) -> kz_term:handle_fsm_ret(state()). -ringing({'member_connect_req', _}, State) -> +-spec ringing(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +ringing('cast', {'member_connect_req', _}, State) -> {'next_state', 'ringing', State}; -ringing({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> +ringing('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (already ringing)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), {'next_state', 'ringing', State}; -ringing({'member_connect_win', _, 'different_node'}, State) -> +ringing('cast', {'member_connect_win', _, 'different_node'}, State) -> lager:debug("received member_connect_win for different node (ringing)"), {'next_state', 'ringing', State}; -ringing({'originate_ready', JObj}, #state{agent_listener=AgentListener}=State) -> +ringing('cast', {'originate_ready', JObj}, #state{agent_listener=AgentListener}=State) -> CallId = kz_json:get_value(<<"Call-ID">>, JObj), lager:debug("ringing agent's phone with call-id ~s", [CallId]), acdc_agent_listener:originate_execute(AgentListener, JObj), {'next_state', 'ringing', State}; -ringing({'member_connect_satisfied', JObj, Node}, #state{agent_listener=AgentListener +ringing('cast', {'member_connect_satisfied', JObj, Node}, #state{agent_listener=AgentListener ,member_call_id=MemberCallId ,account_id=AccountId ,member_call_queue_id=QueueId @@ -846,11 +828,11 @@ ringing({'member_connect_satisfied', JObj, Node}, #state{agent_listener=AgentLis end; _ -> {'next_state', 'ringing', State} end; -ringing({'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> +ringing('cast', {'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> lager:debug("recv originate_uuid for agent call ~s(~s)", [ACallId, ACtrlQ]), acdc_agent_listener:originate_uuid(AgentListener, ACallId, ACtrlQ), {'next_state', 'ringing', State}; -ringing({'originate_started', ACallId}, #state{agent_listener=AgentListener +ringing('cast', {'originate_started', ACallId}, #state{agent_listener=AgentListener ,member_call_id=MemberCallId ,member_call=MemberCall ,account_id=AccountId @@ -871,7 +853,7 @@ ringing({'originate_started', ACallId}, #state{agent_listener=AgentListener {'next_state', 'answered', State#state{agent_call_id=ACallId ,connect_failures=0 }}; -ringing({'originate_failed', E}, #state{agent_listener=AgentListener +ringing('cast', {'originate_failed', E}, #state{agent_listener=AgentListener ,account_id=AccountId ,agent_id=AgentId ,member_call_queue_id=QueueId @@ -891,7 +873,7 @@ ringing({'originate_failed', E}, #state{agent_listener=AgentListener acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), {'next_state', 'ringing', State}; -ringing({'agent_timeout', _JObj}, #state{agent_listener=AgentListener +ringing('cast', {'agent_timeout', _JObj}, #state{agent_listener=AgentListener ,account_id=AccountId ,agent_id=AgentId ,member_call_queue_id=QueueId @@ -911,9 +893,9 @@ ringing({'agent_timeout', _JObj}, #state{agent_listener=AgentListener acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), {'next_state', 'ringing', State}; -ringing({'playback_stop', _JObj}, State) -> +ringing('cast', {'playback_stop', _CallId}, State) -> {'next_state', 'ringing', State}; -ringing({'channel_bridged', MemberCallId}, #state{member_call_id=MemberCallId +ringing('cast', {'channel_bridged', MemberCallId}, #state{member_call_id=MemberCallId ,member_call=MemberCall ,agent_listener=AgentListener ,account_id=AccountId @@ -930,50 +912,9 @@ ringing({'channel_bridged', MemberCallId}, #state{member_call_id=MemberCallId acdc_agent_stats:agent_connected(AccountId, AgentId, MemberCallId, CIDName, CIDNumber), {'next_state', 'answered', State#state{connect_failures=0}}; -ringing({'channel_bridged', _CallId}, State) -> +ringing('cast', {'channel_bridged', _CallId}, State) -> {'next_state', 'ringing', State}; -ringing(?DESTROYED_CHANNEL(AgentCallId, _Cause), #state{agent_listener=AgentListener - ,agent_call_id=AgentCallId - ,connect_failures=Fails - ,max_connect_failures=MaxFails - }=State) -> - lager:debug("agent's channel (~s) down", [AgentCallId]), - - acdc_agent_listener:hangup_call(AgentListener), - - acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), - - NewFSMState = clear_call(State, 'failed'), - NextState = return_to_state(Fails+1, MaxFails), - case NextState of - 'paused' -> {'next_state', 'paused', NewFSMState}; - 'ready' -> apply_state_updates(NewFSMState) - end; -ringing(?DESTROYED_CHANNEL(MemberCallId, _Cause), #state{agent_listener=AgentListener - ,account_id=AccountId - ,member_call_id=MemberCallId - ,member_call_queue_id=QueueId - }=State) -> - lager:debug("caller's channel (~s) has gone down, stop agent's call: ~s", [MemberCallId, _Cause]), - acdc_agent_listener:channel_hungup(AgentListener, MemberCallId), - - _ = acdc_stats:call_abandoned(AccountId, QueueId, MemberCallId, ?ABANDON_HANGUP), - - acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), - apply_state_updates(clear_call(State, 'ready')); -ringing(?DESTROYED_CHANNEL(CallId, _Cause), #state{agent_listener=AgentListener - ,outbound_call_ids=OutboundCallIds - }=State) -> - case lists:member(CallId, OutboundCallIds) of - 'true' -> - lager:debug("agent outbound channel ~s down", [CallId]), - acdc_util:unbind_from_call_events(CallId, AgentListener), - {'next_state', 'ringing', State#state{outbound_call_ids=lists:delete(CallId, OutboundCallIds)}}; - 'false' -> - lager:debug("unexpected channel ~s down", [CallId]), - {'next_state', 'ringing', State} - end; -ringing({'channel_answered', JObj}, #state{member_call_id=MemberCallId +ringing('cast', {'channel_answered', JObj}, #state{member_call_id=MemberCallId ,agent_listener=AgentListener ,outbound_call_ids=OutboundCallIds }=State) -> @@ -992,11 +933,11 @@ ringing({'channel_answered', JObj}, #state{member_call_id=MemberCallId {'next_state', 'ringing', State#state{agent_call_id=OtherCallId}} end end; -ringing({'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> +ringing('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Process-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'ringing', JObj), {'next_state', 'ringing', State}; -ringing({'originate_resp', ACallId}, #state{agent_listener=AgentListener +ringing('cast', {'originate_resp', ACallId}, #state{agent_listener=AgentListener ,member_call_id=MemberCallId ,member_call=MemberCall ,account_id=AccountId @@ -1018,18 +959,18 @@ ringing({'originate_resp', ACallId}, #state{agent_listener=AgentListener acdc_agent_stats:agent_connected(AccountId, AgentId, MemberCallId, CIDName, CIDNumber), {'next_state', 'ringing', State}; -ringing({'shared_failure', _JObj}, #state{connect_failures=Fails +ringing('cast', {'shared_failure', _JObj}, #state{connect_failures=Fails ,max_connect_failures=MaxFails }=State) -> lager:debug("shared originate failure"), - NewFSMState = clear_call(State, 'failed'), + NewServerRefState = clear_call(State, 'failed'), NextState = return_to_state(Fails+1, MaxFails), case NextState of - 'paused' -> {'next_state', 'paused', NewFSMState}; - 'ready' -> apply_state_updates(NewFSMState) + 'paused' -> {'next_state', 'paused', NewServerRefState}; + 'ready' -> apply_state_updates(NewServerRefState) end; -ringing({'shared_call_id', JObj}, #state{agent_listener=AgentListener}=State) -> +ringing('cast', {'shared_call_id', JObj}, #state{agent_listener=AgentListener}=State) -> ACallId = kz_json:get_value(<<"Agent-Call-ID">>, JObj), lager:debug("shared call id ~s acquired, connecting to caller", [ACallId]), @@ -1040,58 +981,100 @@ ringing({'shared_call_id', JObj}, #state{agent_listener=AgentListener}=State) -> {'next_state', 'answered', State#state{agent_call_id=ACallId ,connect_failures=0 }}; -ringing(?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id=MemberCallId}=State) -> +ringing('info', ?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id=MemberCallId}=State) -> lager:debug("new channel ~s for agent", [CallId]), {'next_state', 'ringing', State}; -ringing(?NEW_CHANNEL_FROM(CallId, Number, Name, _), #state{agent_listener=AgentListener}=State) -> +ringing('info', ?NEW_CHANNEL_FROM(CallId, Number, Name,_), #state{agent_listener=AgentListener}=State) -> lager:debug("ringing call_from inbound: ~s", [CallId]), acdc_agent_listener:hangup_call(AgentListener), {'next_state', 'inbound', start_inbound_call_handling(CallId, Number, Name, clear_call(State, 'ready')), 'hibernate'}; -ringing(?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener +ringing('info', ?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener ,outbound_call_ids=OutboundCallIds }=State) -> lager:debug("ringing call_to outbound: ~s", [CallId]), acdc_util:bind_to_call_events(CallId, AgentListener), {'next_state', 'ringing', State#state{outbound_call_ids=[CallId | lists:delete(CallId, OutboundCallIds)]}}; -ringing({'leg_created', _, _}, State) -> +ringing('cast', {'leg_created', _, _}, State) -> {'next_state', 'ringing', State}; -ringing({'leg_destroyed', _CallId}, State) -> +ringing('cast', {'leg_destroyed', _CallId}, State) -> {'next_state', 'ringing', State}; -ringing({'usurp_control', _CallId}, State) -> +ringing('cast', {'usurp_control', _CallId}, State) -> {'next_state', 'ringing', State}; -ringing(_Evt, State) -> - lager:debug("unhandled event while ringing: ~p", [_Evt]), - {'next_state', 'ringing', State}. +ringing('cast', ?DESTROYED_CHANNEL(AgentCallId, _Cause), #state{agent_listener=AgentListener + ,agent_call_id=AgentCallId + ,connect_failures=Fails + ,max_connect_failures=MaxFails + }=State) -> + lager:debug("agent's channel (~s) down", [AgentCallId]), --spec ringing(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -ringing('status', _, #state{member_call_id=MemberCallId - ,agent_call_id=ACallId - }=State) -> - {'reply', [{'state', <<"ringing">>} - ,{'member_call_id', MemberCallId} - ,{'agent_call_id', ACallId} - ] - ,'ringing', State}; -ringing('current_call', _, #state{member_call=Call - ,member_call_queue_id=QueueId - }=State) -> - {'reply', current_call(Call, 'ringing', QueueId, 'undefined'), 'ringing', State}. + acdc_agent_listener:hangup_call(AgentListener), + + acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), + + NewServerRefState = clear_call(State, 'failed'), + NextState = return_to_state(Fails+1, MaxFails), + case NextState of + 'paused' -> {'next_state', 'paused', NewServerRefState}; + 'ready' -> apply_state_updates(NewServerRefState) + end; +ringing('cast', ?DESTROYED_CHANNEL(MemberCallId, _Cause), #state{agent_listener=AgentListener + ,account_id=AccountId + ,member_call_id=MemberCallId + ,member_call_queue_id=QueueId + }=State) -> + lager:debug("caller's channel (~s) has gone down, stop agent's call: ~s", [MemberCallId, _Cause]), + acdc_agent_listener:channel_hungup(AgentListener, MemberCallId), + + _ = acdc_stats:call_abandoned(AccountId, QueueId, MemberCallId, ?ABANDON_HANGUP), + + acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), + apply_state_updates(clear_call(State, 'ready')); +ringing('cast', ?DESTROYED_CHANNEL(CallId, _Cause), #state{agent_listener=AgentListener + ,outbound_call_ids=OutboundCallIds + }=State) -> + case lists:member(CallId, OutboundCallIds) of + 'true' -> + lager:debug("agent outbound channel ~s down", [CallId]), + acdc_util:unbind_from_call_events(CallId, AgentListener), + {'next_state', 'ringing', State#state{outbound_call_ids=lists:delete(CallId, OutboundCallIds)}}; + 'false' -> + lager:debug("unexpected channel ~s down", [CallId]), + {'next_state', 'ringing', State} + end; +ringing('cast', Evt, State) -> + handle_event(Evt, 'ringing', State); +ringing({'call', From}, 'status', #state{member_call_id=MemberCallId + ,agent_call_id=ACallId + }=State) -> + {'next_state', 'ringing', State + ,{'reply', From, [{'state', <<"ringing">>} + ,{'member_call_id', MemberCallId} + ,{'agent_call_id', ACallId} + ]}}; +ringing({'call', From}, 'current_call', #state{member_call=Call + ,member_call_queue_id=QueueId + }=State) -> + {'next_state', 'ringing', State + ,{'reply', From, current_call(Call, 'ringing', QueueId, 'undefined')} + }; +ringing('info', Evt, State) -> + handle_info(Evt, 'ringing', State). %%------------------------------------------------------------------------------ %% @private %% @doc %% @end %%------------------------------------------------------------------------------ --spec ringing_callback(any(), state()) -> kz_term:handle_fsm_ret(state()). -ringing_callback({'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> +-spec ringing_callback(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +ringing_callback('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Server-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'ringing_callback', JObj), {'next_state', 'ringing_callback', State}; -ringing_callback({'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> +ringing_callback('cast', {'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> lager:debug("recv originate_uuid for agent call ~s(~s)", [ACallId, ACtrlQ]), acdc_agent_listener:originate_uuid(AgentListener, ACallId, ACtrlQ), {'next_state', 'ringing_callback', State}; -ringing_callback({'originate_resp', ACallId}, #state{account_id=AccountId +ringing_callback('cast', {'originate_resp', ACallId}, #state{account_id=AccountId ,agent_id=AgentId ,agent_listener=AgentListener ,member_call_id=MemberCallId @@ -1112,12 +1095,11 @@ ringing_callback({'originate_resp', ACallId}, #state{account_id=AccountId {CIDNumber, CIDName} = acdc_util:caller_id(MemberCall), %% TODO: remove if unnecessary - % acdc_util:b_bind_to_call_events(ACallId, AgentListener), + %% acdc_util:b_bind_to_call_events(ACallId, AgentListener), acdc_agent_listener:member_callback_accepted(AgentListener, ACall), acdc_agent_stats:agent_connected(AccountId, AgentId, MemberCallId, CIDName, CIDNumber), - {'next_state', 'ringing_callback', State#state{connect_failures=0}}; -ringing_callback({'originate_failed', JObj}, #state{agent_listener=AgentListener +ringing_callback('cast', {'originate_failed', JObj}, #state{agent_listener=AgentListener ,account_id=AccountId ,agent_id=AgentId ,member_call_queue_id=QueueId @@ -1135,23 +1117,25 @@ ringing_callback({'originate_failed', JObj}, #state{agent_listener=AgentListener acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), {'next_state', 'ringing_callback', State}; -ringing_callback({'shared_failure', _JObj}, #state{connect_failures=Fails +ringing_callback('cast', {'shared_failure', _JObj}, #state{connect_failures=Fails ,max_connect_failures=MaxFails }=State) -> lager:debug("shared originate failure"), - NewFSMState = clear_call(State, 'failed'), + NewServerRefState = clear_call(State, 'failed'), NextState = return_to_state(Fails+1, MaxFails), case NextState of - 'paused' -> {'next_state', 'paused', NewFSMState}; - 'ready' -> apply_state_updates(NewFSMState) + 'paused' -> {'next_state', 'paused', NewServerRefState}; + 'ready' -> apply_state_updates(NewServerRefState) end; %% For the monitoring processes, fake the agent_callback_call so playback_stop isn't ignored -ringing_callback({'shared_call_id', JObj}, #state{agent_callback_call='undefined'}=State) -> +ringing_callback('cast', {'shared_call_id', JObj}, #state{agent_callback_call='undefined'}=State) -> ACallId = kz_json:get_value(<<"Agent-Call-ID">>, JObj), ACall = kapps_call:set_call_id(ACallId, kapps_call:new()), - ringing_callback({'shared_call_id', JObj}, State#state{agent_callback_call=ACall}); -ringing_callback({'shared_call_id', JObj}, #state{agent_listener=AgentListener}=State) -> + ringing_callback('cast', {'shared_call_id', JObj}, State#state{agent_callback_call=ACall}); +ringing_callback('cast', {'shared_call_id', JObj}, #state{agent_listener=AgentListener + ,outbound_call_ids=OutboundCallIds} + =State) -> ACallId = kz_json:get_value(<<"Agent-Call-ID">>, JObj), lager:debug("shared call id ~s acquired, connecting to caller", [ACallId]), @@ -1161,8 +1145,9 @@ ringing_callback({'shared_call_id', JObj}, #state{agent_listener=AgentListener}= {'next_state', 'ringing_callback', State#state{agent_call_id=ACallId ,connect_failures=0 + ,outbound_call_ids=[ACallId | lists:delete(ACallId, OutboundCallIds)] }}; -ringing_callback({'member_connect_satisfied', JObj, Node}, #state{agent_listener=AgentListener +ringing_callback('cast', {'member_connect_satisfied', JObj, Node}, #state{agent_listener=AgentListener ,member_call_id=MemberCallId ,account_id=AccountId ,member_call_queue_id=QueueId @@ -1189,25 +1174,54 @@ ringing_callback({'member_connect_satisfied', JObj, Node}, #state{agent_listener end; _ -> {'next_state', 'ringing_callback', State} end; -ringing_callback({'channel_answered', JObj}, State) -> +ringing_callback('cast', {'channel_answered', JObj}, State) -> CallId = call_id(JObj), lager:debug("agent answered phone on ~s", [CallId]), ACall = kapps_call:set_account_id(kz_json:get_value([<<"Custom-Channel-Vars">>, <<"Account-ID">>], JObj), kapps_call:from_json(JObj)), {'next_state', 'ringing_callback', State#state{agent_call_id=CallId ,agent_callback_call=ACall }}; -ringing_callback(?DESTROYED_CHANNEL(ACallId, _Cause), #state{agent_call_id=ACallId +ringing_callback('cast', {'playback_stop', _}, #state{agent_callback_call='undefined'}=State) -> + {'next_state', 'ringing_callback', State}; +ringing_callback('cast', {'playback_stop', ACallId}, #state{member_call=Call + ,agent_call_id=ACallId + ,member_call_id=MemberCallId + ,monitoring='true' + }=State) -> + {'next_state', 'awaiting_callback', State#state{member_original_call=Call + ,member_original_call_id=MemberCallId + }}; +ringing_callback('cast', {'playback_stop', ACallId}, #state{agent_listener=AgentListener + ,member_call=Call + ,member_call_id=MemberCallId + ,agent_callback_call=AgentCallbackCall + ,agent_call_id=ACallId + ,outbound_call_ids=OutboundCallIds + }=State) -> + NewMemberCallId = acdc_agent_listener:originate_callback_return(AgentListener, AgentCallbackCall), + kz_log:put_callid(NewMemberCallId), + acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_RED_SOLID), + + %% Preserve old call information for sake of stats + {'next_state', 'awaiting_callback', State#state{member_call_id=NewMemberCallId + ,member_original_call=Call + ,member_original_call_id=MemberCallId + ,outbound_call_ids=[NewMemberCallId | lists:delete(NewMemberCallId, OutboundCallIds)] + }}; +ringing_callback('cast', {'usurp_control', _}, State) -> + {'next_state', 'ringing_callback', State}; +ringing_callback('cast', ?DESTROYED_CHANNEL(ACallId, _Cause), #state{agent_call_id=ACallId ,connect_failures=Fails ,max_connect_failures=MaxFails ,monitoring='true' }=State) -> - NewFSMState = clear_call(State, 'failed'), + NewServerRefState = clear_call(State, 'failed'), NextState = return_to_state(Fails+1, MaxFails), case NextState of - 'paused' -> {'next_state', 'paused', NewFSMState}; - 'ready' -> apply_state_updates(NewFSMState) + 'paused' -> {'next_state', 'paused', NewServerRefState}; + 'ready' -> apply_state_updates(NewServerRefState) end; -ringing_callback(?DESTROYED_CHANNEL(ACallId, Cause), #state{account_id=AccountId +ringing_callback('cast', ?DESTROYED_CHANNEL(ACallId, Cause), #state{account_id=AccountId ,agent_id=AgentId ,agent_listener=AgentListener ,member_call_id=CallId @@ -1224,69 +1238,51 @@ ringing_callback(?DESTROYED_CHANNEL(ACallId, Cause), #state{account_id=AccountId acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), - NewFSMState = clear_call(State, 'failed'), + NewServerRefState = clear_call(State, 'failed'), NextState = return_to_state(Fails+1, MaxFails), case NextState of - 'paused' -> {'next_state', 'paused', NewFSMState}; - 'ready' -> apply_state_updates(NewFSMState) + 'paused' -> {'next_state', 'paused', NewServerRefState}; + 'ready' -> apply_state_updates(NewServerRefState) end; -ringing_callback(?NEW_CHANNEL_TO(CallId,_,_), State) -> - lager:debug("new channel ~s for agent", [CallId]), - {'next_state', 'ringing_callback', State}; -ringing_callback({'playback_stop', _}, #state{agent_callback_call='undefined'}=State) -> - {'next_state', 'ringing_callback', State}; -ringing_callback({'playback_stop', _JObj}, #state{member_call=Call - ,member_call_id=MemberCallId - ,monitoring='true' - }=State) -> - {'next_state', 'awaiting_callback', State#state{member_original_call=Call - ,member_original_call_id=MemberCallId - }}; -ringing_callback({'playback_stop', _JObj}, #state{agent_listener=AgentListener - ,member_call=Call - ,member_call_id=MemberCallId - ,agent_callback_call=AgentCallbackCall - }=State) -> - NewMemberCallId = acdc_agent_listener:originate_callback_return(AgentListener, AgentCallbackCall), - kz_log:put_callid(NewMemberCallId), - acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_RED_SOLID), - - %% Preserve old call information for sake of stats - {'next_state', 'awaiting_callback', State#state{member_call_id=NewMemberCallId - ,member_original_call=Call - ,member_original_call_id=MemberCallId - }}; -ringing_callback({'usurp_control', _}, State) -> +ringing_callback('cast', ?NEW_CHANNEL_FROM(CallId,Name,Number, MemberCallId), #state{member_call_id=MemberCallId + } + = State) -> + lager:debug("new inbound channel ~s to agent from ~s(~s)", [CallId, Number, Name]), + {'next_state', 'ringing_callback', State}; +ringing_callback('cast', ?NEW_CHANNEL_TO(CallId,Name,Number), State) -> + lager:debug("new outbound channel ~s from agent ~s(~s)", [CallId, Number, Name]), {'next_state', 'ringing_callback', State}; -ringing_callback(_Evt, State) -> - lager:debug("unhandled event: ~p", [_Evt]), - {'next_state', 'ringing_callback', State}. - --spec ringing_callback(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -ringing_callback('status', _, State) -> - {'reply', [{'state', <<"ringing_callback">>}] - ,'ringing', State}; -ringing_callback('current_call', _, #state{member_call=Call +ringing_callback('cast', Evt, State) -> + handle_event(Evt, 'ringing_callback', State); +ringing_callback({'call', From}, 'status', State) -> + {'next_state', 'ringing_callback', State + ,{'reply', From, [{'state', <<"ringing_callback">>} + ]}}; +ringing_callback({'call', From}, 'current_call', #state{member_call=Call ,member_call_queue_id=QueueId }=State) -> - {'reply', current_call(Call, 'ringing_callback', QueueId, 'undefined'), 'ringing_callback', State}. + {'next_state', 'ringing_callback', State + ,{'reply', From, current_call(Call, 'ringing_callback', QueueId, 'undefined')} + }; +ringing_callback('info', Evt, State) -> + handle_info(Evt, 'ringing_callback', State). %%------------------------------------------------------------------------------ %% @private %% @doc %% @end %%------------------------------------------------------------------------------ --spec awaiting_callback(any(), state()) -> kz_term:handle_fsm_ret(state()). -awaiting_callback({'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> +-spec awaiting_callback(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +awaiting_callback('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Server-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'awaiting_callback', JObj), {'next_state', 'awaiting_callback', State}; -awaiting_callback({'originate_uuid', MemberCallbackCallId, CtrlQ}, #state{member_callback_candidates=Candidates}=State) -> +awaiting_callback('cast', {'originate_uuid', MemberCallbackCallId, CtrlQ}, #state{member_callback_candidates=Candidates}=State) -> lager:debug("recv originate_uuid for member callback call ~s(~s)", [MemberCallbackCallId, CtrlQ]), {'next_state', 'awaiting_callback', State#state{member_callback_candidates=props:set_value(MemberCallbackCallId, CtrlQ, Candidates)}}; -awaiting_callback({'originate_resp', _}, State) -> +awaiting_callback('cast', {'originate_resp', _}, State) -> {'next_state', 'awaiting_callback', State}; -awaiting_callback({'originate_failed', JObj}, #state{account_id=AccountId +awaiting_callback('cast', {'originate_failed', JObj}, #state{account_id=AccountId ,agent_id=AgentId ,agent_listener=AgentListener ,member_call=MemberCall @@ -1303,15 +1299,15 @@ awaiting_callback({'originate_failed', JObj}, #state{account_id=AccountId acdc_agent_listener:member_connect_accepted(AgentListener, ACallId, MemberCall), _ = acdc_stats:call_handled(AccountId, QueueId, OriginalMemberCallId, AgentId), acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), - {'next_state', 'awaiting_callback', State#state{wrapup_ref=hangup_call(State, 'member')}}; -awaiting_callback({'shared_failure', _}, #state{agent_listener=AgentListener + {'next_state', 'wrapup', State#state{wrapup_ref=hangup_call(State, 'member')}}; +awaiting_callback('cast', {'shared_failure', _}, #state{agent_listener=AgentListener ,agent_call_id=ACallId }=State) -> lager:debug("shared originate failure"), acdc_agent_listener:channel_hungup(AgentListener, ACallId), {'next_state', 'wrapup', State#state{wrapup_ref=hangup_call(State, 'member')}}; -awaiting_callback({'shared_call_id', JObj}, #state{agent_listener=AgentListener +awaiting_callback('cast', {'shared_call_id', JObj}, #state{agent_listener=AgentListener ,member_call=MemberCall ,member_callback_candidates=Candidates ,monitoring='true' @@ -1327,9 +1323,9 @@ awaiting_callback({'shared_call_id', JObj}, #state{agent_listener=AgentListener ,member_callback_candidates=props:set_value(NewMemberCallId, NewMemberCall, Candidates) ,connect_failures=0 }}; -awaiting_callback({'shared_call_id', _}, State) -> +awaiting_callback('cast', {'shared_call_id', _}, State) -> {'next_state', 'answered', State}; -awaiting_callback({'channel_answered', JObj}=Evt, #state{account_id=AccountId +awaiting_callback('cast', {'channel_answered', JObj}=Evt, #state{account_id=AccountId ,agent_id=AgentId ,agent_listener=AgentListener ,member_callback_candidates=Candidates @@ -1370,18 +1366,37 @@ awaiting_callback({'channel_answered', JObj}=Evt, #state{account_id=AccountId ,connect_failures=0 }} end; -awaiting_callback(?DESTROYED_CHANNEL(OriginalMemberCallId, _Cause), #state{member_original_call_id=OriginalMemberCallId +awaiting_callback('cast', {'leg_created', CallId, OtherLegCallId}=Evt, #state{agent_listener=AgentListener + ,member_callback_candidates=Candidates + }=State) -> + case props:get_value(CallId, Candidates) of + 'undefined' -> awaiting_callback_unhandled_event(Evt, State); + CtrlQ -> + %% Unbind from originate UUID, bind to bridge of loopback + lager:debug("rebinding from ~s to ~s due to loopback", [CallId, OtherLegCallId]), + acdc_agent_listener:rebind_events(AgentListener, CallId, OtherLegCallId), + + Candidates1 = props:set_value(OtherLegCallId, CtrlQ, []), + {'next_state', 'awaiting_callback', State#state{member_callback_candidates=Candidates1}} + end; +awaiting_callback('cast', {'leg_destroyed', _}, State) -> + {'next_state', 'awaiting_callback', State}; +awaiting_callback('cast', {'playback_stop', _JObj}, State) -> + {'next_state', 'awaiting_callback', State}; +awaiting_callback('cast', {'usurp_control', _}, State) -> + {'next_state', 'awaiting_callback', State}; +awaiting_callback('cast', ?DESTROYED_CHANNEL(OriginalMemberCallId, _Cause), #state{member_original_call_id=OriginalMemberCallId ,monitoring='true' }=State) -> {'next_state', 'awaiting_callback', State}; -awaiting_callback(?DESTROYED_CHANNEL(ACallId, _Cause), #state{agent_listener=AgentListener +awaiting_callback('cast', ?DESTROYED_CHANNEL(ACallId, _Cause), #state{agent_listener=AgentListener ,agent_call_id=ACallId ,monitoring='true' }=State) -> lager:debug("agent hungup ~s while waiting for a callback to connect", [ACallId]), acdc_agent_listener:channel_hungup(AgentListener, ACallId), {'next_state', 'wrapup', State#state{wrapup_ref=hangup_call(State, 'member')}}; -awaiting_callback(?DESTROYED_CHANNEL(ACallId, _Cause), #state{account_id=AccountId +awaiting_callback('cast', ?DESTROYED_CHANNEL(ACallId, _Cause), #state{account_id=AccountId ,agent_id=AgentId ,agent_listener=AgentListener ,member_call=MemberCall @@ -1395,43 +1410,31 @@ awaiting_callback(?DESTROYED_CHANNEL(ACallId, _Cause), #state{account_id=Account _ = acdc_stats:call_handled(AccountId, QueueId, OriginalMemberCallId, AgentId), acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), {'next_state', 'wrapup', State#state{wrapup_ref=hangup_call(State, 'member')}}; -awaiting_callback(?DESTROYED_CHANNEL(CallId, Cause), State) -> +awaiting_callback('cast', ?DESTROYED_CHANNEL(CallId, Cause), State) -> maybe_member_no_answer(CallId, Cause, State); -awaiting_callback({'leg_created', CallId, OtherLegCallId}=Evt, #state{agent_listener=AgentListener - ,member_callback_candidates=Candidates - }=State) -> - case props:get_value(CallId, Candidates) of - 'undefined' -> awaiting_callback_unhandled_event(Evt, State); - CtrlQ -> - %% Unbind from originate UUID, bind to bridge of loopback - lager:debug("rebinding from ~s to ~s due to loopback", [CallId, OtherLegCallId]), - acdc_agent_listener:rebind_events(AgentListener, CallId, OtherLegCallId), - - Candidates1 = props:set_value(OtherLegCallId, CtrlQ, []), - {'next_state', 'awaiting_callback', State#state{member_callback_candidates=Candidates1}} - end; -awaiting_callback({'leg_destroyed', _}, State) -> - {'next_state', 'awaiting_callback', State}; -awaiting_callback({'playback_stop', _JObj}, State) -> - {'next_state', 'awaiting_callback', State}; -awaiting_callback({'usurp_control', _}, State) -> - {'next_state', 'awaiting_callback', State}; -awaiting_callback(Evt, State) -> - awaiting_callback_unhandled_event(Evt, State). - --spec awaiting_callback(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -awaiting_callback('status', _, #state{member_call_id=MemberCallId +awaiting_callback('cast', ?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener + ,outbound_call_ids=OutboundCallIds + }=State) -> + lager:debug("answered call_to outbound: ~s", [CallId]), + acdc_util:bind_to_call_events(CallId, AgentListener), + {'next_state', 'awaiting_callback', State#state{outbound_call_ids=[CallId | lists:delete(CallId, OutboundCallIds)]}}; +awaiting_callback('cast', Evt, State) -> + handle_event(Evt, 'awaiting_callback', State); +awaiting_callback({'call', From}, 'status', #state{member_call_id=MemberCallId ,agent_call_id=ACallId }=State) -> - {'reply', [{'state', <<"awaiting_callback">>} + {'next_state', 'awaiting_callback', State + ,{'reply', From, [{'state', <<"awaiting_callback">>} ,{'member_call_id', MemberCallId} ,{'agent_call_id', ACallId} - ] - ,'ringing', State}; -awaiting_callback('current_call', _, #state{member_call=Call + ]}}; +awaiting_callback({'call', From}, 'current_call', #state{member_call=Call ,member_call_queue_id=QueueId }=State) -> - {'reply', current_call(Call, 'awaiting_callback', QueueId, 'undefined'), 'awaiting_callback', State}. + {'next_state', 'awaiting_callback', State + ,{'reply', From, current_call(Call, 'awaiting_callback', QueueId, 'undefined')}}; +awaiting_callback('info', Evt, State) -> + handle_info(Evt, 'awaiting_callback', State). -spec awaiting_callback_unhandled_event(any(), state()) -> {'next_state', 'awaiting_callback', state()}. @@ -1467,20 +1470,20 @@ maybe_member_no_answer(CallId, Cause, #state{account_id=AccountId %% @doc %% @end %%------------------------------------------------------------------------------ --spec answered(any(), state()) -> kz_term:handle_fsm_ret(state()). -answered({'member_connect_req', _}, State) -> +-spec answered(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +answered('cast', {'member_connect_req', _}, State) -> {'next_state', 'answered', State}; -answered({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> +answered('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (on the phone with someone)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), {'next_state', 'answered', State}; -answered({'member_connect_win', _, 'different_node'}, State) -> +answered('cast', {'member_connect_win', _, 'different_node'}, State) -> lager:debug("received member_connect_win for different node (answered)"), {'next_state', 'answered', State}; -answered({'member_connect_satisfied', _, Node}, State) -> +answered('cast', {'member_connect_satisfied', _, Node}, State) -> lager:debug("received member_connect_satisfied for ~p (answered)", [Node]), {'next_state', 'answered', State}; -answered({'dialplan_error', _App}, #state{agent_listener=AgentListener +answered('cast', {'dialplan_error', _App}, #state{agent_listener=AgentListener ,account_id=AccountId ,agent_id=AgentId ,member_call_queue_id=QueueId @@ -1495,70 +1498,21 @@ answered({'dialplan_error', _App}, #state{agent_listener=AgentListener acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), apply_state_updates(clear_call(State, 'ready')); -answered({'playback_stop', _JObj}, State) -> +answered('cast', {'playback_stop', _JObj}, State) -> {'next_state', 'answered', State}; -answered(?DESTROYED_CHANNEL(CallId, Cause), #state{member_call_id=CallId - ,outbound_call_ids=[] - }=State) -> - lager:debug("caller's channel hung up: ~s", [Cause]), - {'next_state', 'wrapup', State#state{wrapup_ref=hangup_call(State, 'member')}}; -answered(?DESTROYED_CHANNEL(CallId, _Cause), #state{account_id=AccountId - ,agent_id=AgentId - ,agent_listener=AgentListener - ,member_call_id=CallId - ,member_call_queue_id=QueueId - ,queue_notifications=Ns - ,outbound_call_ids=[OutboundCallId|_] - }=State) -> - lager:debug("caller's channel hung up, but there are still some outbounds"), - _ = acdc_stats:call_processed(AccountId, QueueId, AgentId, original_call_id(State), 'member'), - acdc_agent_listener:channel_hungup(AgentListener, CallId), - maybe_notify(Ns, ?NOTIFY_HANGUP, State), - {'next_state', 'outbound', start_outbound_call_handling(OutboundCallId, clear_call(State, 'ready')), 'hibernate'}; -answered(?DESTROYED_CHANNEL(CallId, Cause), #state{agent_call_id=CallId - ,outbound_call_ids=[] - }=State) -> - lager:debug("agent's channel has hung up: ~s", [Cause]), - {'next_state', 'wrapup', State#state{wrapup_ref=hangup_call(State, 'agent')}}; -answered(?DESTROYED_CHANNEL(CallId, _Cause), #state{account_id=AccountId - ,agent_id=AgentId - ,agent_listener=AgentListener - ,member_call_id=MemberCallId - ,member_call_queue_id=QueueId - ,queue_notifications=Ns - ,agent_call_id=CallId - ,outbound_call_ids=[OutboundCallId|_] - }=State) -> - lager:debug("agent's channel hung up, but there are still some outbounds"), - _ = acdc_stats:call_processed(AccountId, QueueId, AgentId, original_call_id(State), 'agent'), - acdc_agent_listener:channel_hungup(AgentListener, MemberCallId), - maybe_notify(Ns, ?NOTIFY_HANGUP, State), - {'next_state', 'outbound', start_outbound_call_handling(OutboundCallId, clear_call(State, 'ready')), 'hibernate'}; -answered(?DESTROYED_CHANNEL(CallId, _Cause), #state{agent_listener=AgentListener - ,outbound_call_ids=OutboundCallIds - }=State) -> - case lists:member(CallId, OutboundCallIds) of - 'true' -> - lager:debug("agent outbound channel ~s down", [CallId]), - acdc_util:unbind_from_call_events(CallId, AgentListener), - {'next_state', 'answered', State#state{outbound_call_ids=lists:delete(CallId, OutboundCallIds)}}; - 'false' -> - lager:debug("unexpected channel ~s down", [CallId]), - {'next_state', 'answered', State} - end; -answered({'sync_req', JObj}, #state{agent_listener=AgentListener +answered('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener ,member_call_id=CallId }=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Process-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'answered', JObj, [{<<"Call-ID">>, CallId}]), {'next_state', 'answered', State}; -answered({'channel_unbridged', CallId}, #state{member_call_id=CallId}=State) -> +answered('cast', {'channel_unbridged', CallId}, #state{member_call_id=CallId}=State) -> lager:info("caller channel ~s unbridged", [CallId]), {'next_state', 'answered', State}; -answered({'channel_unbridged', CallId}, #state{agent_call_id=CallId}=State) -> +answered('cast', {'channel_unbridged', CallId}, #state{agent_call_id=CallId}=State) -> lager:info("agent channel unbridged"), {'next_state', 'answered', State}; -answered({'channel_answered', JObj}=Evt, #state{agent_call_id=AgentCallId +answered('cast', {'channel_answered', JObj}=Evt, #state{agent_call_id=AgentCallId ,member_call_id=MemberCallId ,outbound_call_ids=OutboundCallIds }=State) -> @@ -1579,11 +1533,11 @@ answered({'channel_answered', JObj}=Evt, #state{agent_call_id=AgentCallId {'next_state', 'answered', State} end end; -answered({'channel_bridged', _}, State) -> +answered('cast', {'channel_bridged', _}, State) -> {'next_state', 'answered', State}; -answered({'channel_unbridged', _}, State) -> +answered('cast', {'channel_unbridged', _}, State) -> {'next_state', 'answered', State}; -answered({'channel_transferee', Transferor, Transferee}, #state{account_id=AccountId +answered('cast', {'channel_transferee', Transferor, Transferee}, #state{account_id=AccountId ,agent_id=AgentId ,member_call_id=Transferor ,member_call_queue_id=QueueId @@ -1594,368 +1548,425 @@ answered({'channel_transferee', Transferor, Transferee}, #state{account_id=Accou _ = acdc_stats:call_processed(AccountId, QueueId, AgentId, Transferor, 'member'), maybe_notify(Ns, ?NOTIFY_HANGUP, State), {'next_state', 'outbound', start_outbound_call_handling(Transferee, clear_call(State, 'ready'))}; -answered({'channel_transferee', _, _}, State) -> +answered('cast', {'channel_transferee', _, _}, State) -> + {'next_state', 'answered', State}; +answered('cast', {'channel_replaced', _}, State) -> + {'next_state', 'answered', State}; +answered('cast', {'originate_started', _CallId}, State) -> {'next_state', 'answered', State}; -answered({'channel_replaced', _}, State) -> +answered('cast', {'leg_created', _, _}, State) -> {'next_state', 'answered', State}; -answered({'originate_started', _CallId}, State) -> +answered('cast', {'usurp_control', _CallId}, State) -> {'next_state', 'answered', State}; -answered(?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id=MemberCallId}=State) -> +answered('cast', ?DESTROYED_CHANNEL(CallId, Cause), #state{member_call_id=CallId + ,outbound_call_ids=[] + }=State) -> + lager:debug("caller's channel hung up: ~s", [Cause]), + {'next_state', 'wrapup', State#state{wrapup_ref=hangup_call(State, 'member')}}; +answered('cast', ?DESTROYED_CHANNEL(CallId, _Cause), #state{account_id=AccountId + ,agent_id=AgentId + ,agent_listener=AgentListener + ,member_call_id=CallId + ,member_call_queue_id=QueueId + ,queue_notifications=Ns + ,outbound_call_ids=[OutboundCallId|_] + }=State) -> + lager:debug("caller's channel hung up, but there are still some outbounds"), + _ = acdc_stats:call_processed(AccountId, QueueId, AgentId, original_call_id(State), 'member'), + acdc_agent_listener:channel_hungup(AgentListener, CallId), + maybe_notify(Ns, ?NOTIFY_HANGUP, State), +%% {'next_state', 'outbound', start_outbound_call_handling(OutboundCallId, clear_call(State, 'ready')), 'hibernate'}; + {'next_state', 'outbound', start_outbound_call_handling(OutboundCallId, State), 'hibernate'}; + +answered('cast', ?DESTROYED_CHANNEL(CallId, Cause), #state{agent_call_id=CallId + ,outbound_call_ids=[] + }=State) -> + lager:debug("agent's channel has hung up: ~s", [Cause]), + {'next_state', 'wrapup', State#state{wrapup_ref=hangup_call(State, 'agent')}}; +answered('cast', ?DESTROYED_CHANNEL(CallId, _Cause), #state{account_id=AccountId + ,agent_id=AgentId + ,agent_listener=AgentListener + ,member_call_id=MemberCallId + ,member_call_queue_id=QueueId + ,queue_notifications=Ns + ,agent_call_id=CallId + ,outbound_call_ids=[OutboundCallId|_] + }=State) -> + lager:debug("agent's channel hung up, but there are still some outbounds"), + _ = acdc_stats:call_processed(AccountId, QueueId, AgentId, original_call_id(State), 'agent'), + acdc_agent_listener:channel_hungup(AgentListener, MemberCallId), + maybe_notify(Ns, ?NOTIFY_HANGUP, State), +%% {'next_state', 'outbound', start_outbound_call_handling(OutboundCallId, clear_call(State, 'ready')), 'hibernate'}; + {'next_state', 'outbound', start_outbound_call_handling(OutboundCallId, State), 'hibernate'}; +%% {'next_state', 'answered', State, 'hibernate'}; + +answered('cast', ?DESTROYED_CHANNEL(CallId, _Cause), #state{agent_listener=AgentListener + ,outbound_call_ids=OutboundCallIds + }=State) -> + case lists:member(CallId, OutboundCallIds) of + 'true' -> + lager:debug("agent outbound channel ~s down", [CallId]), + acdc_util:unbind_from_call_events(CallId, AgentListener), + {'next_state', 'answered', State#state{outbound_call_ids=lists:delete(CallId, OutboundCallIds)}}; + % {'next_state', 'answered', State}; + 'false' -> + lager:debug("unexpected channel ~s down", [CallId]), + {'next_state', 'answered', State} + end; +answered('cast', ?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id=MemberCallId}=State) -> lager:debug("new channel ~s for agent", [CallId]), {'next_state', 'answered', State}; -answered(?NEW_CHANNEL_FROM(CallId,_,_, _), #state{agent_listener=AgentListener +answered('cast', ?NEW_CHANNEL_FROM(CallId,_,_,_), #state{agent_listener=AgentListener ,outbound_call_ids=OutboundCallIds }=State) -> lager:debug("answered call_from inbound: ~s", [CallId]), acdc_util:bind_to_call_events(CallId, AgentListener), {'next_state', 'answered', State#state{outbound_call_ids=[CallId | lists:delete(CallId, OutboundCallIds)]}}; -answered(?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener +answered('cast', ?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener ,outbound_call_ids=OutboundCallIds }=State) -> lager:debug("answered call_to outbound: ~s", [CallId]), acdc_util:bind_to_call_events(CallId, AgentListener), {'next_state', 'answered', State#state{outbound_call_ids=[CallId | lists:delete(CallId, OutboundCallIds)]}}; -answered({'leg_created', _, _}, State) -> - {'next_state', 'answered', State}; -answered({'usurp_control', _CallId}, State) -> - {'next_state', 'answered', State}; -answered(_Evt, State) -> - lager:debug("unhandled event while answered: ~p", [_Evt]), - {'next_state', 'answered', State}. +answered('cast', Evt, State) -> + handle_event(Evt, 'answered', State); --spec answered(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -answered('status', _, #state{member_call_id=MemberCallId +answered({'call', From}, 'status', #state{member_call_id=MemberCallId ,agent_call_id=ACallId }=State) -> - {'reply', [{'state', <<"answered">>} + {'next_state', 'answered', State + ,{'reply', From, [{'state', <<"answered">>} ,{'member_call_id', MemberCallId} ,{'agent_call_id', ACallId} - ] - ,'answered', State}; -answered('current_call', _, #state{member_call=Call + ]}}; +answered({'call', From}, 'current_call', #state{member_call=Call ,member_call_start=Start ,member_call_queue_id=QueueId }=State) -> - {'reply', current_call(Call, 'answered', QueueId, Start), 'answered', State}. + {'next_state', 'answered', State + ,{'reply', From, current_call(Call, 'answered', QueueId, Start)}}; +answered('info', Evt, State) -> + handle_info(Evt, 'answered', State). + %%------------------------------------------------------------------------------ %% @private %% @doc %% @end %%------------------------------------------------------------------------------ --spec wrapup(any(), state()) -> kz_term:handle_fsm_ret(state()). -wrapup({'member_connect_req', _}, State) -> +-spec wrapup(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +wrapup('cast', {'member_connect_req', _}, State) -> {'next_state', 'wrapup', State#state{wrapup_timeout=0}}; -wrapup({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> +wrapup('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (in wrapup)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), {'next_state', 'wrapup', State#state{wrapup_timeout=0}}; -wrapup({'member_connect_win', _, 'different_node'}, State) -> +wrapup('cast', {'member_connect_win', _, 'different_node'}, State) -> lager:debug("received member_connect_win for different node (wrapup)"), {'next_state', 'wrapup', State#state{wrapup_timeout=0}}; -wrapup({'member_connect_satisfied', _, Node}, State) -> +wrapup('cast', {'member_connect_satisfied', _, Node}, State) -> lager:info("unexpected connect_satisfied for ~p", [Node]), {'next_state', 'wrapup', State}; -wrapup({'timeout', Ref, ?WRAPUP_FINISHED}, #state{wrapup_ref=Ref - ,agent_listener=AgentListener - }=State) -> - lager:debug("wrapup timer expired, ready for action!"), - acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), - apply_state_updates(clear_call(State, 'ready')); -wrapup({'sync_req', JObj}, #state{agent_listener=AgentListener +wrapup('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener ,wrapup_ref=Ref }=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Process-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'wrapup', JObj, [{<<"Time-Left">>, time_left(Ref)}]), {'next_state', 'wrapup', State}; -wrapup({'channel_bridged', _}, State) -> +wrapup('cast',{'channel_bridged', _}, State) -> {'next_state', 'wrapup', State}; -wrapup({'channel_unbridged', _}, State) -> +wrapup('cast',{'channel_unbridged', _}, State) -> {'next_state', 'wrapup', State}; -wrapup({'channel_transferee', _, _}, State) -> +wrapup('cast',{'channel_transferee', _, _}, State) -> {'next_state', 'wrapup', State}; -wrapup(?DESTROYED_CHANNEL(_, _), State) -> - {'next_state', 'wrapup', State}; -wrapup({'leg_destroyed', CallId}, #state{agent_listener=AgentListener}=State) -> +wrapup('cast',{'leg_destroyed', CallId}, #state{agent_listener=AgentListener}=State) -> lager:debug("leg ~s destroyed", [CallId]), acdc_agent_listener:channel_hungup(AgentListener, CallId), {'next_state', 'wrapup', State}; -wrapup(?NEW_CHANNEL_FROM(CallId, Number, Name, _), State) -> - lager:debug("wrapup call_from inbound: ~s", [CallId]), - {'next_state', 'inbound', start_inbound_call_handling(CallId, Number, Name, State), 'hibernate'}; -wrapup(?NEW_CHANNEL_TO(CallId,Number,Name), State) -> - lager:debug("wrapup call_to outbound: ~s", [CallId]), - {'next_state', 'outbound', start_outbound_call_handling(CallId, Number, Name, State), 'hibernate'}; -wrapup({'playback_stop', _}, State) -> +wrapup('cast',{'playback_stop', _}, State) -> {'next_state', 'wrapup', State}; -wrapup({'originate_resp', _}, State) -> +wrapup('cast',{'originate_resp', _}, State) -> {'next_state', 'wrapup', State}; -wrapup(_Evt, State) -> - lager:debug("unhandled event while in wrapup: ~p", [_Evt]), - {'next_state', 'wrapup', State#state{wrapup_timeout=0}}. - --spec wrapup(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -wrapup('status', _, #state{wrapup_ref=Ref}=State) -> - {'reply', [{'state', <<"wrapup">>} +wrapup('cast', ?DESTROYED_CHANNEL(_, _), State) -> + {'next_state', 'wrapup', State}; +wrapup('cast', Evt, State) -> + handle_event(Evt, 'wrapup', State); +wrapup({call, From}, 'status', #state{wrapup_ref=Ref}=State) -> + {'next_state', 'wrapup', State + ,{'reply', From, [{'state', <<"wrapup">>} ,{'wrapup_left', time_left(Ref)} - ] - ,'wrapup', State}; -wrapup('current_call', _, #state{member_call=Call + ]}}; +wrapup({call, From}, 'current_call', #state{member_call=Call ,member_call_start=Start ,member_call_queue_id=QueueId }=State) -> - {'reply', current_call(Call, 'wrapup', QueueId, Start), 'wrapup', State}. + {'next_state', 'wrapup', State + ,{'reply', From, current_call(Call, 'wrapup', QueueId, Start)}}; +wrapup('info', {'timeout', Ref, ?WRAPUP_FINISHED}, #state{wrapup_ref=Ref + ,agent_listener=AgentListener + }=State) -> + lager:debug("wrapup timer expired, ready for action!"), + acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), + apply_state_updates(clear_call(State, 'ready')); +wrapup('info', ?NEW_CHANNEL_FROM(CallId, Number, Name,_), State) -> + lager:debug("wrapup call_from inbound: ~s", [CallId]), + {'next_state', 'inbound', start_inbound_call_handling(CallId, Number, Name, State), 'hibernate'}; +wrapup('info', ?NEW_CHANNEL_TO(CallId, Number, Name), State) -> + lager:debug("wrapup call_to outbound: ~s", [CallId]), + {'next_state', 'outbound', start_outbound_call_handling(CallId, Number, Name, State), 'hibernate'}; +wrapup('info', Evt, State) -> + handle_info(Evt, 'wrapup', State#state{wrapup_timeout=0}). %%------------------------------------------------------------------------------ %% @private %% @doc %% @end %%------------------------------------------------------------------------------ --spec paused(any(), state()) -> kz_term:handle_fsm_ret(state()). -paused({'timeout', Ref, ?PAUSE_MESSAGE}, #state{pause_ref=Ref - ,agent_listener=AgentListener - }=State) when is_reference(Ref) -> - lager:debug("pause timer expired, putting agent back into action"), - - acdc_agent_listener:update_agent_status(AgentListener, <<"resume">>), - - acdc_agent_listener:send_status_resume(AgentListener), - - acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), - - apply_state_updates(clear_call(State#state{sync_ref='undefined'}, 'ready')); -paused({'sync_req', JObj}, #state{agent_listener=AgentListener +-spec paused(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +paused('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener ,pause_ref=Ref }=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Process-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'paused', JObj, [{<<"Time-Left">>, time_left(Ref)}]), {'next_state', 'paused', State}; -paused({'member_connect_req', _}, State) -> +paused('cast', {'member_connect_req', _}, State) -> {'next_state', 'paused', State}; -paused({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> +paused('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), - {'next_state', 'paused', State}; -paused({'member_connect_win', _, 'different_node'}, State) -> +paused('cast', {'member_connect_win', _, 'different_node'}, State) -> lager:debug("received member_connect_win for different node (paused)"), {'next_state', 'paused', State}; - -paused({'member_connect_satisfied', _, Node}, State) -> +paused('cast', {'member_connect_satisfied', _, Node}, State) -> lager:info("unexpected connect_satisfied for ~p", [Node]), {'next_state', 'paused', State}; - -paused({'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> +paused('cast', {'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> acdc_agent_listener:originate_uuid(AgentListener, ACallId, ACtrlQ), {'next_state', 'paused', State}; -paused(?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id = MemberCallId} = State) -> +paused('cast', Evt, State) -> + handle_event(Evt, 'paused', State); +paused({call, From}, 'status', #state{pause_ref=Ref}=State) -> + {'next_state', 'paused', State + ,{'reply', From, [{'state', <<"paused">>} + ,{'pause_left', time_left(Ref)} + ]}}; +paused({call, From}, 'current_call', State) -> + {'next_state', 'paused', State + ,{'reply', From, 'undefined'}}; +paused('info', {'timeout', Ref, ?PAUSE_MESSAGE}, #state{pause_ref=Ref + ,agent_listener=AgentListener + }=State) when is_reference(Ref) -> + lager:debug("pause timer expired, putting agent back into action"), + acdc_agent_listener:update_agent_status(AgentListener, <<"resume">>), + acdc_agent_listener:send_status_resume(AgentListener), + acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), + apply_state_updates(clear_call(State#state{sync_ref='undefined'}, 'ready')); +paused('info', ?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id = MemberCallId} = State) -> cancel_if_failed_originate(CallId, MemberCallId, 'paused', State); -paused(?NEW_CHANNEL_FROM(CallId, Number, Name, _), State) -> +paused('info', ?NEW_CHANNEL_FROM(CallId, Number, Name,_), State) -> lager:debug("paused call_from inbound: ~s", [CallId]), {'next_state', 'inbound', start_inbound_call_handling(CallId, Number, Name, State), 'hibernate'}; -paused(?NEW_CHANNEL_TO(CallId, Number, Name), State) -> +paused('info', ?NEW_CHANNEL_TO(CallId,Number, Name), State) -> lager:debug("paused call_to outbound: ~s", [CallId]), {'next_state', 'outbound', start_outbound_call_handling(CallId, Number, Name, State), 'hibernate'}; -paused(_Evt, State) -> - lager:debug("unhandled event while paused: ~p", [_Evt]), - {'next_state', 'paused', State}. +paused('info', Evt, State) -> + handle_info(Evt, 'paused', State). --spec paused(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -paused('status', _, #state{pause_ref=Ref}=State) -> - {'reply', [{'state', <<"paused">>} - ,{'pause_left', time_left(Ref)} - ] - ,'paused', State}; -paused('current_call', _, State) -> - {'reply', 'undefined', 'paused', State}. - --spec outbound(any(), state()) -> kz_term:handle_fsm_ret(state()). -outbound({'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> +%%------------------------------------------------------------------------------ +%% @private +%% @doc +%% @end +%%------------------------------------------------------------------------------ +-spec outbound(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +outbound('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Server-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'outbound', JObj), {'next_state', 'outbound', State}; -outbound({'playback_stop', _JObj}, State) -> +outbound('cast',{'playback_stop', _JObj}, State) -> {'next_state', 'outbound', State}; -outbound(?DESTROYED_CHANNEL(CallId, Cause), #state{agent_listener=AgentListener - ,outbound_call_ids=OutboundCallIds - }=State) -> - acdc_agent_listener:channel_hungup(AgentListener, CallId), - case lists:member(CallId, OutboundCallIds) of - 'true' -> - lager:debug("agent outbound channel ~s down: ~s", [CallId, Cause]), - outbound_hungup(State#state{outbound_call_ids=lists:delete(CallId, OutboundCallIds)}); - 'false' -> - lager:debug("unexpected channel ~s down", [CallId]), - {'next_state', 'outbound', State} - end; -outbound({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> +outbound('cast',{'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (on outbound call)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), {'next_state', 'outbound', State}; -outbound({'member_connect_win', _, 'different_node'}, State) -> +outbound('cast',{'member_connect_win', _, 'different_node'}, State) -> lager:debug("received member_connect_win for different node (outbound)"), {'next_state', 'outbound', State}; - -outbound({'member_connect_satisfied', _, Node}, State) -> +outbound('cast',{'member_connect_satisfied', _, Node}, State) -> lager:info("unexpected connect_satisfied for ~p", [Node]), {'next_state', 'wrapup', State}; - -outbound({'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> +outbound('cast',{'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> acdc_agent_listener:originate_uuid(AgentListener, ACallId, ACtrlQ), {'next_state', 'outbound', State}; -outbound({'originate_failed', _E}, State) -> +outbound('cast',{'originate_failed', _E}, State) -> {'next_state', 'outbound', State}; -outbound({'timeout', Ref, ?PAUSE_MESSAGE}, #state{pause_ref=Ref}=State) -> - lager:debug("pause timer expired while outbound"), - {'next_state', 'outbound', State#state{pause_ref='undefined'}}; -outbound({'timeout', WRef, ?WRAPUP_FINISHED}, #state{wrapup_ref=WRef}=State) -> - lager:debug("wrapup timer ended while on outbound call"), - {'next_state', 'outbound', State#state{wrapup_ref='undefined'}, 'hibernate'}; -outbound({'member_connect_req', _}, State) -> +outbound('cast',{'member_connect_req', _}, State) -> + {'next_state', 'outbound', State}; +outbound('cast',{'leg_created', _, _}, State) -> {'next_state', 'outbound', State}; -outbound({'leg_created', _, _}, State) -> +outbound('cast', {'channel_answered', _}, State) -> {'next_state', 'outbound', State}; -outbound({'channel_answered', _}, State) -> +outbound('cast',{'channel_bridged', _}, State) -> {'next_state', 'outbound', State}; -outbound({'channel_bridged', _}, State) -> +outbound('cast',{'channel_unbridged', _}, State) -> {'next_state', 'outbound', State}; -outbound({'channel_unbridged', _}, State) -> +outbound('cast',{'channel_replaced', _}, State) -> {'next_state', 'outbound', State}; -outbound({'channel_replaced', _}, State) -> +outbound('cast',{'leg_destroyed', _CallId}, State) -> {'next_state', 'outbound', State}; -outbound(?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id = MemberCallId} = State) -> +outbound('cast',{'usurp_control', _CallId}, State) -> + {'next_state', 'outbound', State}; +outbound('cast', ?DESTROYED_CHANNEL(CallId, Cause), #state{agent_listener=AgentListener + ,outbound_call_ids=OutboundCallIds + }=State) -> + acdc_agent_listener:channel_hungup(AgentListener, CallId), + case lists:member(CallId, OutboundCallIds) of + 'true' -> + lager:debug("agent outbound channel ~s down: ~s", [CallId, Cause]), + outbound_hungup(State#state{outbound_call_ids=lists:delete(CallId, OutboundCallIds)}); + 'false' -> + lager:debug("unexpected channel ~s down", [CallId]), + {'next_state', 'outbound', State} + end; +outbound('cast', ?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id = MemberCallId} = State) -> cancel_if_failed_originate(CallId, MemberCallId, 'outbound', State); -outbound(?NEW_CHANNEL_FROM(CallId,_,_, _), #state{outbound_call_ids=[CallId]}=State) -> +outbound('cast', ?NEW_CHANNEL_FROM(CallId,_,_,_), #state{outbound_call_ids=[CallId]}=State) -> {'next_state', 'outbound', State}; -outbound(?NEW_CHANNEL_FROM(CallId,_,_, _), #state{agent_listener=AgentListener +outbound('cast', ?NEW_CHANNEL_FROM(CallId,_,_,_), #state{agent_listener=AgentListener ,outbound_call_ids=OutboundCallIds }=State) -> lager:debug("outbound call_from outbound: ~s", [CallId]), acdc_util:bind_to_call_events(CallId, AgentListener), {'next_state', 'outbound', State#state{outbound_call_ids=[CallId | lists:delete(CallId, OutboundCallIds)]}}; -outbound(?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener +outbound('cast', ?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener ,outbound_call_ids=OutboundCallIds }=State) -> lager:debug("outbound call_to outbound: ~s", [CallId]), acdc_util:bind_to_call_events(CallId, AgentListener), {'next_state', 'outbound', State#state{outbound_call_ids=[CallId | lists:delete(CallId, OutboundCallIds)]}}; -outbound({'leg_destroyed', _CallId}, State) -> - {'next_state', 'outbound', State}; -outbound({'usurp_control', _CallId}, State) -> - {'next_state', 'outbound', State}; -outbound(_Msg, State) -> - lager:debug("ignoring msg in outbound: ~p", [_Msg]), - {'next_state', 'outbound', State}. - --spec outbound(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -outbound('status', _, #state{wrapup_ref=Ref +outbound('cast', Evt, State) -> + handle_event(Evt, 'outbound', State); +outbound({call, From}, 'status', #state{wrapup_ref=Ref ,outbound_call_ids=OutboundCallIds }=State) -> - {'reply', [{'state', <<"outbound">>} + {'next_state', 'outbound', State + ,{'reply', From, [{'state', <<"outbound">>} ,{'wrapup_left', time_left(Ref)} ,{'outbound_call_id', hd(OutboundCallIds)} - ] - ,'outbound', State}; -outbound('current_call', _, State) -> - {'reply', 'undefined', 'outbound', State}. + ]}}; +outbound({call, From}, 'current_call', State) -> + {'next_state', 'outbound', State + ,{'reply', From, 'undefined'}}; +outbound('info', {'timeout', Ref, ?PAUSE_MESSAGE}, #state{pause_ref=Ref}=State) -> + lager:debug("pause timer expired while outbound"), + {'next_state', 'outbound', State#state{pause_ref='undefined'}}; +outbound('info', {'timeout', WRef, ?WRAPUP_FINISHED}, #state{wrapup_ref=WRef}=State) -> + lager:debug("wrapup timer ended while on outbound call"), + {'next_state', 'outbound', State#state{wrapup_ref='undefined'}, 'hibernate'}; +outbound('info', Evt, State) -> + handle_info(Evt, 'outbound', State). + --spec inbound(any(), state()) -> kz_term:handle_fsm_ret(state()). -inbound({'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> +%%------------------------------------------------------------------------------ +%% @private +%% @doc +%% @end +%%------------------------------------------------------------------------------ +-spec inbound(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +inbound('cast', {'sync_req', JObj}, #state{agent_listener=AgentListener}=State) -> lager:debug("recv sync_req from ~s", [kz_json:get_value(<<"Server-ID">>, JObj)]), acdc_agent_listener:send_sync_resp(AgentListener, 'inbound', JObj), {'next_state', 'inbound', State}; -inbound({'playback_stop', _JObj}, State) -> +inbound('cast', {'playback_stop', _JObj}, State) -> {'next_state', 'inbound', State}; -inbound(?DESTROYED_CHANNEL(CallId, Cause), #state{agent_listener=AgentListener - ,inbound_call_ids=OutboundCallIds - }=State) -> - acdc_agent_listener:channel_hungup(AgentListener, CallId), - case lists:member(CallId, OutboundCallIds) of - 'true' -> - lager:debug("agent inbound channel ~s down: ~s", [CallId, Cause]), - inbound_hungup(State#state{inbound_call_ids=lists:delete(CallId, OutboundCallIds)}); - 'false' -> - lager:debug("unexpected channel ~s down", [CallId]), - {'next_state', 'inbound', State} - end; -inbound({'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> +inbound('cast', {'member_connect_win', JObj, 'same_node'}, #state{agent_listener=AgentListener}=State) -> lager:debug("agent won, but can't process this right now (on inbound call)"), acdc_agent_listener:member_connect_retry(AgentListener, JObj), {'next_state', 'inbound', State}; -inbound({'member_connect_win', _, 'different_node'}, State) -> +inbound('cast', {'member_connect_win', _, 'different_node'}, State) -> lager:debug("received member_connect_win for different node (inbound)"), {'next_state', 'inbound', State}; - -inbound({'member_connect_satisfied', _, Node}, State) -> +inbound('cast', {'member_connect_satisfied', _, Node}, State) -> lager:info("unexpected connect_satisfied for ~p", [Node]), {'next_state', 'wrapup', State}; - -inbound({'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> +inbound('cast', {'originate_uuid', ACallId, ACtrlQ}, #state{agent_listener=AgentListener}=State) -> acdc_agent_listener:originate_uuid(AgentListener, ACallId, ACtrlQ), {'next_state', 'inbound', State}; -inbound({'originate_failed', _E}, State) -> +inbound('cast', {'originate_failed', _E}, State) -> {'next_state', 'inbound', State}; -inbound({'timeout', Ref, ?PAUSE_MESSAGE}, #state{pause_ref=Ref}=State) -> - lager:debug("pause timer expired while inbound"), - {'next_state', 'inbound', State#state{pause_ref='undefined'}}; -inbound({'timeout', WRef, ?WRAPUP_FINISHED}, #state{wrapup_ref=WRef}=State) -> - lager:debug("wrapup timer ended while on inbound call"), - {'next_state', 'inbound', State#state{wrapup_ref='undefined'}, 'hibernate'}; -inbound({'member_connect_req', _}, State) -> +inbound('cast', {'member_connect_req', _}, State) -> + {'next_state', 'inbound', State}; +inbound('cast', {'leg_created', _, _}, State) -> {'next_state', 'inbound', State}; -inbound({'leg_created', _, _}, State) -> +inbound('cast', {'channel_answered', _}, State) -> {'next_state', 'inbound', State}; -inbound({'channel_answered', _}, State) -> +inbound('cast', {'channel_bridged', _}, State) -> {'next_state', 'inbound', State}; -inbound({'channel_bridged', _}, State) -> +inbound('cast', {'channel_unbridged', _}, State) -> {'next_state', 'inbound', State}; -inbound({'channel_unbridged', _}, State) -> +inbound('cast', {'channel_replaced', _}, State) -> {'next_state', 'inbound', State}; -inbound({'channel_replaced', _}, State) -> +inbound('cast', {'leg_destroyed', _CallId}, State) -> {'next_state', 'inbound', State}; -inbound(?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id = MemberCallId} = State) -> +inbound('cast', {'usurp_control', _CallId}, State) -> + {'next_state', 'inbound', State}; +inbound('cast', ?DESTROYED_CHANNEL(CallId, Cause), #state{agent_listener=AgentListener + ,inbound_call_ids=InboundCallIds + }=State) -> + acdc_agent_listener:channel_hungup(AgentListener, CallId), + case lists:member(CallId, InboundCallIds) of + 'true' -> + lager:debug("agent inbound channel ~s down: ~s", [CallId, Cause]), + inbound_hungup(State#state{inbound_call_ids=lists:delete(CallId, InboundCallIds)}); + 'false' -> + lager:debug("unexpected channel ~s down", [CallId]), + {'next_state', 'inbound', State} + end; +inbound('cast', ?NEW_CHANNEL_FROM(CallId,_,_, MemberCallId), #state{member_call_id = MemberCallId} = State) -> cancel_if_failed_originate(CallId, MemberCallId, 'inbound', State); -inbound(?NEW_CHANNEL_FROM(CallId,_,_, _), #state{inbound_call_ids=[CallId]}=State) -> +inbound('cast', ?NEW_CHANNEL_FROM(CallId,_,_,_), #state{inbound_call_ids=[CallId]}=State) -> {'next_state', 'inbound', State}; -inbound(?NEW_CHANNEL_FROM(CallId,_,_, _), #state{agent_listener=AgentListener - ,inbound_call_ids=OutboundCallIds +inbound('cast', ?NEW_CHANNEL_FROM(CallId,_,_,_), #state{agent_listener=AgentListener + ,inbound_call_ids=InboundCallIds }=State) -> lager:debug("inbound call_from inbound: ~s", [CallId]), acdc_util:bind_to_call_events(CallId, AgentListener), - {'next_state', 'inbound', State#state{inbound_call_ids=[CallId | lists:delete(CallId, OutboundCallIds)]}}; -inbound(?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener - ,inbound_call_ids=OutboundCallIds + {'next_state', 'inbound', State#state{inbound_call_ids=[CallId | lists:delete(CallId, InboundCallIds)]}}; +inbound('cast', ?NEW_CHANNEL_TO(CallId,_,_), #state{agent_listener=AgentListener + ,inbound_call_ids=InboundCallIds }=State) -> lager:debug("inbound call_to inbound: ~s", [CallId]), acdc_util:bind_to_call_events(CallId, AgentListener), - {'next_state', 'inbound', State#state{inbound_call_ids=[CallId | lists:delete(CallId, OutboundCallIds)]}}; -inbound({'leg_destroyed', _CallId}, State) -> - {'next_state', 'inbound', State}; -inbound({'usurp_control', _CallId}, State) -> - {'next_state', 'inbound', State}; -inbound(_Msg, State) -> - lager:debug("ignoring msg in inbound: ~p", [_Msg]), - {'next_state', 'inbound', State}. - --spec inbound(any(), atom(), state()) -> kz_term:handle_sync_event_ret(state()). -inbound('status', _, #state{wrapup_ref=Ref - ,inbound_call_ids=OutboundCallIds + {'next_state', 'inbound', State#state{inbound_call_ids=[CallId | lists:delete(CallId, InboundCallIds)]}}; +inbound('cast', Evt, State) -> + handle_event(Evt, 'inbound', State); +inbound({call, From}, 'status', #state{wrapup_ref=Ref + ,inbound_call_ids=InboundCallIds }=State) -> - {'reply', [{'state', <<"inbound">>} + {'next_state', 'inbound', State + ,{'reply', From, [{'state', <<"inbound">>} ,{'wrapup_left', time_left(Ref)} - ,{'inbound_call_id', hd(OutboundCallIds)} - ] - ,'inbound', State}; -inbound('current_call', _, State) -> - {'reply', 'undefined', 'inbound', State}. + ,{'inbound_call_id', hd(InboundCallIds)} + ]}}; +inbound({call, From}, 'current_call', State) -> + {'next_state', 'inbound', State + ,{'reply', From, 'undefined'}}; +inbound('info', {'timeout', Ref, ?PAUSE_MESSAGE}, #state{pause_ref=Ref}=State) -> + lager:debug("pause timer expired while inbound"), + {'next_state', 'inbound', State#state{pause_ref='undefined'}}; +inbound('info', {'timeout', WRef, ?WRAPUP_FINISHED}, #state{wrapup_ref=WRef}=State) -> + lager:debug("wrapup timer ended while on inbound call"), + {'next_state', 'inbound', State#state{wrapup_ref='undefined'}, 'hibernate'}; +inbound('info', Evt, State) -> + handle_info(Evt, 'inbound', State). %%------------------------------------------------------------------------------ %% @private -%% @doc Whenever a gen_fsm receives an event sent using -%% gen_fsm:send_all_state_event/2, this function is called to handle +%% @doc Whenever a gen_ServerRef receives an event sent using +%% gen_statem:cast/2, this function is called to handle %% the event. %% %% @end @@ -1988,9 +1999,9 @@ handle_event({'pause', Timeout, Alias}, 'ringing', #state{agent_listener=AgentLi acdc_agent_listener:hangup_call(AgentListener), lager:debug("stopping ringing agent in order to move to pause"), _ = acdc_stats:call_missed(AccountId, QueueId, AgentId, original_call_id(State), <<"agent pausing">>), - NewFSMState = clear_call(State, 'failed'), + NewServerRefState = clear_call(State, 'failed'), %% After clearing we are basically 'ready' state, pause from that state - handle_event({'pause', Timeout, Alias}, 'ready', NewFSMState); + handle_event({'pause', Timeout, Alias}, 'ready', NewServerRefState); handle_event({'pause', 0, _}=Event, 'ready', #state{agent_state_updates=Queue}=State) -> lager:debug("recv status update:, pausing for up to infinity s"), NewQueue = [Event | Queue], @@ -2029,10 +2040,9 @@ handle_event({'refresh', AgentJObj}, StateName, #state{agent_listener=AgentListe {'next_state', StateName, State}; handle_event('load_endpoints', StateName, #state{agent_listener='undefined'}=State) -> lager:debug("agent proc not ready, not loading endpoints yet"), - gen_fsm:send_all_state_event(self(), 'load_endpoints'), + gen_statem:cast(self(), 'load_endpoints'), {'next_state', StateName, State}; handle_event('load_endpoints', StateName, #state{agent_id=AgentId - ,agent_listener=AgentListener ,account_id=AccountId ,account_db=AccountDb }=State) -> @@ -2047,7 +2057,7 @@ handle_event('load_endpoints', StateName, #state{agent_id=AgentId %% Inform us of things with us as owner catch gproc:reg(?OWNER_UPDATE_REG(AccountId, AgentId)), - case get_endpoints([], AgentListener, Call, AgentId, 'undefined') of + case get_endpoints([], Call, AgentId, 'undefined') of {'error', 'no_endpoints'} -> {'next_state', StateName, State}; {'ok', EPs} -> {'next_state', StateName, State#state{endpoints=EPs}}; {'error', E} -> {'stop', E, State} @@ -2058,20 +2068,7 @@ handle_event(_Event, StateName, State) -> %%------------------------------------------------------------------------------ %% @private -%% @doc Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_all_state_event/[2,3], this function is called -%% to handle the event. -%% -%% @end -%%------------------------------------------------------------------------------ --spec handle_sync_event(any(), {pid(),any()}, atom(), state()) -> kz_types:handle_sync_event_ret(state()). -handle_sync_event(_Event, _From, StateName, State) -> - lager:debug("unhandled sync event in state ~s: ~p", [StateName, _Event]), - {'reply', 'ok', StateName, State}. - -%%------------------------------------------------------------------------------ -%% @private -%% @doc This function is called by a gen_fsm when it receives any +%% @doc This function is called by a gen_ServerRef when it receives any %% message other than a synchronous or asynchronous event %% (or a system message). %% @@ -2079,39 +2076,36 @@ handle_sync_event(_Event, _From, StateName, State) -> %%------------------------------------------------------------------------------ -spec handle_info(any(), atom(), state()) -> kz_types:handle_fsm_ret(state()). handle_info({'timeout', _Ref, ?SYNC_RESPONSE_MESSAGE}=Msg, StateName, State) -> - gen_fsm:send_event(self(), Msg), + gen_statem:cast(self(), Msg), {'next_state', StateName, State}; handle_info({'endpoint_edited', EP}, StateName, #state{endpoints=EPs ,account_id=AccountId ,agent_id=AgentId - ,agent_listener=AgentListener }=State) -> EPId = kz_doc:id(EP), case kz_json:get_value(<<"owner_id">>, EP) of AgentId -> lager:debug("device ~s edited, we're the owner, maybe adding it", [EPId]), - {'next_state', StateName, State#state{endpoints=maybe_add_endpoint(EPId, EP, EPs, AccountId, AgentListener)}, 'hibernate'}; + {'next_state', StateName, State#state{endpoints=maybe_add_endpoint(EPId, EP, EPs, AccountId)}, 'hibernate'}; _OwnerId -> lager:debug("device ~s edited, owner now ~s, maybe removing it", [EPId, _OwnerId]), - {'next_state', StateName, State#state{endpoints=maybe_remove_endpoint(EPId, EPs, AccountId, AgentListener)}, 'hibernate'} + {'next_state', StateName, State#state{endpoints=maybe_remove_endpoint(EPId, EPs, AccountId)}, 'hibernate'} end; handle_info({'endpoint_deleted', EP}, StateName, #state{endpoints=EPs ,account_id=AccountId - ,agent_listener=AgentListener }=State) -> EPId = kz_doc:id(EP), lager:debug("device ~s deleted, maybe removing it", [EPId]), - {'next_state', StateName, State#state{endpoints=maybe_remove_endpoint(EPId, EPs, AccountId, AgentListener)}, 'hibernate'}; + {'next_state', StateName, State#state{endpoints=maybe_remove_endpoint(EPId, EPs, AccountId)}, 'hibernate'}; handle_info({'endpoint_created', EP}, StateName, #state{endpoints=EPs ,account_id=AccountId ,agent_id=AgentId - ,agent_listener=AgentListener }=State) -> EPId = kz_doc:id(EP), case kz_json:get_value(<<"owner_id">>, EP) of AgentId -> lager:debug("device ~s created, we're the owner, maybe adding it", [EPId]), - {'next_state', StateName, State#state{endpoints=maybe_add_endpoint(EPId, EP, EPs, AccountId, AgentListener)}, 'hibernate'}; + {'next_state', StateName, State#state{endpoints=maybe_add_endpoint(EPId, EP, EPs, AccountId)}, 'hibernate'}; _OwnerId -> lager:debug("device ~s created, owner is ~s, maybe ignoring", [EPId, _OwnerId]), @@ -2119,17 +2113,17 @@ handle_info({'endpoint_created', EP}, StateName, #state{endpoints=EPs 'undefined' -> {'next_state', StateName, State}; _ -> lager:debug("device ~s created, we're a hotdesk user, maybe adding it", [EPId]), - {'next_state', StateName, State#state{endpoints=maybe_add_endpoint(EPId, EP, EPs, AccountId, AgentListener)}, 'hibernate'} + {'next_state', StateName, State#state{endpoints=maybe_add_endpoint(EPId, EP, EPs, AccountId)}, 'hibernate'} end end; handle_info(?NEW_CHANNEL_FROM(_CallId,_,_,_)=Evt, StateName, State) -> - gen_fsm:send_event(self(), Evt), + gen_statem:cast(self(), Evt), {'next_state', StateName, State}; handle_info(?NEW_CHANNEL_TO(_CallId,_,_)=Evt, StateName, State) -> - gen_fsm:send_event(self(), Evt), + gen_statem:cast(self(), Evt), {'next_state', StateName, State}; handle_info(?DESTROYED_CHANNEL(_, _)=Evt, StateName, State) -> - gen_fsm:send_event(self(), Evt), + gen_statem:cast(self(), Evt), {'next_state', StateName, State}; handle_info(_Info, StateName, State) -> lager:debug("unhandled message in state ~s: ~p", [StateName, _Info]), @@ -2137,17 +2131,23 @@ handle_info(_Info, StateName, State) -> %%------------------------------------------------------------------------------ %% @private -%% @doc This function is called by a gen_fsm when it is about to +%% @doc This function is called by a gen_ServerRef when it is about to %% terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_fsm terminates with +%% necessary cleaning up. When it returns, the gen_ServerRef terminates with %% Reason. The return value is ignored. %% %% @end %%------------------------------------------------------------------------------ -spec terminate(any(), atom(), state()) -> 'ok'. -terminate(_Reason, _StateName, #state{agent_listener=AgentListener}) -> - lager:debug("acdc agent fsm terminating while in ~s: ~p", [_StateName, _Reason]), - acdc_agent_listener:stop(AgentListener), +terminate(Reason, _StateName, #state{account_id=AccountId + ,agent_id=AgentId + ,agent_listener=AgentListener + }) -> + lager:debug("acdc agent statem terminating while in ~s: ~p", [_StateName, Reason]), + + Reason =:= 'normal' + andalso kz_process:spawn(fun acdc_agents_sup:stop_agent/2, [AccountId, AgentId]), + acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_RED_SOLID). %%------------------------------------------------------------------------------ @@ -2180,13 +2180,17 @@ cancel_if_failed_originate(CallId, MemberCallId, StateName, #state{agent_listene cancel_if_failed_originate(_, _, StateName, State) -> {'next_state', StateName, State}. +%%------------------------------------------------------------------------------ +%% @doc +%% @end +%%------------------------------------------------------------------------------ -spec start_wrapup_timer(integer()) -> reference(). start_wrapup_timer(Timeout) when Timeout =< 0 -> start_wrapup_timer(1); % send immediately -start_wrapup_timer(Timeout) -> gen_fsm:start_timer(Timeout*1000, ?WRAPUP_FINISHED). +start_wrapup_timer(Timeout) -> erlang:start_timer(Timeout*1000, self(), ?WRAPUP_FINISHED). -spec start_sync_timer() -> reference(). start_sync_timer() -> - gen_fsm:start_timer(?SYNC_RESPONSE_TIMEOUT, ?SYNC_RESPONSE_MESSAGE). + erlang:start_timer(?SYNC_RESPONSE_TIMEOUT, self(), ?SYNC_RESPONSE_MESSAGE). -spec start_sync_timer(pid()) -> reference(). start_sync_timer(P) -> @@ -2194,13 +2198,13 @@ start_sync_timer(P) -> -spec start_resync_timer() -> reference(). start_resync_timer() -> - gen_fsm:start_timer(?RESYNC_RESPONSE_TIMEOUT, ?RESYNC_RESPONSE_MESSAGE). + erlang:start_timer(?RESYNC_RESPONSE_TIMEOUT, self(), ?RESYNC_RESPONSE_MESSAGE). -spec start_pause_timer(pos_integer()) -> kz_term:api_reference(). start_pause_timer('undefined') -> start_pause_timer(1); start_pause_timer(0) -> 'undefined'; start_pause_timer(Timeout) -> - gen_fsm:start_timer(Timeout * 1000, ?PAUSE_MESSAGE). + erlang:start_timer(Timeout * ?MILLISECONDS_IN_SECOND, self(), ?PAUSE_MESSAGE). -spec call_id(kz_json:object()) -> kz_term:api_binary(). call_id(JObj) -> @@ -2210,12 +2214,12 @@ call_id(JObj) -> end. %% returns time left in seconds --spec time_left(reference() | 'false' | kz_term:api_integer() | 'infinity') -> kz_term:api_integer() | kz_term:ne_binary(). +-spec time_left(kz_term:api_reference() | 'false' | timeout()) -> timeout() | 'undefined'. time_left(Ref) when is_reference(Ref) -> time_left(erlang:read_timer(Ref)); time_left('false') -> 'undefined'; time_left('undefined') -> 'undefined'; -time_left('infinity') -> <<"infinity">>; +time_left('infinity') -> 'infinity'; time_left(Ms) when is_integer(Ms) -> Ms div 1000. -spec clear_call(state(), atom()) -> state(). @@ -2312,7 +2316,7 @@ hangup_call(#state{agent_listener=AgentListener maybe_stop_timer('undefined') -> 'ok'; maybe_stop_timer('infinity') -> 'ok'; maybe_stop_timer(ConnRef) when is_reference(ConnRef) -> - _ = gen_fsm:cancel_timer(ConnRef), + _ = erlang:cancel_timer(ConnRef), 'ok'. -spec maybe_stop_timer(kz_term:api_reference() | 'infinity', boolean()) -> 'ok'. @@ -2360,7 +2364,7 @@ outbound_hungup(#state{agent_listener=AgentListener _W -> case time_left(PRef) of N when is_integer(N), N > 0 -> apply_state_updates(clear_call(State, 'paused')); - <<"infinity">> -> apply_state_updates(clear_call(State, 'paused')); + 'infinity' -> apply_state_updates(clear_call(State, 'paused')); _P -> lager:debug("wrapup left: ~p pause left: ~p", [_W, _P]), acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), @@ -2382,7 +2386,7 @@ inbound_hungup(#state{agent_listener=AgentListener _W -> case time_left(PRef) of N when is_integer(N), N > 0 -> apply_state_updates(clear_call(State, 'paused')); - <<"infinity">> -> apply_state_updates(clear_call(State, 'paused')); + 'infinity' -> apply_state_updates(clear_call(State, 'paused')); _P -> lager:debug("wrapup left: ~p pause left: ~p", [_W, _P]), acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), @@ -2404,12 +2408,11 @@ missed_reason(Reason) -> Reason. -spec find_username(kz_json:object()) -> kz_term:api_binary(). find_username(EP) -> - find_sip_username(EP, kzd_devices:sip_username(EP)). - --spec find_sip_username(kz_json:object(), kz_term:api_binary()) -> kz_term:api_binary(). -find_sip_username(EP, 'undefined') -> kz_json:get_value(<<"To-User">>, EP); -find_sip_username(_EP, Username) -> Username. - + AccountId = kz_json:get_value(<<"Account-ID">>, EP), + AgentId = kz_json:get_value(<<"Endpoint-ID">>, EP), + AccountDb = kzs_util:format_account_db(AccountId), + [Device] = acdc_util:agent_devices(AccountDb, AgentId), + kz_json:get_ne_value([<<"sip">>, <<"username">>], Device). -spec find_endpoint_id(kz_json:object()) -> kz_term:api_binary(). find_endpoint_id(EP) -> @@ -2419,51 +2422,39 @@ find_endpoint_id(EP) -> find_endpoint_id(EP, 'undefined') -> kz_json:get_value(<<"Endpoint-ID">>, EP); find_endpoint_id(_EP, EPId) -> EPId. --spec monitor_endpoint(kz_term:api_object(), kz_term:ne_binary(), kz_term:server_ref()) -> _. -monitor_endpoint('undefined', _, _) -> 'ok'; -monitor_endpoint(EP, AccountId, AgentListener) -> +-spec monitor_endpoint(kz_term:api_object(), kz_term:ne_binary()) -> _. +monitor_endpoint('undefined', _) -> 'ok'; +monitor_endpoint(EP, AccountId) -> Username = find_username(EP), - - %% Bind for outbound call requests - acdc_agent_listener:add_endpoint_bindings(AgentListener - ,kz_endpoint:get_sip_realm(EP, AccountId) - ,Username - ), %% Inform us of device changes catch gproc:reg(?ENDPOINT_UPDATE_REG(AccountId, find_endpoint_id(EP))), catch gproc:reg(?NEW_CHANNEL_REG(AccountId, Username)), catch gproc:reg(?DESTROYED_CHANNEL_REG(AccountId, Username)). --spec unmonitor_endpoint(kz_json:object(), kz_term:ne_binary(), kz_term:server_ref()) -> any(). -unmonitor_endpoint(EP, AccountId, AgentListener) -> +-spec unmonitor_endpoint(kz_json:object(), kz_term:ne_binary()) -> any(). +unmonitor_endpoint(EP, AccountId) -> Username = find_username(EP), - - %% Bind for outbound call requests - acdc_agent_listener:remove_endpoint_bindings(AgentListener - ,kz_endpoint:get_sip_realm(EP, AccountId) - ,Username - ), %% Inform us of device changes catch gproc:unreg(?ENDPOINT_UPDATE_REG(AccountId, find_endpoint_id(EP))), catch gproc:unreg(?NEW_CHANNEL_REG(AccountId, Username)), catch gproc:unreg(?DESTROYED_CHANNEL_REG(AccountId, Username)). --spec maybe_add_endpoint(kz_term:ne_binary(), kz_json:object(), kz_json:objects(), kz_term:ne_binary(), kz_term:server_ref()) -> any(). -maybe_add_endpoint(EPId, EP, EPs, AccountId, AgentListener) -> +-spec maybe_add_endpoint(kz_term:ne_binary(), kz_json:object(), kz_json:objects(), kz_term:ne_binary()) -> any(). +maybe_add_endpoint(EPId, EP, EPs, AccountId) -> case lists:partition(fun(E) -> find_endpoint_id(E) =:= EPId end, EPs) of {[], _} -> lager:debug("endpoint ~s not in our list, adding it", [EPId]), - [begin monitor_endpoint(convert_to_endpoint(EP), AccountId, AgentListener), EP end | EPs]; + [begin monitor_endpoint(convert_to_endpoint(EP), AccountId), EP end | EPs]; {_, _} -> EPs end. --spec maybe_remove_endpoint(kz_term:ne_binary(), kz_json:objects(), kz_term:ne_binary(), kz_term:server_ref()) -> kz_json:objects(). -maybe_remove_endpoint(EPId, EPs, AccountId, AgentListener) -> +-spec maybe_remove_endpoint(kz_term:ne_binary(), kz_json:objects(), kz_term:ne_binary()) -> kz_json:objects(). +maybe_remove_endpoint(EPId, EPs, AccountId) -> case lists:partition(fun(EP) -> find_endpoint_id(EP) =:= EPId end, EPs) of {[], _} -> EPs; %% unknown endpoint {[RemoveEP], EPs1} -> lager:debug("endpoint ~s in our list, removing it", [EPId]), - _ = unmonitor_endpoint(RemoveEP, AccountId, AgentListener), + _ = unmonitor_endpoint(RemoveEP, AccountId), EPs1 end. @@ -2476,15 +2467,15 @@ convert_to_endpoint(EPDoc) -> ], Call = kapps_call:exec(Setters, kapps_call:new()), - case kz_endpoint:build(kz_doc:id(EPDoc), [], Call) of + case kz_endpoint:build(kz_doc:id(EPDoc), kz_json:new(), Call) of {'ok', EP} -> EP; {'error', _} -> 'undefined' end. --spec get_endpoints(kz_json:objects(), kz_term:server_ref(), kapps_call:call(), kz_term:api_binary(), kz_term:api_binary()) -> +-spec get_endpoints(kz_json:objects(), kapps_call:call(), kz_term:api_binary(), kz_term:api_binary()) -> {'ok', kz_json:objects()} | {'error', any()}. -get_endpoints(OrigEPs, AgentListener, Call, AgentId, QueueId) -> +get_endpoints(OrigEPs, Call, AgentId, QueueId) -> case catch acdc_util:get_endpoints(Call, AgentId) of [] -> %% Survive couch connection issue by using last list of valid endpoints @@ -2496,8 +2487,8 @@ get_endpoints(OrigEPs, AgentListener, Call, AgentId, QueueId) -> AccountId = kapps_call:account_id(Call), {Add, Rm} = changed_endpoints(OrigEPs, EPs), - _ = [monitor_endpoint(EP, AccountId, AgentListener) || EP <- Add], - _ = [unmonitor_endpoint(EP, AccountId, AgentListener) || EP <- Rm], + _ = [monitor_endpoint(EP, AccountId) || EP <- Add], + _ = [unmonitor_endpoint(EP, AccountId) || EP <- Rm], {'ok', [kz_json:set_value([<<"Custom-Channel-Vars">>, <<"Queue-ID">>], QueueId, EP) || EP <- EPs]}; {'EXIT', E} -> @@ -2566,7 +2557,6 @@ get_method(Ns) -> standardize_method(<<"post">>) -> 'post'; standardize_method(_) -> 'get'. --spec notify(kz_term:ne_binary(), 'get' | 'post', kz_term:ne_binary(), state()) -> 'ok'. notify(Url, Method, Key, #state{account_id=AccountId ,agent_id=AgentId ,agent_name=AgentName @@ -2600,7 +2590,7 @@ notify_method(Url, 'get', Data) -> ,[], 'get', <<>>, [] ). --spec notify(iolist(), kz_term:proplist(), 'get' | 'post', binary(), kz_term:proplist()) -> 'ok'. +-spec notify(kz_term:ne_binary(), kz_term:proplist(), 'get' | 'post', binary(), kz_term:proplist()) -> 'ok'. notify(Uri, Headers, Method, Body, Opts) -> Options = [{'connect_timeout', 1000} ,{'timeout', 1000} @@ -2628,13 +2618,14 @@ recording_url(JObj) -> Url -> Url end. --spec uri(kz_term:ne_binary(), iolist()) -> iolist(). +-spec uri(kz_term:ne_binary(), iolist()) -> kz_term:ne_binary(). uri(URI, QueryString) -> + QueryBinary = kz_term:to_binary(QueryString), case kz_http_util:urlsplit(URI) of {Scheme, Host, Path, <<>>, Fragment} -> - kz_http_util:urlunsplit({Scheme, Host, Path, QueryString, Fragment}); + kz_http_util:urlunsplit({Scheme, Host, Path, QueryBinary, Fragment}); {Scheme, Host, Path, QS, Fragment} -> - kz_http_util:urlunsplit({Scheme, Host, Path, <>, Fragment}) + kz_http_util:urlunsplit({Scheme, Host, Path, <>, Fragment}) end. -spec apply_state_updates(state()) -> kz_term:handle_fsm_ret(state()). @@ -2647,7 +2638,7 @@ apply_state_updates(#state{agent_state_updates=Q _W -> case time_left(PRef) of N when is_integer(N), N > 0 -> 'paused'; - <<"infinity">> -> 'paused'; + 'infinity' -> 'paused'; _P -> 'ready' end end, @@ -2673,6 +2664,8 @@ apply_state_updates_fold({_, StateName, #state{account_id=AccountId acdc_agent_stats:agent_paused(AccountId, AgentId, time_left(PRef), Alias) end, Acc; +apply_state_updates_fold({_, _, State}, [{'pause', 'infinity', Alias}|Updates]) -> + apply_state_updates_fold(handle_pause('infinity', Alias, State), Updates); apply_state_updates_fold({_, _, State}, [{'pause', 0, Alias}|Updates]) -> apply_state_updates_fold(handle_pause('infinity', Alias, State), Updates); apply_state_updates_fold({_, _, State}, [{'pause', Timeout, Alias}|Updates]) -> @@ -2685,7 +2678,7 @@ apply_state_updates_fold({_, StateName, State}, [{'end_wrapup'}|Updates]) -> apply_state_updates_fold(handle_end_wrapup(StateName, State), Updates); apply_state_updates_fold({_, _, State}, [{'agent_logout'}|_]) -> lager:debug("agent logging out"), - %% Do not continue fold, stop FSM + %% Do not continue fold, stop ServerRef handle_agent_logout(State); apply_state_updates_fold({_, _, State}=Acc, [{'update_presence', PresenceId, PresenceState}|Updates]) -> handle_presence_update(PresenceId, PresenceState, State), @@ -2725,7 +2718,7 @@ handle_resume(#state{agent_listener=AgentListener acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_GREEN), {'next_state', 'ready', State#state{pause_ref='undefined'}}. --spec handle_pause(integer() | 'infinity', kz_term:api_binary(), state()) -> kz_term:handle_fsm_ret(state()). +-spec handle_pause(timeout(), kz_term:ne_binary(), state()) -> kz_types:handle_fsm_ret(state()). handle_pause(Timeout, Alias, #state{agent_listener=AgentListener}=State) -> acdc_agent_listener:presence_update(AgentListener, ?PRESENCE_RED_FLASH), State1 = case Timeout of diff --git a/applications/acdc/src/acdc_agent_handler.erl b/applications/acdc/src/acdc_agent_handler.erl index 319c0080828..7c941f0235d 100644 --- a/applications/acdc/src/acdc_agent_handler.erl +++ b/applications/acdc/src/acdc_agent_handler.erl @@ -177,7 +177,10 @@ maybe_stop_agent(AccountId, AgentId, JObj) -> end. -maybe_pause_agent(AccountId, AgentId, Timeout, Alias, JObj) when is_integer(Timeout) -> +maybe_pause_agent(AccountId, AgentId, <<"infinity">>, Alias, JObj) -> + maybe_pause_agent(AccountId, AgentId, 'infinity', Alias, JObj); +maybe_pause_agent(AccountId, AgentId, Timeout, Alias, JObj) when is_integer(Timeout) + orelse Timeout =:= 'infinity' -> case acdc_agents_sup:find_agent_supervisor(AccountId, AgentId) of 'undefined' -> lager:debug("agent ~s (~s) not found, nothing to do", [AgentId, AccountId]); Sup when is_pid(Sup) -> @@ -320,13 +323,13 @@ handle_destroyed_channel(JObj, AccountId) -> get_to_user(JObj) -> case kz_json:is_defined(<<"To-Uri">>, JObj) of true -> hd(binary:split(kz_json:get_value(<<"To-Uri">>, JObj), <<"@">>)); - false -> get_to_or_destination_number(JObj) + false -> get_username_or_destination_number(JObj) end. --spec get_to_or_destination_number(kz_json:object()) -> kz_term:api_binary(). -get_to_or_destination_number(JObj) -> - case kz_json:is_defined(<<"To">>, JObj) of - true -> hd(binary:split(kz_json:get_value(<<"To">>, JObj), <<"@">>)); +-spec get_username_or_destination_number(kz_json:object()) -> kz_term:api_binary(). +get_username_or_destination_number(JObj) -> + case kz_json:is_defined([<<"Custom-Channel-Vars">>,<<"Username">>], JObj) of + true -> kz_json:get_ne_value([<<"Custom-Channel-Vars">>,<<"Username">>], JObj); false -> kz_json:get_value(<<"Caller-Destination-Number">>, JObj) end. @@ -479,12 +482,9 @@ handle_agent_change(AccountDb, AccountId, AgentId, ?DOC_EDITED) -> P when is_pid(P) -> acdc_agent_fsm:refresh(acdc_agent_sup:fsm(P), JObj) end; handle_agent_change(_, AccountId, AgentId, ?DOC_DELETED) -> - case acdc_agents_sup:find_agent_supervisor(AccountId, AgentId) of - 'undefined' -> lager:debug("user ~s has left us, but wasn't started", [AgentId]); - P when is_pid(P) -> - lager:debug("agent ~s(~s) has been deleted, stopping ~p", [AccountId, AgentId, P]), - _ = acdc_agent_sup:stop(P), - acdc_agent_stats:agent_logged_out(AccountId, AgentId) + case acdc_agents_sup:stop_agent(AccountId, AgentId) of + 'ok' -> acdc_agent_stats:agent_logged_out(AccountId, AgentId); + _ -> 'ok' end. -spec handle_presence_probe(kz_json:object(), kz_term:proplist()) -> 'ok'. diff --git a/applications/acdc/src/acdc_agent_listener.erl b/applications/acdc/src/acdc_agent_listener.erl index ff27f64eb8f..2b741937ca5 100644 --- a/applications/acdc/src/acdc_agent_listener.erl +++ b/applications/acdc/src/acdc_agent_listener.erl @@ -13,7 +13,7 @@ -behaviour(gen_listener). %% API --export([start_link/2, start_link/3, start_link/5 +-export([start_link/3, start_link/4, start_link/5 ,member_connect_resp/2 ,member_connect_retry/2 ,member_connect_accepted/1, member_connect_accepted/2, member_connect_accepted/3 @@ -41,15 +41,12 @@ ,add_acdc_queue/3 ,rm_acdc_queue/2 ,call_status_req/1, call_status_req/2 - ,stop/1 ,fsm_started/2 - ,add_endpoint_bindings/3, remove_endpoint_bindings/3 ,outbound_call_id/2 ,remove_cdr_urls/2 ,logout_agent/1 ,agent_info/2 ,maybe_update_presence_id/2 - ,maybe_update_presence_state/2 ,presence_update/2 ,update_agent_status/2 ]). @@ -81,11 +78,11 @@ ,original_call :: kapps_call:call() ,acdc_queue_id :: api_kz_term:ne_binary() % the ACDc Queue ID ,msg_queue_id :: api_kz_term:ne_binary() % the AMQP Queue ID of the ACDc Queue process - ,agent_id :: api_kz_term:ne_binary() + ,agent_id :: kz_term:api_ne_binary() ,agent_priority :: agent_priority() ,skills :: kz_term:ne_binaries() % skills this agent has - ,acct_db :: api_kz_term:ne_binary() - ,acct_id :: api_kz_term:ne_binary() + ,acct_db :: kz_term:api_ne_binary() + ,acct_id :: kz_term:api_binary() ,fsm_pid :: kz_term:api_pid() ,agent_queues = [] :: kz_term:ne_binaries() ,last_connect :: kz_term:kz_now() | undefined % last connection @@ -178,15 +175,8 @@ %% @doc Starts the server %% @end %%------------------------------------------------------------------------------ --spec start_link(pid(), kz_json:object()) -> kz_term:startlink_ret(). -start_link(Supervisor, AgentJObj) -> - AgentId = kz_doc:id(AgentJObj), - AccountId = account_id(AgentJObj), - Queues = kz_json:get_value(<<"queues">>, AgentJObj, []), - start_link(Supervisor, AgentJObj, AccountId, AgentId, Queues). - --spec start_link(pid(), kz_json:object(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binaries()) -> kz_term:startlink_ret(). -start_link(Supervisor, AgentJObj, AccountId, AgentId, Queues) -> +-spec start_link(pid(), kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object(), kz_term:ne_binaries()) -> kz_types:startlink_ret(). +start_link(Supervisor, AccountId, AgentId, AgentJObj, Queues) -> lager:debug("start bindings for ~s(~s) in ready", [AccountId, AgentId]), gen_listener:start_link(?SERVER ,[{'bindings', ?BINDINGS(AccountId, AgentId)} @@ -195,6 +185,11 @@ start_link(Supervisor, AgentJObj, AccountId, AgentId, Queues) -> ,[Supervisor, AgentJObj, Queues] ). +-spec start_link(pid(), kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object()) -> kz_types:startlink_ret(). +start_link(Supervisor, AccountId, AgentId, AgentJObj) -> + Queues = kz_json:get_value(<<"queues">>, AgentJObj, []), + start_link(Supervisor, AccountId, AgentId, AgentJObj, Queues). + -spec start_link(pid(), kapps_call:call(), kz_term:ne_binary()) -> kz_term:startlink_ret(). start_link(Supervisor, ThiefCall, QueueId) -> AgentId = kapps_call:owner_id(ThiefCall), @@ -208,9 +203,6 @@ start_link(Supervisor, ThiefCall, QueueId) -> ,[Supervisor, ThiefCall, [QueueId]] ). --spec stop(pid()) -> 'ok'. -stop(Srv) -> gen_listener:cast(Srv, {'stop_agent', self()}). - -spec member_connect_resp(pid(), kz_json:object()) -> 'ok'. member_connect_resp(Srv, ReqJObj) -> gen_listener:cast(Srv, {'member_connect_resp', ReqJObj}). @@ -347,22 +339,6 @@ call_status_req(Srv, CallId) -> fsm_started(Srv, FSM) -> gen_listener:cast(Srv, {'fsm_started', FSM}). --spec add_endpoint_bindings(pid(), kz_term:ne_binary(), api_kz_term:ne_binary()) -> 'ok'. -add_endpoint_bindings(_Srv, _Realm, 'undefined') -> - lager:debug("ignoring adding endpoint bindings for undefined user @ ~s", [_Realm]); -add_endpoint_bindings(Srv, Realm, User) -> - lager:debug("adding route bindings to ~p for endpoint ~s@~s", [Srv, User, Realm]), - gen_listener:add_binding(Srv, 'route', [{'realm', Realm} - ,{'user', User} - ]). - --spec remove_endpoint_bindings(pid(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok'. -remove_endpoint_bindings(Srv, Realm, User) -> - lager:debug("removing route bindings to ~p for endpoint ~s@~s", [Srv, User, Realm]), - gen_listener:rm_binding(Srv, 'route', [{'realm', Realm} - ,{'user', User} - ]). - -spec remove_cdr_urls(pid(), kz_term:ne_binary()) -> 'ok'. remove_cdr_urls(Srv, CallId) -> gen_listener:cast(Srv, {'remove_cdr_urls', CallId}). @@ -374,11 +350,6 @@ maybe_update_presence_id(_Srv, 'undefined') -> 'ok'; maybe_update_presence_id(Srv, Id) -> gen_listener:cast(Srv, {'presence_id', Id}). --spec maybe_update_presence_state(pid(), api_kz_term:ne_binary()) -> 'ok'. -maybe_update_presence_state(_Srv, 'undefined') -> 'ok'; -maybe_update_presence_state(Srv, State) -> - presence_update(Srv, State). - -spec presence_update(pid(), api_kz_term:ne_binary()) -> 'ok'. presence_update(_, 'undefined') -> 'ok'; presence_update(Srv, PresenceState) -> @@ -492,10 +463,6 @@ handle_cast({'refresh_config', JObj, StateName}, #state{agent_priority=Priority0 {'noreply', State#state{agent_priority=Priority ,skills=Skills }}; -handle_cast({'stop_agent', Req}, #state{supervisor=Supervisor}=State) -> - lager:debug("stop agent requested by ~p", [Req]), - _ = kz_process:spawn(fun acdc_agent_sup:stop/1, [Supervisor]), - {'noreply', State}; handle_cast({'fsm_started', FSMPid}, State) -> lager:debug("fsm started: ~p", [FSMPid]), @@ -580,8 +547,7 @@ handle_cast({'channel_hungup', CallId}, #state{call=Call ,'hibernate'}; 'true' -> lager:debug("thief is done, going down"), - stop(self()), - {'noreply', State} + {'stop', 'normal', State} end; _ -> case props:get_value(CallId, ACallIds) of @@ -852,8 +818,9 @@ handle_cast({'member_callback_accepted', ACall}, #state{msg_queue_id=AmqpQueue send_member_callback_accepted(AmqpQueue, call_id(Call), AccountId, AgentId, MyId), - ACall1 = kapps_call:set_control_queue(props:get_value(ACallId, ACallIds), ACall), - kapps_call_command:prompt(<<"queue-now_calling_back">>, ACall1), + CtrlQ = props:get_value(ACallId, ACallIds), + ACall1 = kapps_call:set_control_queue(CtrlQ, ACall), + kapps_call_command:audio_macro([{'prompt', <<"queue-now_calling_back">>}], ACall1), {'noreply', State#state{agent_call_ids=ACallIds1}, 'hibernate'}; @@ -1096,6 +1063,8 @@ terminate(Reason, #state{agent_queues=Queues } ) when Reason == 'normal'; Reason == 'shutdown' -> _ = [rm_queue_binding(AccountId, AgentId, QueueId) || QueueId <- Queues], + Reason =:= 'normal' %% Prevent race condition of supervisor delete_child/restart_child + andalso kz_process:spawn(fun acdc_agents_sup:stop_agent/2, [AccountId, AgentId]), lager:debug("agent process going down: ~p", [Reason]); terminate(_Reason, _State) -> lager:debug("agent process going down: ~p", [_Reason]). @@ -1596,28 +1565,28 @@ recording_format() -> -spec agent_id(agent()) -> kz_term:api_binary(). agent_id(Agent) -> - case kz_json:is_json_object(Agent) of - 'true' -> kz_doc:id(Agent); - 'false' -> kapps_call:owner_id(Agent) + case is_thief(Agent) of + 'true' -> kapps_call:owner_id(Agent); + 'false' -> kz_doc:id(Agent) end. -spec account_id(agent()) -> kz_term:api_binary(). account_id(Agent) -> - case kz_json:is_json_object(Agent) of - 'true' -> find_account_id(Agent); - 'false' -> kapps_call:account_id(Agent) + case is_thief(Agent) of + 'true' -> kapps_call:account_id(Agent); + 'false' -> find_account_id(Agent) end. -spec account_db(agent()) -> kz_term:api_binary(). account_db(Agent) -> - case kz_json:is_json_object(Agent) of - 'true' -> kz_doc:account_db(Agent); - 'false' -> kapps_call:account_db(Agent) + case is_thief(Agent) of + 'true' -> kapps_call:account_db(Agent); + 'false' -> kz_doc:account_db(Agent) end. -spec record_calls(agent()) -> boolean(). record_calls(Agent) -> - kz_json:is_json_object(Agent) + not is_thief(Agent) andalso kz_json:is_true(<<"record_calls">>, Agent, 'false'). -spec is_thief(agent()) -> boolean(). @@ -1636,6 +1605,7 @@ stop_agent_leg(ACallId, ACtrlQ) -> lager:debug("sending hangup to ~s: ~s", [ACallId, ACtrlQ]), kapi_dialplan:publish_command(ACtrlQ, Command). +-spec find_account_id(kz_json:object()) -> kz_term:api_ne_binary(). find_account_id(JObj) -> case kz_doc:account_id(JObj) of 'undefined' -> kzs_util:format_account_id(kz_doc:account_db(JObj)); diff --git a/applications/acdc/src/acdc_agent_maintenance.erl b/applications/acdc/src/acdc_agent_maintenance.erl index 2570d5512d7..8af5a3b2e4b 100644 --- a/applications/acdc/src/acdc_agent_maintenance.erl +++ b/applications/acdc/src/acdc_agent_maintenance.erl @@ -45,31 +45,15 @@ agent_status(AccountId, AgentId) -> S -> acdc_agent_sup:status(S) end. --spec acct_restart(kz_term:text()) -> 'ok'. +-spec acct_restart(kz_term:text()) -> [kz_types:sup_startchild_ret()]. acct_restart(AccountId) when not is_binary(AccountId) -> acct_restart(kz_term:to_binary(AccountId)); acct_restart(AccountId) -> - case acdc_agents_sup:find_acct_supervisors(AccountId) of - [] -> lager:info("no agents with account id ~s available", [AccountId]); - As -> - lager:debug("terminating existing agent processes in ~s", [AccountId]), - _ = [exit(Sup, 'kill') || Sup <- As], - lager:info("restarting agents in ~s", [AccountId]), - _ = acdc_init:init_acct_agents(AccountId), - 'ok' - end. + acdc_agents_sup:restart_acct(AccountId). --spec agent_restart(kz_term:text(), kz_term:text()) -> 'ok'. +-spec agent_restart(kz_term:text(), kz_term:text()) -> kz_types:sup_startchild_ret(). agent_restart(AccountId, AgentId) when not is_binary(AccountId); not is_binary(AgentId) -> agent_restart(kz_term:to_binary(AccountId), kz_term:to_binary(AgentId)); agent_restart(AccountId, AgentId) -> - case acdc_agents_sup:find_agent_supervisor(AccountId, AgentId) of - 'undefined' -> lager:info("no agent ~s in account ~s available", [AgentId, AccountId]); - S -> - lager:info("terminating existing agent process ~p", [S]), - exit(S, 'kill'), - lager:info("restarting agent ~s in ~s", [AgentId, AccountId]), - acdc_agents_sup:new(AccountId, AgentId), - 'ok' - end. + acdc_agents_sup:restart_agent(AccountId, AgentId). \ No newline at end of file diff --git a/applications/acdc/src/acdc_agent_stats.erl b/applications/acdc/src/acdc_agent_stats.erl index 8e3a3905562..be40d167a41 100644 --- a/applications/acdc/src/acdc_agent_stats.erl +++ b/applications/acdc/src/acdc_agent_stats.erl @@ -17,8 +17,8 @@ ,agent_logged_in/2 ,agent_logged_out/2 ,agent_pending_logged_out/2 - ,agent_connecting/3, agent_connecting/5 - ,agent_connected/3, agent_connected/5 + ,agent_connecting/5, agent_connecting/6 + ,agent_connected/5, agent_connected/6 ,agent_wrapup/3 ,agent_paused/4 ,agent_outbound/3 @@ -125,14 +125,14 @@ agent_pending_logged_out(AccountId, AgentId) -> ,fun kapi_acdc_stats:publish_status_pending_logged_out/1 ). --spec agent_connecting(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> +-spec agent_connecting(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok'. -agent_connecting(AccountId, AgentId, CallId) -> - agent_connecting(AccountId, AgentId, CallId, 'undefined', 'undefined'). +agent_connecting(AccountId, AgentId, CallId, CIDName, CIDNumber) -> + agent_connecting(AccountId, AgentId, CallId, CIDName, CIDNumber, 'undefined'). --spec agent_connecting(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:api_binary(), kz_term:api_binary()) -> +-spec agent_connecting(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:api_binary(), kz_term:api_binary(), kz_term:api_binary()) -> 'ok'. -agent_connecting(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber) -> +agent_connecting(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber, QueueId) -> Prop = props:filter_undefined( [{<<"Account-ID">>, AccountId} ,{<<"Agent-ID">>, AgentId} @@ -141,6 +141,7 @@ agent_connecting(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber) -> ,{<<"Call-ID">>, CallId} ,{<<"Caller-ID-Name">>, CallerIDName} ,{<<"Caller-ID-Number">>, CallerIDNumber} + ,{<<"Queue-ID">>, QueueId} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), edr_log_status_change(AccountId, Prop), @@ -148,14 +149,14 @@ agent_connecting(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber) -> ,fun kapi_acdc_stats:publish_status_connecting/1 ). --spec agent_connected(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> +-spec agent_connected(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok'. -agent_connected(AccountId, AgentId, CallId) -> - agent_connected(AccountId, AgentId, CallId, 'undefined', 'undefined'). +agent_connected(AccountId, AgentId, CallId, CIDName, CIDNumber) -> + agent_connected(AccountId, AgentId, CallId, CIDName, CIDNumber, 'undefined'). --spec agent_connected(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:api_binary(), kz_term:api_binary()) -> +-spec agent_connected(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:api_binary(), kz_term:api_binary(), kz_term:api_binary()) -> 'ok'. -agent_connected(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber) -> +agent_connected(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber, QueueId) -> Prop = props:filter_undefined( [{<<"Account-ID">>, AccountId} ,{<<"Agent-ID">>, AgentId} @@ -164,6 +165,7 @@ agent_connected(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber) -> ,{<<"Call-ID">>, CallId} ,{<<"Caller-ID-Name">>, CallerIDName} ,{<<"Caller-ID-Number">>, CallerIDNumber} + ,{<<"Queue-ID">>, QueueId} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), edr_log_status_change(AccountId, Prop), @@ -342,8 +344,9 @@ status_match_builder_fold(<<"Agent-ID">>, AgentId, {StatusStat, Contstraints}) - status_match_builder_fold(<<"Start-Range">>, Start, {StatusStat, Contstraints}) -> Now = kz_time:current_tstamp(), Past = Now - ?CLEANUP_WINDOW, + Start1 = acdc_stats_util:apply_query_window_wiggle_room(Start, Past), - try kz_term:to_integer(Start) of + try kz_term:to_integer(Start1) of N when N < Past -> {'error', kz_json:from_list([{<<"Start-Range">>, <<"supplied value is too far in the past">>} ,{<<"Window-Size">>, ?CLEANUP_WINDOW} @@ -366,8 +369,9 @@ status_match_builder_fold(<<"Start-Range">>, Start, {StatusStat, Contstraints}) status_match_builder_fold(<<"End-Range">>, End, {StatusStat, Contstraints}) -> Now = kz_time:current_tstamp(), Past = Now - ?CLEANUP_WINDOW, + End1 = acdc_stats_util:apply_query_window_wiggle_room(End, Past), - try kz_term:to_integer(End) of + try kz_term:to_integer(End1) of N when N < Past -> {'error', kz_json:from_list([{<<"End-Range">>, <<"supplied value is too far in the past">>} ,{<<"Window-Size">>, ?CLEANUP_WINDOW} diff --git a/applications/acdc/src/acdc_agent_sup.erl b/applications/acdc/src/acdc_agent_sup.erl index a459c1e6053..86a21002e56 100644 --- a/applications/acdc/src/acdc_agent_sup.erl +++ b/applications/acdc/src/acdc_agent_sup.erl @@ -17,10 +17,9 @@ -define(SERVER, ?MODULE). %% API --export([start_link/1, start_link/2, start_link/4 +-export([start_link/2, start_link/3, start_link/4 ,listener/1 ,fsm/1 - ,stop/1 ,status/1 ]). @@ -36,40 +35,35 @@ %%%============================================================================= %%------------------------------------------------------------------------------ -%% @doc Starts the supervisor +%% @doc Starts the supervisor. %% @end %%------------------------------------------------------------------------------ --spec start_link(kz_json:object()) -> kz_term:startlink_ret(). -start_link(AgentJObj) -> - supervisor:start_link(?SERVER, [AgentJObj]). - --spec start_link(kapps_call:call(), kz_term:ne_binary()) -> kz_term:startlink_ret(). +-spec start_link(kapps_call:call(), kz_term:ne_binary()) -> kz_types:startlink_ret(). start_link(ThiefCall, QueueId) -> supervisor:start_link(?SERVER, [ThiefCall, QueueId]). --spec start_link(kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object(), kz_term:ne_binaries()) -> kz_term:startlink_ret(). -start_link(AccountId, AgentId, AgentJObj, Queues) -> - supervisor:start_link(?SERVER, [AccountId, AgentId, AgentJObj, Queues]). +-spec start_link(kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object()) -> kz_types:startlink_ret(). +start_link(AcctId, AgentId, AgentJObj) -> + supervisor:start_link(?SERVER, [AcctId, AgentId, AgentJObj]). --spec stop(pid()) -> 'ok' | {'error', 'not_found'}. -stop(Supervisor) -> - supervisor:terminate_child('acdc_agents_sup', Supervisor). +-spec start_link(kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object(), kz_term:ne_binaries()) -> kz_types:startlink_ret(). +start_link(AcctId, AgentId, AgentJObj, Queues) -> + supervisor:start_link(?SERVER, [AcctId, AgentId, AgentJObj, Queues]). -spec status(pid()) -> 'ok'. status(Supervisor) -> case {listener(Supervisor), fsm(Supervisor)} of {LPid, FSM} when is_pid(LPid), is_pid(FSM) -> - {AccountId, AgentId, Q} = acdc_agent_listener:config(LPid), + {AcctId, AgentId, Q} = acdc_agent_listener:config(LPid), Status = acdc_agent_fsm:status(FSM), - ?PRINT("Agent ~s (Account ~s)", [AgentId, AccountId]), + ?PRINT("Agent ~s (Account ~s)", [AgentId, AcctId]), ?PRINT(" Supervisor: ~p", [Supervisor]), ?PRINT(" Listener: ~p (~s)", [LPid, Q]), ?PRINT(" FSM: ~p", [FSM]), print_status(augment_status(Status, LPid)); _ -> - ?PRINT("Agent Supervisor ~p is dead, stopping", [Supervisor]), - stop(Supervisor) + ?PRINT("Agent Supervisor ~p is dead", [Supervisor]) end. -define(AGENT_INFO_FIELDS, kapps_config:get(?CONFIG_CAT, <<"agent_info_fields">> @@ -111,8 +105,7 @@ child_of_type(S, T) -> [P || {Ty, P, 'worker', _} <- supervisor:which_children(S %%%============================================================================= %%------------------------------------------------------------------------------ -%% @private -%% @doc Whenever a supervisor is started using supervisor:start_link/[2,3], +%% @doc Whenever a supervisor is started using `supervisor:start_link/[2,3]', %% this function is called by the new process to find out about %% restart strategy, maximum restart frequency and child %% specifications. diff --git a/applications/acdc/src/acdc_agents_sup.erl b/applications/acdc/src/acdc_agents_sup.erl index f2b457d58ec..6305a88b02a 100644 --- a/applications/acdc/src/acdc_agents_sup.erl +++ b/applications/acdc/src/acdc_agents_sup.erl @@ -21,6 +21,7 @@ -export([start_link/0 ,new/1, new/2, new/4 ,new_thief/2 + ,stop_agent/2 ,workers/0 ,find_acct_supervisors/1 ,find_agent_supervisor/2 @@ -33,18 +34,20 @@ %% Supervisor callbacks -export([init/1]). --define(CHILDREN, [?SUPER_TYPE('acdc_agent_sup', 'transient') - ]). +-define(CHILD_ID(AccountId, AgentId), <>). +-define(THIEF_ID(AccountId, QueueId, CallId), <<"thief-", AccountId/binary, "-", QueueId/binary, "-", CallId/binary>>). +-define(CHILD(Id, Args), ?SUPER_NAME_ARGS_TYPE(Id, 'acdc_agent_sup', Args, 'transient')). +-define(INITIAL_CHILDREN, []). %%%============================================================================= %%% API functions %%%============================================================================= %%------------------------------------------------------------------------------ -%% @doc Starts the supervisor +%% @doc Starts the supervisor. %% @end %%------------------------------------------------------------------------------ --spec start_link() -> kz_term:startlink_ret(). +-spec start_link() -> kz_types:startlink_ret(). start_link() -> supervisor:start_link({'local', ?SERVER}, ?MODULE, []). @@ -55,50 +58,60 @@ status() -> _ = kz_process:spawn(fun() -> lists:foreach(fun acdc_agent_sup:status/1, Ws) end), 'ok'. --spec new(kz_json:object()) -> kz_term:sup_startchild_ret(). +-spec new(kz_json:object()) -> kz_types:sup_startchild_ret(). new(JObj) -> - acdc_agent_manager:start_agent(kz_doc:account_id(JObj) - ,kz_doc:id(JObj) - ,[JObj] - ). + AccountId = kz_doc:account_id(JObj), + AgentId = kz_doc:id(JObj), + start_agent(AccountId, AgentId, JObj). --spec new(kz_term:ne_binary(), kz_term:ne_binary()) -> kz_term:sup_startchild_ret(). +-spec new(kz_term:ne_binary(), kz_term:ne_binary()) -> kz_types:sup_startchild_ret(). new(AccountId, AgentId) -> - {'ok', Agent} = kz_datamgr:open_doc(kzs_util:format_account_db(AccountId), AgentId), - acdc_agent_manager:start_agent(AccountId - ,AgentId - ,[Agent] - ). + {'ok', JObj} = kz_datamgr:open_doc(kzs_util:format_account_db(AccountId), AgentId), + start_agent(AccountId, AgentId, JObj). --spec new(kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object(), kz_term:ne_binaries()) -> kz_term:sup_startchild_ret() | 'ok'. +-spec new(kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object(), kz_term:ne_binaries()) -> kz_types:sup_startchild_ret(). new(AccountId, AgentId, AgentJObj, Queues) -> - acdc_agent_manager:start_agent(AccountId - ,AgentId - ,[AgentJObj, AccountId, AgentId, Queues] - ). - --spec new_thief(kapps_call:call(), kz_term:ne_binary()) -> kz_term:sup_startchild_ret(). -new_thief(Call, QueueId) -> supervisor:start_child(?SERVER, [Call, QueueId]). + start_agent(AccountId, AgentId, AgentJObj, [Queues]). + +-spec new_thief(kapps_call:call(), kz_term:ne_binary()) -> kz_types:sup_startchild_ret(). +new_thief(Call, QueueId) -> + AccountId = kapps_call:account_id(Call), + CallId = kapps_call:call_id(Call), + Id = ?THIEF_ID(AccountId, QueueId, CallId), + supervisor:start_child(?MODULE, ?CHILD(Id, [Call, QueueId])). + +-spec stop_agent(kz_term:ne_binary(), kz_term:ne_binary()) -> kz_types:sup_deletechild_ret(). +stop_agent(AccountId, AgentId) -> + Id = ?CHILD_ID(AccountId, AgentId), + case supervisor:terminate_child(?SERVER, Id) of + 'ok' -> + lager:info("stopping agent ~s(~s)", [AgentId, AccountId]), + supervisor:delete_child(?SERVER, Id); + E -> + lager:info("no supervisor for agent ~s(~s) to stop", [AgentId, AccountId]), + E + end. -spec workers() -> kz_term:pids(). workers() -> [Pid || {_, Pid, 'supervisor', [_]} <- supervisor:which_children(?SERVER)]. --spec restart_acct(kz_term:ne_binary()) -> [kz_term:sup_startchild_ret()]. +-spec restart_acct(kz_term:ne_binary()) -> [kz_types:sup_startchild_ret()]. restart_acct(AccountId) -> [restart_agent(AccountId, AgentId) || {_, {AccountId1, AgentId, _}} <- agents_running() ,AccountId =:= AccountId1 ]. --spec restart_agent(kz_term:ne_binary(), kz_term:ne_binary()) -> kz_term:sup_startchild_ret(). +-spec restart_agent(kz_term:ne_binary(), kz_term:ne_binary()) -> kz_types:sup_startchild_ret(). restart_agent(AccountId, AgentId) -> - case find_agent_supervisor(AccountId, AgentId) of - 'undefined' -> + Id = ?CHILD_ID(AccountId, AgentId), + case supervisor:terminate_child(?SERVER, Id) of + 'ok' -> + lager:info("restarting agent ~s(~s)", [AgentId, AccountId]), + supervisor:restart_child(?SERVER, Id); + E -> lager:info("no supervisor for agent ~s(~s) to restart", [AgentId, AccountId]), - new(AccountId, AgentId); - S -> - _ = acdc_agent_sup:stop(S), - new(AccountId, AgentId) + E end. -spec find_acct_supervisors(kz_term:ne_binary()) -> kz_term:pids(). @@ -120,13 +133,13 @@ agents_running() -> find_agent_supervisor(AccountId, AgentId) -> find_agent_supervisor(AccountId, AgentId, workers()). -spec find_agent_supervisor(kz_term:api_binary(), kz_term:api_binary(), kz_term:pids()) -> kz_term:api_pid(). -find_agent_supervisor(_AccountId, _AgentId, []) -> - lager:debug("ran out of supers"), - 'undefined'; find_agent_supervisor(AccountId, AgentId, _) when AccountId =:= 'undefined'; - AgentId =:= 'undefined' -> + AgentId =:= 'undefined' -> lager:debug("failed to get good data: ~s ~s", [AccountId, AgentId]), 'undefined'; +find_agent_supervisor(AccountId, AgentId, []) -> + lager:debug("supervisor for agent ~s(~s) not found", [AgentId, AccountId]), + 'undefined'; find_agent_supervisor(AccountId, AgentId, [Super|Rest]) -> case catch acdc_agent_listener:config(acdc_agent_sup:listener(Super)) of {'EXIT', _E} -> find_agent_supervisor(AccountId, AgentId, Rest); @@ -139,8 +152,7 @@ find_agent_supervisor(AccountId, AgentId, [Super|Rest]) -> %%%============================================================================= %%------------------------------------------------------------------------------ -%% @private -%% @doc Whenever a supervisor is started using supervisor:start_link/[2,3], +%% @doc Whenever a supervisor is started using `supervisor:start_link/[2,3]', %% this function is called by the new process to find out about %% restart strategy, maximum restart frequency and child %% specifications. @@ -148,14 +160,36 @@ find_agent_supervisor(AccountId, AgentId, [Super|Rest]) -> %%------------------------------------------------------------------------------ -spec init(any()) -> kz_types:sup_init_ret(). init([]) -> - RestartStrategy = 'simple_one_for_one', + RestartStrategy = 'one_for_one', MaxRestarts = 1, MaxSecondsBetweenRestarts = 1, SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, - {'ok', {SupFlags, ?CHILDREN}}. + {'ok', {SupFlags, ?INITIAL_CHILDREN}}. %%%============================================================================= %%% Internal functions %%%============================================================================= + +%%------------------------------------------------------------------------------ +%% @doc Start a new agent supervisor child. There can only be one child per +%% account ID/agent ID pair. +%% @end +%%------------------------------------------------------------------------------ +-spec start_agent(kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object()) -> kz_types:sup_startchild_ret(). +start_agent(AccountId, AgentId, AgentJObj) -> + start_agent(AccountId, AgentId, AgentJObj, []). + +-spec start_agent(kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object(), [any()]) -> kz_types:sup_startchild_ret(). +start_agent(AccountId, AgentId, AgentJObj, ExtraArgs) -> + Id = ?CHILD_ID(AccountId, AgentId), + case supervisor:start_child(?SERVER, ?CHILD(Id, [AccountId, AgentId, AgentJObj] ++ ExtraArgs)) of + {'error', 'already_present'}=E -> + lager:debug("agent ~s(~s) already present", [AgentId, AccountId]), + E; + {'error', {'already_started', Pid}}=E -> + lager:debug("agent ~s(~s) already started here: ~p", [AgentId, AccountId, Pid]), + E; + StartChildRet -> StartChildRet + end. diff --git a/applications/acdc/src/acdc_announcements_sup.erl b/applications/acdc/src/acdc_announcements_sup.erl index 88a9dc887e3..3362407896f 100644 --- a/applications/acdc/src/acdc_announcements_sup.erl +++ b/applications/acdc/src/acdc_announcements_sup.erl @@ -57,7 +57,7 @@ maybe_start_announcements(Manager, Call, Props) -> %% %% @end %%------------------------------------------------------------------------------ --spec stop_announcements(pid()) -> 'ok' | {'error', atom()}. +-spec stop_announcements(pid()) -> kz_types:sup_terminatechild_ret(). stop_announcements(Pid) -> supervisor:terminate_child(?SERVER, Pid). diff --git a/applications/acdc/src/acdc_init.erl b/applications/acdc/src/acdc_init.erl index cd8232bff46..b589e5d1839 100644 --- a/applications/acdc/src/acdc_init.erl +++ b/applications/acdc/src/acdc_init.erl @@ -23,6 +23,8 @@ -include("acdc.hrl"). +-define(CB_AGENTS_LIST, <<"queues/agents_listing">>). + -spec start_link() -> 'ignore'. start_link() -> _ = declare_exchanges(), @@ -87,7 +89,8 @@ init_acct_queues(AccountDb, AccountId) -> -spec init_acct_agents(kz_term:ne_binary(), kz_term:ne_binary()) -> any(). init_acct_agents(AccountDb, AccountId) -> init_agents(AccountId - ,kz_datamgr:get_results(AccountDb, <<"queues/agents_listing">>, []) + ,kz_datamgr:get_results(AccountDb, ?CB_AGENTS_LIST + ,[{'reduce', 'false'}]) ). -spec init_queues(kz_term:ne_binary(), kazoo_data:get_results_return()) -> any(). diff --git a/applications/acdc/src/acdc_maintenance.erl b/applications/acdc/src/acdc_maintenance.erl index 825959a0142..d758d048d05 100644 --- a/applications/acdc/src/acdc_maintenance.erl +++ b/applications/acdc/src/acdc_maintenance.erl @@ -304,7 +304,7 @@ flush_call_stat(CallId) -> _ = acdc_stats:call_abandoned(kz_json:get_value(<<"Account-ID">>, Call) ,kz_json:get_value(<<"Queue-ID">>, Call) ,CallId - ,<<"INTERNAL_ERROR">> + ,?ABANDON_INTERNAL_ERROR ), io:format("setting call to 'abandoned'~n", []) end. diff --git a/applications/acdc/src/acdc_queue_fsm.erl b/applications/acdc/src/acdc_queue_fsm.erl index 8cab22ca7aa..c062cceac26 100644 --- a/applications/acdc/src/acdc_queue_fsm.erl +++ b/applications/acdc/src/acdc_queue_fsm.erl @@ -11,10 +11,10 @@ %%% @end %%%----------------------------------------------------------------------------- -module(acdc_queue_fsm). --behaviour(gen_fsm). +-behaviour(gen_statem). %% API --export([start_link/3]). +-export([start_link/4]). %% Event injectors -export([member_call/3 @@ -35,16 +35,14 @@ ]). %% State handlers --export([ready/2, ready/3 - ,connect_req/2, connect_req/3 - ,connecting/2, connecting/3 +-export([ready/3 + ,connect_req/3 + ,connecting/3 ]). -%% gen_fsm callbacks +%% gen_statem callbacks -export([init/1 - ,handle_event/3 - ,handle_sync_event/4 - ,handle_info/3 + ,callback_mode/0 ,terminate/3 ,code_change/4 ]). @@ -65,7 +63,7 @@ -define(AGENT_RING_TIMEOUT, 5). -define(AGENT_RING_TIMEOUT_MESSAGE, 'agent_timer_expired'). --record(state, {queue_proc :: pid() +-record(state, {listener_proc :: pid() | undefined ,manager_proc :: pid() ,connect_wins = [] :: kz_json:objects() ,connect_resps = [] :: kz_json:objects() @@ -111,18 +109,19 @@ %%%============================================================================= %%------------------------------------------------------------------------------ -%% @doc Creates a gen_fsm process which calls Module:init/1 to +%% @doc Creates a gen_statem process which calls Module:init/1 to %% initialize. To ensure a synchronized start-up procedure, this %% function does not return until Module:init/1 has returned. %% @end %%------------------------------------------------------------------------------ --spec start_link(pid(), pid(), kz_json:object()) -> kz_term:startlink_ret(). -start_link(MgrPid, ListenerPid, QueueJObj) -> - gen_fsm:start_link(?SERVER, [MgrPid, ListenerPid, QueueJObj], []). +-spec start_link(pid(), pid(), kz_term:ne_binary(), kz_term:ne_binary()) -> kz_types:startlink_ret(). +start_link(WorkerSup, MgrPid, AccountId, QueueId) -> + gen_statem:start_link(?SERVER, [WorkerSup, MgrPid, AccountId, QueueId], []). -spec refresh(pid(), kz_json:object()) -> 'ok'. refresh(FSM, QueueJObj) -> - gen_fsm:send_all_state_event(FSM, {'refresh', QueueJObj}). + gen_statem:cast(FSM, {'refresh', QueueJObj}). + %%------------------------------------------------------------------------------ %% @doc @@ -130,7 +129,7 @@ refresh(FSM, QueueJObj) -> %%------------------------------------------------------------------------------ -spec member_call(pid(), kz_json:object(), gen_listener:basic_deliver()) -> 'ok'. member_call(FSM, CallJObj, Delivery) -> - gen_fsm:send_event(FSM, {'member_call', CallJObj, Delivery}). + gen_statem:cast(FSM, {'member_call', CallJObj, Delivery}). %%------------------------------------------------------------------------------ %% @doc @@ -138,7 +137,7 @@ member_call(FSM, CallJObj, Delivery) -> %%------------------------------------------------------------------------------ -spec member_call_cancel(pid(), kz_json:object()) -> 'ok'. member_call_cancel(FSM, JObj) -> - gen_fsm:send_event(FSM, {'member_call_cancel', JObj}). + gen_statem:cast(FSM, {'member_call_cancel', JObj}). %%------------------------------------------------------------------------------ %% @doc @@ -146,7 +145,7 @@ member_call_cancel(FSM, JObj) -> %%------------------------------------------------------------------------------ -spec member_connect_resp(pid(), kz_json:object()) -> 'ok'. member_connect_resp(FSM, Resp) -> - gen_fsm:send_event(FSM, {'agent_resp', Resp}). + gen_statem:cast(FSM, {'agent_resp', Resp}). %%------------------------------------------------------------------------------ %% @doc @@ -154,11 +153,11 @@ member_connect_resp(FSM, Resp) -> %%------------------------------------------------------------------------------ -spec member_accepted(pid(), kz_json:object()) -> 'ok'. member_accepted(FSM, AcceptJObj) -> - gen_fsm:send_event(FSM, {'accepted', AcceptJObj}). + gen_statem:cast(FSM, {'accepted', AcceptJObj}). -spec member_callback_accepted(pid(), kz_json:object()) -> 'ok'. member_callback_accepted(FSM, AcceptJObj) -> - gen_fsm:send_event(FSM, {'callback_accepted', AcceptJObj}). + gen_statem:cast(FSM, {'callback_accepted', AcceptJObj}). %%------------------------------------------------------------------------------ %% @doc @@ -166,7 +165,16 @@ member_callback_accepted(FSM, AcceptJObj) -> %%------------------------------------------------------------------------------ -spec member_connect_retry(pid(), kz_json:object()) -> 'ok'. member_connect_retry(FSM, RetryJObj) -> - gen_fsm:send_event(FSM, {'retry', RetryJObj}). + gen_statem:cast(FSM, {'retry', RetryJObj}). + +%%------------------------------------------------------------------------------ +%% @doc +%% @end +%%------------------------------------------------------------------------------ +-spec register_callback(pid(), kz_json:object()) -> 'ok'. +register_callback(FSM, JObj) -> + gen_statem:cast(FSM, {'register_callback', JObj}). + %%------------------------------------------------------------------------------ %% @doc When a queue is processing a call, it will receive call events. @@ -176,56 +184,51 @@ member_connect_retry(FSM, RetryJObj) -> %%------------------------------------------------------------------------------ -spec call_event(pid(), kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object()) -> 'ok'. call_event(FSM, <<"call_event">>, <<"CHANNEL_DESTROY">>, EvtJObj) -> - gen_fsm:send_event(FSM, {'member_hungup', EvtJObj}); + gen_statem:cast(FSM, {'member_hungup', EvtJObj}); call_event(FSM, <<"call_event">>, <<"DTMF">>, EvtJObj) -> - gen_fsm:send_event(FSM, {'dtmf_pressed', kz_json:get_value(<<"DTMF-Digit">>, EvtJObj)}); + gen_statem:cast(FSM, {'dtmf_pressed', kz_json:get_value(<<"DTMF-Digit">>, EvtJObj)}); call_event(FSM, <<"call_event">>, <<"CHANNEL_BRIDGE">>, EvtJObj) -> - gen_fsm:send_event(FSM, {'channel_bridged', EvtJObj}); + gen_statem:cast(FSM, {'channel_bridged', EvtJObj}); call_event(_, _E, _N, _J) -> 'ok'. -%% lager:debug("unhandled event: ~s: ~s (~s)" -%% ,[_E, _N, kz_json:get_value(<<"Application-Name">>, _J)] -%% ). -spec current_call(pid()) -> kz_term:api_object(). current_call(FSM) -> - gen_fsm:sync_send_event(FSM, 'current_call'). + gen_statem:call(FSM, 'current_call'). -spec status(pid()) -> kz_term:proplist(). status(FSM) -> - gen_fsm:sync_send_event(FSM, 'status'). + gen_statem:call(FSM, 'status'). -spec cdr_url(pid()) -> kz_term:api_binary(). cdr_url(FSM) -> - gen_fsm:sync_send_all_state_event(FSM, 'cdr_url'). - --spec register_callback(pid(), kz_json:object()) -> 'ok'. -register_callback(FSM, JObj) -> - gen_fsm:send_event(FSM, {'register_callback', JObj}). + gen_statem:call(FSM, 'cdr_url'). %%%============================================================================= -%%% gen_fsm callbacks +%%% gen_statem callbacks %%%============================================================================= %%------------------------------------------------------------------------------ %% @private -%% @doc Whenever a gen_fsm is started using gen_fsm:start/[3,4] or -%% gen_fsm:start_link/[3,4], this function is called by the new +%% @doc Whenever a gen_statem is started using gen_statem:start/[3,4] or +%% gen_statem:start_link/[3,4], this function is called by the new %% process to initialize. %% %% @end %%------------------------------------------------------------------------------ -spec init(list()) -> {'ok', atom(), state()}. -init([MgrPid, ListenerPid, QueueJObj]) -> - QueueId = kz_doc:id(QueueJObj), - kz_log:put_callid(<<"fsm_", QueueId/binary, "_", (kz_term:to_binary(self()))/binary>>), +init([WorkerSup, MgrPid, AccountId, QueueId]) -> + kz_log:put_callid(<<"statem_", QueueId/binary, "_", (kz_term:to_binary(self()))/binary>>), - webseq:start(?WSD_ID), + _ = webseq:start(?WSD_ID), webseq:reg_who(?WSD_ID, self(), iolist_to_binary([<<"qFSM">>, pid_to_list(self())])), + AccountDb = kzs_util:format_account_db(AccountId), + {'ok', QueueJObj} = kz_datamgr:open_cache_doc(AccountDb, QueueId), + + gen_statem:cast(self(), {'get_listener_proc', WorkerSup}), {'ok' ,'ready' - ,#state{queue_proc = ListenerPid - ,manager_proc = MgrPid + ,#state{manager_proc = MgrPid ,account_id = kz_doc:account_id(QueueJObj) ,account_db = kz_doc:account_db(QueueJObj) ,queue_id = QueueId @@ -250,8 +253,21 @@ init([MgrPid, ListenerPid, QueueJObj]) -> %% @doc %% @end %%------------------------------------------------------------------------------ --spec ready(any(), state()) -> kz_term:handle_fsm_ret(state()). -ready({'member_call', CallJObj, Delivery}, #state{queue_proc=QueueSrv +-spec callback_mode() -> 'state_functions'. +callback_mode() -> + 'state_functions'. + +%%------------------------------------------------------------------------------ +%% @doc +%% @end +%%------------------------------------------------------------------------------ +-spec ready(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +ready('cast', {'get_listener_proc', WorkerSup}, State) -> + ListenerSrv = acdc_queue_worker_sup:listener(WorkerSup), + lager:debug("got listener proc: ~p", [ListenerSrv]), + {'next_state', 'ready', State#state{listener_proc=ListenerSrv}}; + +ready('cast', {'member_call', CallJObj, Delivery}, #state{listener_proc=ListenerSrv ,manager_proc=MgrSrv }=State) -> Call = kapps_call:from_json(kz_json:get_value(<<"Call">>, CallJObj)), @@ -263,56 +279,71 @@ ready({'member_call', CallJObj, Delivery}, #state{queue_proc=QueueSrv maybe_delay_connect_req(Call, CallJObj, Delivery, State); 'true' -> lager:debug("queue mgr said to ignore this call: ~s", [CallId]), - acdc_queue_listener:ignore_member_call(QueueSrv, Call, Delivery), + acdc_queue_listener:ignore_member_call(ListenerSrv, Call, Delivery), {'next_state', 'ready', State} end; -ready({'agent_resp', _Resp}, State) -> + +ready('cast', {'agent_resp', _Resp}, State) -> lager:debug("someone jumped the gun, or was slow on the draw"), {'next_state', 'ready', State}; -ready({'accepted', _AcceptJObj}, State) -> - lager:debug("weird to receive an acceptance"), + +ready('cast', {'accepted', _AcceptJObj}, State) -> + lager:debug("weird to receive accepted in state 'ready'"), + {'next_state', 'ready', State}; + +ready('cast', {'callback_accepted', _AcceptJObj}, State) -> + lager:debug("weird to receive callback_acceptance in state 'ready'"), {'next_state', 'ready', State}; -ready({'retry', _RetryJObj}, State) -> + +ready('cast', {'retry', _RetryJObj}, State) -> lager:debug("weird to receive a retry when we're just hanging here"), {'next_state', 'ready', State}; -ready({'member_hungup', _CallEvt}, State) -> + +ready('cast', {'member_hungup', _CallEvt}, State) -> lager:debug("member hungup from previous call: ~p", [_CallEvt]), {'next_state', 'ready', State}; -ready({'dtmf_pressed', _DTMF}, State) -> + +ready('cast', {'dtmf_pressed', _DTMF}, State) -> lager:debug("DTMF(~s) for old call", [_DTMF]), {'next_state', 'ready', State}; -ready({'register_callback', JObj}, State) -> - lager:debug("unexpected register_callback for ~s in ready", [kz_json:get_value(<<"Call-ID">>, JObj)]), - {'next_state', 'ready', State}; -ready(_Event, State) -> - lager:debug("unhandled event in ready: ~p", [_Event]), - {'next_state', 'ready', State}. +ready('cast', Event, State) -> + handle_event(Event, ready, State); --spec ready(any(), any(), state()) -> kz_term:handle_sync_event_ret(state()). -ready('status', _, #state{cdr_url=Url +ready({'call', From}, 'status', #state{cdr_url=Url ,recording_url=RecordingUrl }=State) -> - {'reply', [{'state', <<"ready">>} + {'next_state', 'ready', State + ,{'reply', From, [{'state', <<"ready">>} ,{<<"cdr_url">>, Url} ,{<<"recording_url">>, RecordingUrl} - ], 'ready', State}; -ready('current_call', _, State) -> - {'reply', 'undefined', 'ready', State}. + ]}}; + +ready({'call', From}, 'current_call', State) -> + {'next_state', 'ready', State + ,{'reply', From, 'undefined'}}; + +ready({'call', From}, Event, State) -> + handle_sync_event(Event, From, ready, State); + +ready('info', {'timeout', _, ?COLLECT_RESP_MESSAGE}, State) -> + {'next_state', 'ready', State}; +ready('info', Evt, State) -> + handle_info(Evt, 'ready', State). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ --spec connect_req(any(), state()) -> kz_term:handle_fsm_ret(state()). -connect_req({'member_call', CallJObj, Delivery}, #state{queue_proc=Srv}=State) -> +-spec connect_req(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +connect_req('cast', {'member_call', CallJObj, Delivery}, #state{listener_proc=ListenerSrv}=State) -> lager:debug("recv a member_call while processing a different member"), CallId = kz_json:get_value(<<"Call-ID">>, CallJObj), webseq:evt(?WSD_ID, CallId, self(), <<"member call recv while busy">>), - acdc_queue_listener:cancel_member_call(Srv, CallJObj, Delivery), + acdc_queue_listener:cancel_member_call(ListenerSrv, CallJObj, Delivery), {'next_state', 'connect_req', State}; -connect_req({'member_call_cancel', JObj}, #state{queue_proc=Srv +connect_req('cast', {'member_call_cancel', JObj}, #state{listener_proc=ListenerSrv ,account_id=AccountId ,queue_id=QueueId ,member_call=Call @@ -326,13 +357,13 @@ connect_req({'member_call_cancel', JObj}, #state{queue_proc=Srv webseq:evt(?WSD_ID, self(), CallId, <<"member call finish - DTMF">>), - acdc_queue_listener:exit_member_call(Srv), + acdc_queue_listener:exit_member_call(ListenerSrv), _ = acdc_stats:call_abandoned(AccountId, QueueId, CallId, ?ABANDON_EXIT), {'next_state', 'ready', clear_member_call(State), 'hibernate'}; 'false' -> {'next_state', 'connect_req', State} end; -connect_req({'agent_resp', Resp}, #state{connect_resps=CRs +connect_req('cast', {'agent_resp', Resp}, #state{connect_resps=CRs ,manager_proc=MgrSrv }=State) -> Agents = acdc_queue_manager:current_agents(MgrSrv), @@ -344,42 +375,21 @@ connect_req({'agent_resp', Resp}, #state{connect_resps=CRs end, {'next_state', NextState, State1}; -connect_req({'timeout', Ref, ?COLLECT_RESP_MESSAGE}, #state{collect_ref=Ref - ,connect_resps=[] - ,manager_proc=MgrSrv - ,member_call=Call - ,queue_proc=Srv - ,account_id=AccountId - ,queue_id=QueueId - }=State) -> - maybe_stop_timer(Ref), - case acdc_queue_manager:should_ignore_member_call(MgrSrv, Call, AccountId, QueueId) of - 'true' -> - lager:debug("queue mgr said to ignore this call: ~s, not retrying agents", [kapps_call:call_id(Call)]), - acdc_queue_listener:finish_member_call(Srv), - {'next_state', 'ready', clear_member_call(State), 'hibernate'}; - 'false' -> - maybe_connect_re_req(MgrSrv, Srv, State) - end; - -connect_req({'timeout', Ref, ?COLLECT_RESP_MESSAGE}, #state{collect_ref=Ref}=State) -> - {NextState, State1} = handle_agent_responses(State), - {'next_state', NextState, State1}; - -connect_req({'accepted', AcceptJObj}=Accept, #state{member_call=Call}=State) -> +connect_req('cast', {'accepted', AcceptJObj}=Accept, #state{member_call=Call}=State) -> case accept_is_for_call(AcceptJObj, Call) of 'true' -> lager:debug("received acceptance for call ~s: yet to send connect_req though", [kapps_call:call_id(Call)]), - connecting(Accept, State); + connecting('cast', Accept, State); 'false' -> lager:debug("received (and ignoring) acceptance payload"), {'next_state', 'connect_req', State} end; -connect_req({'retry', _RetryJObj}, State) -> + +connect_req('cast', {'retry', _RetryJObj}, State) -> lager:debug("recv retry response before win sent"), {'next_state', 'connect_req', State}; -connect_req({'member_hungup', JObj}, #state{queue_proc=Srv +connect_req('cast', {'member_hungup', JObj}, #state{listener_proc=ListenerSrv ,member_call=Call ,account_id=AccountId ,queue_id=QueueId @@ -391,7 +401,7 @@ connect_req({'member_hungup', JObj}, #state{queue_proc=Srv webseq:evt(?WSD_ID, self(), CallId, <<"member call finish - abandon">>), - acdc_queue_listener:cancel_member_call(Srv, JObj), + acdc_queue_listener:cancel_member_call(ListenerSrv, JObj), _ = acdc_stats:call_abandoned(AccountId, QueueId, CallId, ?ABANDON_HANGUP), {'next_state', 'ready', clear_member_call(State), 'hibernate'}; 'false' -> @@ -401,38 +411,23 @@ connect_req({'member_hungup', JObj}, #state{queue_proc=Srv {'next_state', 'connect_req', State} end; -connect_req({'timeout', ConnRef, ?CONNECTION_TIMEOUT_MESSAGE}, #state{queue_proc=Srv - ,connection_timer_ref=ConnRef - ,account_id=AccountId - ,queue_id=QueueId - ,member_call=Call - }=State) -> - lager:debug("connection timeout occurred, bounce the caller out of the queue"), - CallId = kapps_call:call_id(Call), - webseq:evt(?WSD_ID, self(), CallId, <<"member call finish - timeout">>), - - acdc_queue_listener:timeout_member_call(Srv), - _ = acdc_stats:call_abandoned(AccountId, QueueId, CallId, ?ABANDON_TIMEOUT), - {'next_state', 'ready', clear_member_call(State), 'hibernate'}; - -connect_req({'register_callback', JObj}, #state{connection_timer_ref=ConnRef}=State) -> +connect_req('cast', {'register_callback', JObj}, #state{connection_timer_ref=ConnRef}=State) -> lager:debug("register_callback recv'd for ~s during connect_req", [kz_json:get_value(<<"Call-ID">>, JObj)]), %% disable queue timeout for callback maybe_stop_timer(ConnRef), {'next_state', 'connect_req', State#state{connection_timer_ref='undefined'}}; -connect_req(_Event, State) -> - lager:debug("unhandled event in connect_req: ~p", [_Event]), - {'next_state', 'connect_req', State}. +connect_req('cast', Event, State) -> + handle_event(Event, connect_req, State); --spec connect_req(any(), any(), state()) -> kz_term:handle_sync_event_ret(state()). -connect_req('status', _, #state{member_call=Call +connect_req({call, From}, 'status', #state{member_call=Call ,member_call_start=Start ,connection_timer_ref=ConnRef ,cdr_url=Url ,recording_url=RecordingUrl }=State) -> - {'reply', [{<<"state">>, <<"connect_req">>} + {'next_state', 'connect_req', State + ,{'reply', From, [{<<"state">>, <<"connect_req">>} ,{<<"call_id">>, kapps_call:call_id(Call)} ,{<<"caller_id_name">>, kapps_call:caller_id_name(Call)} ,{<<"caller_id_number">>, kapps_call:caller_id_name(Call)} @@ -442,24 +437,67 @@ connect_req('status', _, #state{member_call=Call ,{<<"wait_time">>, elapsed(Start)} ,{<<"cdr_url">>, Url} ,{<<"recording_url">>, RecordingUrl} - ], 'connect_req', State}; -connect_req('current_call', _, #state{member_call=Call + ]}}; + +connect_req({call, From}, 'current_call', #state{member_call=Call ,member_call_start=Start ,connection_timer_ref=ConnRef }=State) -> - {'reply', current_call(Call, ConnRef, Start), 'connect_req', State}. + {'next_state', 'connect_req', State + ,{'reply', From, current_call(Call, ConnRef, Start)}}; + +connect_req({'call', From}, Event, State) -> + handle_sync_event(Event, From, connect_req, State); + +connect_req('info', {'timeout', Ref, ?COLLECT_RESP_MESSAGE}, #state{collect_ref=Ref + ,connect_resps=[] + ,manager_proc=MgrSrv + ,member_call=Call + ,listener_proc=ListenerSrv + ,account_id=AccountId + ,queue_id=QueueId + }=State) -> + maybe_stop_timer(Ref), + case acdc_queue_manager:should_ignore_member_call(MgrSrv, Call, AccountId, QueueId) of + 'true' -> + lager:debug("queue mgr said to ignore this call: ~s, not retrying agents", [kapps_call:call_id(Call)]), + acdc_queue_listener:finish_member_call(ListenerSrv), + {'next_state', 'ready', clear_member_call(State), 'hibernate'}; + 'false' -> + maybe_connect_re_req(MgrSrv, ListenerSrv, State) + end; + +connect_req('info', {'timeout', Ref, ?COLLECT_RESP_MESSAGE}, #state{collect_ref=Ref}=State) -> + {NextState, State1} = handle_agent_responses(State), + {'next_state', NextState, State1}; + +connect_req('info', {'timeout', ConnRef, ?CONNECTION_TIMEOUT_MESSAGE}, #state{listener_proc=ListenerSrv + ,connection_timer_ref=ConnRef + ,account_id=AccountId + ,queue_id=QueueId + ,member_call=Call + }=State) -> + lager:debug("connection timeout occurred, bounce the caller out of the queue"), + CallId = kapps_call:call_id(Call), + webseq:evt(?WSD_ID, self(), CallId, <<"member call finish - timeout">>), + + acdc_queue_listener:timeout_member_call(ListenerSrv), + _ = acdc_stats:call_abandoned(AccountId, QueueId, CallId, ?ABANDON_TIMEOUT), + {'next_state', 'ready', clear_member_call(State), 'hibernate'}; +connect_req('info', Evt, State) -> + handle_info(Evt, 'connect_req', State). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ --spec connecting(any(), state()) -> kz_term:handle_fsm_ret(state()). -connecting({'member_call', CallJObj, Delivery}, #state{queue_proc=Srv}=State) -> +-spec connecting(gen_statem:event_type(), any(), state()) -> kz_types:handle_fsm_ret(state()). +connecting('cast', {'member_call', CallJObj, Delivery}, #state{listener_proc=ListenerSrv}=State) -> lager:debug("recv a member_call while connecting"), - acdc_queue_listener:cancel_member_call(Srv, CallJObj, Delivery), + acdc_queue_listener:cancel_member_call(ListenerSrv, CallJObj, Delivery), {'next_state', 'connecting', State}; -connecting({'member_call_cancel', JObj}, #state{queue_proc=Srv +connecting('cast', {'member_call_cancel', JObj}, #state{listener_proc=ListenerSrv ,account_id=AccountId ,queue_id=QueueId ,member_call=Call @@ -476,20 +514,20 @@ connecting({'member_call_cancel', JObj}, #state{queue_proc=Srv lists:foreach(fun(Winner) -> lager:debug("sending timeout agent to ~s(~s)", [kz_json:get_value(<<"Agent-ID">>, Winner) ,kz_json:get_value(<<"Process-ID">>, Winner) ]), - acdc_queue_listener:timeout_agent(Srv, Winner) + acdc_queue_listener:timeout_agent(ListenerSrv, Winner) end, Winners), - acdc_queue_listener:exit_member_call(Srv), + acdc_queue_listener:exit_member_call(ListenerSrv), _ = acdc_stats:call_abandoned(AccountId, QueueId, CallId, ?ABANDON_EXIT), {'next_state', 'ready', clear_member_call(State), 'hibernate'}; 'false' -> {'next_state', 'connecting', State} end; -connecting({'agent_resp', _Resp}, State) -> +connecting('cast', {'agent_resp', _Resp}, State) -> lager:debug("agent resp must have just missed cutoff"), {'next_state', 'connecting', State}; -connecting({'accepted', AcceptJObj}, #state{queue_proc=Srv +connecting('cast', {'accepted', AcceptJObj}, #state{listener_proc=ListenerSrv ,connect_wins=Wins ,member_call=Call ,account_id=AccountId @@ -502,10 +540,10 @@ connecting({'accepted', AcceptJObj}, #state{queue_proc=Srv CallId = kapps_call:call_id(Call), webseq:evt(?WSD_ID, self(), CallId, <<"member call - agent acceptance">>), - lists:foreach(fun(Win) -> acdc_queue_listener:member_connect_satisfied(Srv, kz_json:set_value(<<"Accept-Agent-ID">>, AcceptAgentID, Win), []) end, Wins), - %% lists:foreach(fun(Win) -> acdc_queue_listener:member_connect_satisfied(Srv, Win, []) end, Wins), + lists:foreach(fun(Win) -> acdc_queue_listener:member_connect_satisfied(ListenerSrv, kz_json:set_value(<<"Accept-Agent-ID">>, AcceptAgentID, Win), []) end, Wins), + %% lists:foreach(fun(Win) -> acdc_queue_listener:member_connect_satisfied(ListenerSrv, Win, []) end, Wins), - acdc_queue_listener:finish_member_call(Srv, AcceptJObj), + acdc_queue_listener:finish_member_call(ListenerSrv, AcceptJObj), _ = case kz_json:get_value(<<"Old-Call-ID">>, AcceptJObj) of 'undefined' -> acdc_stats:call_handled(AccountId, QueueId, CallId @@ -520,7 +558,7 @@ connecting({'accepted', AcceptJObj}, #state{queue_proc=Srv {'next_state', 'connecting', State} end; -connecting({'callback_accepted', AcceptJObj}, #state{queue_proc=Srv +connecting('cast', {'callback_accepted', AcceptJObj}, #state{listener_proc=ListenerSrv ,connect_wins=Wins ,agent_ring_timer_ref=AgentRef ,member_call=Call @@ -532,7 +570,7 @@ connecting({'callback_accepted', AcceptJObj}, #state{queue_proc=Srv CallId = kapps_call:call_id(Call), webseq:evt(?WSD_ID, self(), CallId, <<"member call - agent callback acceptance">>), - lists:foreach(fun(Win) -> acdc_queue_listener:member_connect_satisfied(Srv, kz_json:set_value(<<"Accept-Agent-ID">>, AcceptAgentID, Win), []) end, Wins), + lists:foreach(fun(Win) -> acdc_queue_listener:member_connect_satisfied(ListenerSrv, kz_json:set_value(<<"Accept-Agent-ID">>, AcceptAgentID, Win), []) end, Wins), %% Do not send timeout to the agent once they've picked up the %% initiating call of the callback @@ -543,7 +581,7 @@ connecting({'callback_accepted', AcceptJObj}, #state{queue_proc=Srv {'next_state', 'connecting', State} end; -connecting({'retry', RetryJObj}, #state{agent_ring_timer_ref=AgentRef +connecting('cast', {'retry', RetryJObj}, #state{agent_ring_timer_ref=AgentRef ,collect_ref=CollectRef ,member_call_winners=Winners }=State) -> @@ -560,7 +598,7 @@ connecting({'retry', RetryJObj}, #state{agent_ring_timer_ref=AgentRef case NewWinners of [] -> lager:debug("recv retry from all of our winning agents"), - gen_fsm:send_event(self(), {'timeout', 'undefined', ?COLLECT_RESP_MESSAGE}), + erlang:send(self(), {'timeout', 'undefined', ?COLLECT_RESP_MESSAGE}), maybe_stop_timer(CollectRef), maybe_stop_timer(AgentRef), webseq:evt(?WSD_ID, webseq:process_pid(RetryJObj), self(), <<"member call - retry">>), @@ -572,44 +610,25 @@ connecting({'retry', RetryJObj}, #state{agent_ring_timer_ref=AgentRef lager:debug("recv retry from agent ~s(~s), removing from Winnners", [RetryAgentId, RetryProcId]), {'next_state', 'connecting', State#state{member_call_winners=NewWinners}} end; -connecting({'timeout', AgentRef, ?AGENT_RING_TIMEOUT_MESSAGE}, #state{agent_ring_timer_ref=AgentRef - ,member_call_winners=Winners - ,queue_proc=Srv - }=State) -> - lager:debug("timed out waiting for agent to pick up"), - lager:debug("let's try another agent"), - gen_fsm:send_event(self(), {'timeout', 'undefined', ?COLLECT_RESP_MESSAGE}), - - lists:foreach(fun(Winner) -> - lager:debug("sending timeout agent to ~s(~s)", [kz_json:get_value(<<"Agent-ID">>, Winner) ,kz_json:get_value(<<"Process-ID">>, Winner) ]), - acdc_queue_listener:timeout_agent(Srv, Winner) - end, - Winners), - {'next_state', 'connect_req', State#state{agent_ring_timer_ref='undefined' - ,member_call_winners='undefined' - }}; -connecting({'timeout', _OtherAgentRef, ?AGENT_RING_TIMEOUT_MESSAGE}, #state{agent_ring_timer_ref=_AgentRef}=State) -> - lager:debug("unknown agent ref: ~p known: ~p", [_OtherAgentRef, _AgentRef]), - {'next_state', 'connect_req', State}; -connecting({'member_hungup', CallEvt}, #state{queue_proc=Srv +connecting('cast', {'member_hungup', CallEvt}, #state{listener_proc=ListenerSrv ,connection_timer_ref='undefined' ,agent_ring_timer_ref='undefined' }=State) -> lager:debug("caller did not answer a callback"), - acdc_queue_listener:finish_member_call(Srv), + acdc_queue_listener:finish_member_call(ListenerSrv), webseq:evt(?WSD_ID, self(), kz_json:get_value(<<"Call-ID">>, CallEvt), <<"member call - hungup">>), {'next_state', 'ready', clear_member_call(State), 'hibernate'}; -connecting({'member_hungup', CallEvt}, #state{queue_proc=Srv +connecting('cast', {'member_hungup', CallEvt}, #state{listener_proc=ListenerSrv ,account_id=AccountId ,queue_id=QueueId ,member_call=Call }=State) -> lager:debug("caller hungup while we waited for the agent to connect"), - acdc_queue_listener:cancel_member_call(Srv, CallEvt), + acdc_queue_listener:cancel_member_call(ListenerSrv, CallEvt), CallId = kapps_call:call_id(Call), _ = acdc_stats:call_abandoned(AccountId, QueueId, CallId, ?ABANDON_HANGUP), @@ -617,27 +636,7 @@ connecting({'member_hungup', CallEvt}, #state{queue_proc=Srv {'next_state', 'ready', clear_member_call(State), 'hibernate'}; -connecting({'timeout', ConnRef, ?CONNECTION_TIMEOUT_MESSAGE}, #state{queue_proc=Srv - ,connection_timer_ref=ConnRef - ,account_id=AccountId - ,queue_id=QueueId - ,member_call=Call - ,member_call_winners=Winners - }=State) -> - lager:debug("connection timeout occurred, bounce the caller out of the queue"), - - lists:foreach(fun(Winner) -> - lager:debug("maybe sending timeout agent to ~s(~s)", [kz_json:get_value(<<"Agent-ID">>, Winner) ,kz_json:get_value(<<"Process-ID">>, Winner) ]), - maybe_timeout_winner(Srv, Winner) - end, - Winners), - - CallId = kapps_call:call_id(Call), - _ = acdc_stats:call_abandoned(AccountId, QueueId, CallId, ?ABANDON_TIMEOUT), - webseq:evt(?WSD_ID, self(), CallId, <<"member call finish - timeout">>), - {'next_state', 'ready', clear_member_call(State), 'hibernate'}; - -connecting({'register_callback', JObj}, #state{queue_proc=Srv +connecting('cast', {'register_callback', JObj}, #state{listener_proc=ListenerSrv ,connection_timer_ref=ConnRef ,agent_ring_timer_ref=AgentRef ,member_call_winners=Winners @@ -646,11 +645,11 @@ connecting({'register_callback', JObj}, #state{queue_proc=Srv %% disable queue timeout for callback maybe_stop_timer(ConnRef), %% cancel agent ringing and do re_req - gen_fsm:send_event(self(), {'timeout', 'undefined', ?COLLECT_RESP_MESSAGE}), + erlang:send(self(), {'timeout', 'undefined', ?COLLECT_RESP_MESSAGE}), maybe_stop_timer(AgentRef), lists:foreach(fun(Winner) -> lager:debug("sending timeout agent to ~s(~s)", [kz_json:get_value(<<"Agent-ID">>, Winner) ,kz_json:get_value(<<"Process-ID">>, Winner) ]), - acdc_queue_listener:timeout_agent(Srv, Winner) + acdc_queue_listener:timeout_agent(ListenerSrv, Winner) end, Winners), @@ -659,19 +658,18 @@ connecting({'register_callback', JObj}, #state{queue_proc=Srv ,member_call_winners='undefined' }}; -connecting(_Event, State) -> - lager:debug("unhandled event in connecting: ~p", [_Event]), - {'next_state', 'connecting', State}. +connecting('cast', Event, State) -> + handle_event(Event, connecting, State); --spec connecting(any(), any(), state()) -> kz_term:handle_sync_event_ret(state()). -connecting('status', _, #state{member_call=Call +connecting({call, From}, 'status', #state{member_call=Call ,member_call_start=Start ,connection_timer_ref=ConnRef ,agent_ring_timer_ref=AgentRef ,cdr_url=Url ,recording_url=RecordingUrl }=State) -> - {'reply', [{<<"state">>, <<"connecting">>} + {'next_state', 'connecting', State + ,{'reply', From, [{<<"state">>, <<"connecting">>} ,{<<"call_id">>, kapps_call:call_id(Call)} ,{<<"caller_id_name">>, kapps_call:caller_id_name(Call)} ,{<<"caller_id_number">>, kapps_call:caller_id_name(Call)} @@ -682,17 +680,65 @@ connecting('status', _, #state{member_call=Call ,{<<"agent_wait_left">>, elapsed(AgentRef)} ,{<<"cdr_url">>, Url} ,{<<"recording_url">>, RecordingUrl} - ], 'connecting', State}; -connecting('current_call', _, #state{member_call=Call + ]}}; + +connecting({call, From}, 'current_call', #state{member_call=Call ,member_call_start=Start ,connection_timer_ref=ConnRef }=State) -> - {'reply', current_call(Call, ConnRef, Start), 'connecting', State}. + {'next_state', 'connecting', State + ,{'reply', From, current_call(Call, ConnRef, Start)}}; + +connecting({'call', From}, Event, State) -> + handle_sync_event(Event, From, connecting, State); + +connecting('info', {'timeout', AgentRef, ?AGENT_RING_TIMEOUT_MESSAGE}, #state{agent_ring_timer_ref=AgentRef + ,member_call_winners=Winners + ,listener_proc=ListenerSrv + }=State) -> + lager:debug("timed out waiting for agent to pick up"), + lager:debug("let's try another agent"), + erlang:send(self(), {'timeout', 'undefined', ?COLLECT_RESP_MESSAGE}), + + lists:foreach(fun(Winner) -> + lager:debug("sending timeout agent to ~s(~s)", [kz_json:get_value(<<"Agent-ID">>, Winner) ,kz_json:get_value(<<"Process-ID">>, Winner) ]), + acdc_queue_listener:timeout_agent(ListenerSrv, Winner) + end, + Winners), + {'next_state', 'connect_req', State#state{agent_ring_timer_ref='undefined' + ,member_call_winners='undefined' + }}; + +connecting('info', {'timeout', _OtherAgentRef, ?AGENT_RING_TIMEOUT_MESSAGE}, #state{agent_ring_timer_ref=_AgentRef}=State) -> + lager:debug("unknown agent ref: ~p known: ~p", [_OtherAgentRef, _AgentRef]), + {'next_state', 'connect_req', State}; + +connecting('info', {'timeout', ConnRef, ?CONNECTION_TIMEOUT_MESSAGE}, #state{listener_proc=ListenerSrv + ,connection_timer_ref=ConnRef + ,account_id=AccountId + ,queue_id=QueueId + ,member_call=Call + ,member_call_winners=Winners + }=State) -> + lager:debug("connection timeout occurred, bounce the caller out of the queue"), + + lists:foreach(fun(Winner) -> + lager:debug("maybe sending timeout agent to ~s(~s)", [kz_json:get_value(<<"Agent-ID">>, Winner) ,kz_json:get_value(<<"Process-ID">>, Winner) ]), + maybe_timeout_winner(ListenerSrv, Winner) + end, + Winners), + + CallId = kapps_call:call_id(Call), + _ = acdc_stats:call_abandoned(AccountId, QueueId, CallId, ?ABANDON_TIMEOUT), + webseq:evt(?WSD_ID, self(), CallId, <<"member call finish - timeout">>), + {'next_state', 'ready', clear_member_call(State), 'hibernate'}; +connecting('info', Evt, State) -> + handle_info(Evt, 'connecting', State). %%------------------------------------------------------------------------------ %% @private -%% @doc Whenever a gen_fsm receives an event sent using -%% gen_fsm:send_all_state_event/2, this function is called to handle +%% @doc Whenever a gen_statem receives an event sent using +%% gen_statem:send_all_state_event/2, this function is called to handle %% the event. %% %% @end @@ -707,39 +753,39 @@ handle_event(_Event, StateName, State) -> %%------------------------------------------------------------------------------ %% @private -%% @doc Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_all_state_event/[2,3], this function is called +%% @doc Whenever a gen_statem receives an event sent using +%% gen_statem:sync_send_all_state_event/[2,3], this function is called %% to handle the event. %% %% @end %%------------------------------------------------------------------------------ --spec handle_sync_event(any(), {pid(),any()}, atom(), state()) -> - kz_types:handle_sync_event_ret(state()). -handle_sync_event('cdr_url', _, StateName, #state{cdr_url=Url}=State) -> - {'reply', Url, StateName, State}; -handle_sync_event(_Event, _From, StateName, State) -> +-spec handle_sync_event(any(), From :: pid(), StateName :: atom(), state()) -> + {'next_state', StateName :: atom(), state() + ,{'reply', From :: pid(), any()}}. +handle_sync_event('cdr_url', From, StateName, #state{cdr_url=Url}=State) -> + {'next_state', StateName, State + ,{'reply', From, Url} + }; +handle_sync_event(_Event, From, StateName, State) -> Reply = 'ok', lager:debug("unhandled sync event in ~s: ~p", [StateName, _Event]), - {'reply', Reply, StateName, State}. + {'next_state', StateName, State + ,{'reply', From, Reply} + }. -%%------------------------------------------------------------------------------ -%% @private -%% @doc This function is called by a gen_fsm when it receives any -%% message other than a synchronous or asynchronous event -%% (or a system message). -%% -%% @end -%%------------------------------------------------------------------------------ -spec handle_info(any(), atom(), state()) -> kz_types:handle_fsm_ret(state()). +handle_info({'member_call', CallJObj, Delivery}, 'ready', State) -> + ?MODULE:member_call(self(), CallJObj, Delivery), + {'next_state', 'ready', State}; handle_info(_Info, StateName, State) -> lager:debug("unhandled message in state ~s: ~p", [StateName, _Info]), {'next_state', StateName, State}. %%------------------------------------------------------------------------------ %% @private -%% @doc This function is called by a gen_fsm when it is about to +%% @doc This function is called by a gen_statem when it is about to %% terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_fsm terminates with +%% necessary cleaning up. When it returns, the gen_statem terminates with %% Reason. The return value is ignored. %% %% @end @@ -767,7 +813,7 @@ code_change(_OldVsn, StateName, State, _Extra) -> %% @end %%------------------------------------------------------------------------------ start_collect_timer() -> - gen_fsm:start_timer(?COLLECT_RESP_TIMEOUT, ?COLLECT_RESP_MESSAGE). + erlang:start_timer(?COLLECT_RESP_TIMEOUT, self(), ?COLLECT_RESP_MESSAGE). -spec connection_timeout(kz_term:api_integer()) -> pos_integer(). connection_timeout(N) when is_integer(N), N > 0 -> N * 1000; @@ -775,7 +821,7 @@ connection_timeout(_) -> ?CONNECTION_TIMEOUT. -spec start_connection_timer(pos_integer()) -> reference(). start_connection_timer(ConnTimeout) -> - gen_fsm:start_timer(ConnTimeout, ?CONNECTION_TIMEOUT_MESSAGE). + erlang:start_timer(ConnTimeout, self(), ?CONNECTION_TIMEOUT_MESSAGE). -spec agent_ring_timeout(kz_term:api_integer()) -> pos_integer(). agent_ring_timeout(N) when is_integer(N), N > 0 -> N; @@ -783,12 +829,12 @@ agent_ring_timeout(_) -> ?AGENT_RING_TIMEOUT. -spec start_agent_ring_timer(pos_integer()) -> reference(). start_agent_ring_timer(AgentTimeout) -> - gen_fsm:start_timer(AgentTimeout * 1600, ?AGENT_RING_TIMEOUT_MESSAGE). + erlang:start_timer(AgentTimeout * 1600, self(), ?AGENT_RING_TIMEOUT_MESSAGE). -spec maybe_stop_timer(kz_term:api_reference()) -> 'ok'. maybe_stop_timer('undefined') -> 'ok'; maybe_stop_timer(ConnRef) -> - _ = gen_fsm:cancel_timer(ConnRef), + _ = erlang:cancel_timer(ConnRef), 'ok'. -spec maybe_timeout_winner(pid(), kz_term:api_object()) -> 'ok'. @@ -867,7 +913,7 @@ elapsed(Time) -> kz_time:elapsed_s(Time). %%------------------------------------------------------------------------------ -spec maybe_delay_connect_req(kapps_call:call(), kz_json:object(), gen_listener:basic_deliver(), state()) -> {'next_state', 'ready' | 'connect_req', state()}. -maybe_delay_connect_req(Call, CallJObj, Delivery, #state{queue_proc=QueueSrv +maybe_delay_connect_req(Call, CallJObj, Delivery, #state{listener_proc=ListenerSrv ,manager_proc=MgrSrv ,connection_timeout=ConnTimeout ,connection_timer_ref=ConnRef @@ -881,7 +927,7 @@ maybe_delay_connect_req(Call, CallJObj, Delivery, #state{queue_proc=QueueSrv webseq:note(?WSD_ID, self(), 'right', [CallId, <<": member call">>]), webseq:evt(?WSD_ID, CallId, self(), <<"member call received">>), - acdc_queue_listener:member_connect_req(QueueSrv, CallJObj, Delivery, Url), + acdc_queue_listener:member_connect_req(ListenerSrv, CallJObj, Delivery, Url), maybe_stop_timer(ConnRef), % stop the old one, maybe @@ -892,7 +938,7 @@ maybe_delay_connect_req(Call, CallJObj, Delivery, #state{queue_proc=QueueSrv }}; 'false' -> lager:debug("connect_req delayed (not up next)"), - gen_fsm:send_event_after(1000, {'member_call', CallJObj, Delivery}), + erlang:send_after(1000, self(), {'member_call', CallJObj, Delivery}), {'next_state', 'ready', State} end. @@ -935,7 +981,7 @@ maybe_delay_connect_re_req(MgrSrv, ListenerSrv, #state{member_call=Call}=State) {'next_state', 'connect_req', State#state{collect_ref=start_collect_timer()}}; 'false' -> lager:debug("connect_re_req delayed (not up next)"), - gen_fsm:send_event_after(1000, {'timeout', 'undefined', ?COLLECT_RESP_MESSAGE}), + erlang:send_after(1000, self(), {'timeout', 'undefined', ?COLLECT_RESP_MESSAGE}), {'next_state', 'connect_req', State#state{collect_ref='undefined'}} end. @@ -952,7 +998,7 @@ update_agent(Agent, Winners) -> -spec handle_agent_responses(state()) -> {atom(), state()}. handle_agent_responses(#state{collect_ref=Ref ,manager_proc=MgrSrv - ,queue_proc=Srv + ,listener_proc=ListenerSrv ,member_call=Call ,account_id=AccountId ,queue_id=QueueId @@ -961,7 +1007,7 @@ handle_agent_responses(#state{collect_ref=Ref case acdc_queue_manager:should_ignore_member_call(MgrSrv, Call, AccountId, QueueId) of 'true' -> lager:debug("queue mgr said to ignore this call: ~s, not connecting to agents", [kapps_call:call_id(Call)]), - acdc_queue_listener:finish_member_call(Srv), + acdc_queue_listener:finish_member_call(ListenerSrv), {'ready', clear_member_call(State)}; 'false' -> lager:debug("done waiting for agents to respond, picking a winner"), @@ -971,7 +1017,7 @@ handle_agent_responses(#state{collect_ref=Ref -spec maybe_pick_winner(state()) -> {atom(), state()}. maybe_pick_winner(#state{connect_resps=CRs - ,queue_proc=Srv + ,listener_proc=ListenerSrv ,manager_proc=Mgr ,member_call=Call ,agent_ring_timeout=RingTimeout @@ -999,7 +1045,7 @@ maybe_pick_winner(#state{connect_resps=CRs lager:debug("sending win to ~s(~s)", [kz_json:get_value(<<"Agent-ID">>, Winner) ,kz_json:get_value(<<"Process-ID">>, Winner) ]), - acdc_queue_listener:member_connect_win(Srv, NewAgent, QueueOpts), + acdc_queue_listener:member_connect_win(ListenerSrv, NewAgent, QueueOpts), [NewAgent|Wins] end, [], Winners), @@ -1011,7 +1057,7 @@ maybe_pick_winner(#state{connect_resps=CRs }}; 'undefined' -> lager:info("no response from the winner"), - {_, NextState, State1} = maybe_connect_re_req(Mgr, Srv, State#state{connect_resps=[]}), + {_, NextState, State1} = maybe_connect_re_req(Mgr, ListenerSrv, State#state{connect_resps=[]}), {NextState, State1} end. diff --git a/applications/acdc/src/acdc_queue_listener.erl b/applications/acdc/src/acdc_queue_listener.erl index a476660ec7b..c077a6e183b 100644 --- a/applications/acdc/src/acdc_queue_listener.erl +++ b/applications/acdc/src/acdc_queue_listener.erl @@ -23,6 +23,7 @@ %% API -export([start_link/4 + ,member_call/3 ,member_connect_req/4 ,member_connect_re_req/1 ,member_connect_win/3 @@ -59,7 +60,7 @@ ,account_id :: kz_term:ne_binary() %% PIDs of the gang - ,worker_sup :: pid() + ,worker_sup :: pid() | undefined ,mgr_pid :: pid() ,fsm_pid :: kz_term:api_pid() ,shared_pid :: kz_term:api_pid() @@ -128,10 +129,15 @@ start_link(WorkerSup, MgrPid, AccountId, QueueId) -> ,[WorkerSup, MgrPid, AccountId, QueueId] ). +-spec member_call(pid(), kz_json:object(), any()) -> 'ok'. +member_call(Srv, MemberCallJObj, Delivery) -> + gen_listener:cast(Srv, {'member_call', MemberCallJObj, Delivery}). + -spec member_connect_req(pid(), kz_json:object(), any(), kz_term:api_binary()) -> 'ok'. member_connect_req(Srv, MemberCallJObj, Delivery, Url) -> gen_listener:cast(Srv, {'member_connect_req', MemberCallJObj, Delivery, Url}). + -spec member_connect_re_req(pid()) -> 'ok'. member_connect_re_req(Srv) -> gen_listener:cast(Srv, {'member_connect_re_req'}). @@ -218,16 +224,14 @@ delivery(Srv) -> init([WorkerSup, MgrPid, AccountId, QueueId]) -> kz_log:put_callid(QueueId), lager:debug("starting queue ~s", [QueueId]), - AccountDb = kzs_util:format_account_db(AccountId), - {'ok', QueueJObj} = kz_datamgr:open_cache_doc(AccountDb, QueueId), - gen_listener:cast(self(), {'start_friends', QueueJObj}), + gen_listener:cast(self(), {'get_friends', WorkerSup}), {'ok', #state{queue_id = QueueId ,account_id = AccountId ,my_id = acdc_util:proc_id() - ,worker_sup = WorkerSup ,mgr_pid = MgrPid }}. + %%------------------------------------------------------------------------------ %% @private %% @doc Handling call messages @@ -251,54 +255,42 @@ handle_call(_Request, _From, State) -> %% %% @end %%------------------------------------------------------------------------------ -find_pid_from_supervisor({'ok', P}) when is_pid(P) -> - {'ok', P}; -find_pid_from_supervisor({'error', {'already_started', P}}) when is_pid(P) -> - {'ok', P}; -find_pid_from_supervisor(E) -> E. - --spec start_shared_queue(state(), pid(), kz_term:api_integer()) -> {'noreply', state()}. -start_shared_queue(#state{account_id=AccountId - ,queue_id=QueueId - ,worker_sup=WorkerSup - }=State, FSMPid, Priority) -> - {'ok', SharedPid} = - find_pid_from_supervisor( - acdc_queue_worker_sup:start_shared_queue(WorkerSup, FSMPid, AccountId, QueueId, Priority) - ), - lager:debug("started shared queue listener: ~p", [SharedPid]), - - {'noreply', State#state{ - fsm_pid = FSMPid - ,shared_pid = SharedPid - ,my_id = acdc_util:proc_id(FSMPid) - }}. +-spec handle_cast(any(), state()) -> kz_types:handle_cast_ret_state(state()). +handle_cast({'get_friends', WorkerSup}, State) -> + FSMPid = acdc_queue_worker_sup:fsm(WorkerSup), + lager:debug("got queue FSM: ~p", [FSMPid]), + SharedPid = acdc_queue_worker_sup:shared_queue(WorkerSup), + lager:debug("got shared queue listener: ~p", [SharedPid]), + {'noreply', State#state{fsm_pid=FSMPid + ,shared_pid=SharedPid + }}; --spec handle_cast(any(), state()) -> kz_term:handle_cast_ret_state(state()). -handle_cast({'start_friends', QueueJObj}, #state{queue_id=QueueId - ,account_id=AccountId - ,worker_sup=WorkerSup - ,mgr_pid=MgrPid - }=State) -> - Priority = acdc_util:max_priority(kzs_util:format_account_db(AccountId), QueueId), - case find_pid_from_supervisor(acdc_queue_worker_sup:start_fsm(WorkerSup, MgrPid, QueueJObj)) of - {'ok', FSMPid} -> - lager:debug("started queue FSM: ~p", [FSMPid]), - start_shared_queue(State, FSMPid, Priority); - {'error', 'already_present'} -> - lager:debug("queue FSM is already present"), - case acdc_queue_worker_sup:fsm(WorkerSup) of - FSMPid when is_pid(FSMPid) -> - lager:debug("found queue FSM pid: ~p", [FSMPid]), - start_shared_queue(State, FSMPid, Priority); - 'undefined' -> - lager:debug("no queue FSM pid found"), - {'stop', 'failed_fsm', State} - end - end; handle_cast({'gen_listener', {'created_queue', Q}}, #state{my_q='undefined'}=State) -> {'noreply', State#state{my_q=Q}, 'hibernate'}; +handle_cast({'member_call', MemberCallJObj, Delivery}, #state{queue_id=QueueId + ,account_id=AccountId + }=State) -> + Call = kapps_call:from_json(kz_json:get_value(<<"Call">>, MemberCallJObj)), + CallId = kapps_call:call_id(Call), + + kz_log:put_callid(CallId), + + acdc_util:bind_to_call_events(Call), + lager:debug("bound to call events for ~s", [CallId]), + + %% Be ready in case a cancel comes in while queue_listener is handling call + gen_listener:add_binding(self(), 'acdc_queue', [{'restrict_to', ['member_call_result']} + ,{'account_id', AccountId} + ,{'queue_id', QueueId} + ,{'callid', CallId} + ]), + + {'noreply', State#state{call=Call + ,delivery=Delivery + ,member_call_queue=kz_json:get_value(<<"Server-ID">>, MemberCallJObj) + }}; + handle_cast({'member_connect_req', MemberCallJObj, Delivery, _Url} ,#state{my_q=MyQ ,my_id=MyId @@ -334,6 +326,8 @@ handle_cast({'member_connect_req', MemberCallJObj, Delivery, _Url} ,member_call_queue=kz_json:get_value(<<"Server-ID">>, MemberCallJObj) } ,'hibernate'}; + + handle_cast({'member_connect_re_req'}, #state{my_q=MyQ ,my_id=MyId ,account_id=AccountId @@ -746,22 +740,18 @@ clear_call_state(#state{call=Call -spec publish(kz_term:api_terms(), kz_amqp_worker:publish_fun()) -> 'ok'. publish(Req, F) -> - case catch F(Req) of - 'ok' -> 'ok'; - {'EXIT', _R} -> - ST = erlang:get_stacktrace(), - lager:debug("failed to publish message: ~p", [_R]), - kz_log:log_stacktrace(ST), - 'ok' - end. + try F(Req) + catch + ?STACKTRACE(_E, _R, ST) + lager:debug("failed to publish message: ~p:~p", [_E, _R]), + kz_log:log_stacktrace(ST) + end. -spec publish(kz_term:ne_binary(), kz_term:api_terms(), fun((kz_term:ne_binary(), kz_term:api_terms()) -> 'ok')) -> 'ok'. publish(Q, Req, F) -> - case catch F(Q, Req) of - 'ok' -> 'ok'; - {'EXIT', _R} -> - ST = erlang:get_stacktrace(), - lager:debug("failed to publish message to ~s: ~p", [Q, _R]), - kz_log:log_stacktrace(ST), - 'ok' - end. + try F(Q, Req) + catch + ?STACKTRACE(_E, _R, ST) + lager:debug("failed to publish message to ~s: ~p:~p", [Q, _E, _R]), + kz_log:log_stacktrace(ST) + end. diff --git a/applications/acdc/src/acdc_queue_manager.erl b/applications/acdc/src/acdc_queue_manager.erl index cc621211803..b0061bb1110 100644 --- a/applications/acdc/src/acdc_queue_manager.erl +++ b/applications/acdc/src/acdc_queue_manager.erl @@ -17,6 +17,8 @@ -module(acdc_queue_manager). -behaviour(gen_listener). +-define(CB_AGENTS_LIST, <<"queues/agents_listing">>). + %% API -export([start_link/2, start_link/3 ,handle_member_call/2 @@ -39,6 +41,7 @@ ,callback_details/2 ]). + %% FSM helpers -export([pick_winner/3]). @@ -349,8 +352,8 @@ init([Super, QueueJObj]) -> init([Super, AccountId, QueueId]) -> kz_log:put_callid(<<"mgr_", QueueId/binary>>), - AcctDb = kzs_util:format_account_db(AccountId), - {'ok', QueueJObj} = kz_datamgr:open_cache_doc(AcctDb, QueueId), + AccountDb = kzs_util:format_account_db(AccountId), + {'ok', QueueJObj} = kz_datamgr:open_cache_doc(AccountDb, QueueId), init(Super, AccountId, QueueId, QueueJObj). @@ -530,20 +533,18 @@ handle_cast({'start_workers'}, #state{account_id=AccountId }=State) -> WorkersSup = acdc_queue_sup:workers_sup(QueueSup), case kz_datamgr:get_results(kzs_util:format_account_db(AccountId) - ,<<"queues/agents_listing">> + ,?CB_AGENTS_LIST ,[{'key', QueueId} - ,'include_docs' + ,{'group', 'true'} + ,{'group_level', 1} ]) of {'ok', []} -> lager:debug("no agents yet, but create a worker anyway"), acdc_queue_workers_sup:new_worker(WorkersSup, AccountId, QueueId); - {'ok', Agents} -> - _ = [start_agent_and_worker(WorkersSup, AccountId, QueueId - ,kz_json:get_value(<<"doc">>, A) - ) - || A <- Agents - ], + {'ok', [Result]} -> + QWC = kz_json:get_integer_value(<<"value">>, Result), + acdc_queue_workers_sup:new_workers(WorkersSup, AccountId, QueueId, QWC), 'ok'; {'error', _E} -> lager:debug("failed to find agent count: ~p", [_E]), @@ -958,22 +959,6 @@ publish_queue_member_remove(AccountId, QueueId, CallId) -> ], kapi_acdc_queue:publish_queue_member_remove(Prop). --spec start_agent_and_worker(pid(), kz_term:ne_binary(), kz_term:ne_binary(), kz_json:object()) -> 'ok'. -start_agent_and_worker(WorkersSup, AccountId, QueueId, AgentJObj) -> - acdc_queue_workers_sup:new_worker(WorkersSup, AccountId, QueueId), - AgentId = kz_doc:id(AgentJObj), - case acdc_agent_util:most_recent_status(AccountId, AgentId) of - {'ok', <<"logout">>} -> 'ok'; - {'ok', <<"logged_out">>} -> 'ok'; - {'ok', _Status} -> - lager:debug("maybe starting agent ~s(~s) for queue ~s", [AgentId, _Status, QueueId]), - - case acdc_agents_sup:find_agent_supervisor(AccountId, AgentId) of - 'undefined' -> acdc_agents_sup:new(AgentJObj); - P when is_pid(P) -> 'ok' - end - end. - %% Really sophisticated selection algorithm -spec pick_winner_(kz_json:objects(), queue_strategy(), kz_term:api_binary()) -> 'undefined' | {kz_json:objects(), kz_json:objects()}. @@ -1412,21 +1397,22 @@ get_strategy(_) -> 'rr'. -spec create_strategy_state(queue_strategy() ,kz_term:ne_binary(), kz_term:ne_binary() ) -> strategy_state(). -create_strategy_state(Strategy, AcctDb, QueueId) -> - create_strategy_state(Strategy, #strategy_state{}, AcctDb, QueueId). +create_strategy_state(Strategy, AccountDb, QueueId) -> + create_strategy_state(Strategy, #strategy_state{}, AccountDb, QueueId). -spec create_strategy_state(queue_strategy() ,strategy_state() ,kz_term:ne_binary(), kz_term:ne_binary() ) -> strategy_state(). -create_strategy_state(S, #strategy_state{agents='undefined'}=SS, AcctDb, QueueId) when S =:= 'rr' +create_strategy_state(S, #strategy_state{agents='undefined'}=SS, AccountDb, QueueId) when S =:= 'rr' orelse S =:= 'all' -> - create_strategy_state(S, SS#strategy_state{agents=pqueue4:new()}, AcctDb, QueueId); -create_strategy_state(S, #strategy_state{agents=AgentQ}=SS, AcctDb, QueueId) when S =:= 'rr' + create_strategy_state(S, SS#strategy_state{agents=pqueue4:new()}, AccountDb, QueueId); +create_strategy_state(S, #strategy_state{agents=AgentQ}=SS, AccountDb, QueueId) when S =:= 'rr' orelse S =:= 'all' -> - case kz_datamgr:get_results(AcctDb, <<"queues/agents_listing">>, [{'key', QueueId}]) of - {'ok', []} -> lager:debug("no agents around"), SS; - {'ok', JObjs} -> + case acdc_util:agents_in_queue(AccountDb, QueueId) of + [] -> lager:debug("no agents around"), SS; + {'error', _E} -> lager:debug("error creating strategy rr: ~p", [_E]), SS; + JObjs -> AgentMap = lists:map(fun(JObj) -> {kz_doc:id(JObj) ,-1 * kz_json:get_integer_value([<<"value">>, <<"agent_priority">>], JObj, 0) @@ -1444,15 +1430,15 @@ create_strategy_state(S, #strategy_state{agents=AgentQ}=SS, AcctDb, QueueId) whe end, dict:new(), JObjs), SS#strategy_state{agents=Q1 ,details=Details - }; - {'error', _E} -> lager:debug("error creating strategy rr: ~p", [_E]), SS + } end; -create_strategy_state('mi', #strategy_state{agents='undefined'}=SS, AcctDb, QueueId) -> - create_strategy_state('mi', SS#strategy_state{agents=[]}, AcctDb, QueueId); -create_strategy_state('mi', #strategy_state{agents=AgentL}=SS, AcctDb, QueueId) -> - case kz_datamgr:get_results(AcctDb, <<"queues/agents_listing">>, [{key, QueueId}]) of - {'ok', []} -> lager:debug("no agents around"), SS; - {'ok', JObjs} -> +create_strategy_state('mi', #strategy_state{agents='undefined'}=SS, AccountDb, QueueId) -> + create_strategy_state('mi', SS#strategy_state{agents=[]}, AccountDb, QueueId); +create_strategy_state('mi', #strategy_state{agents=AgentL}=SS, AccountDb, QueueId) -> + case acdc_util:agents_in_queue(AccountDb, QueueId) of + [] -> lager:debug("no agents around"), SS; + {'error', _E} -> lager:debug("error creating strategy mi: ~p", [_E]), SS; + JObjs -> AgentL1 = lists:foldl(fun(JObj, Acc) -> AgentId = kz_doc:id(JObj), case lists:member(AgentId, Acc) of @@ -1467,21 +1453,20 @@ create_strategy_state('mi', #strategy_state{agents=AgentL}=SS, AcctDb, QueueId) end, dict:new(), JObjs), SS#strategy_state{agents=AgentL1 ,details=Details - }; - {'error', _E} -> lager:debug("error creating strategy mi: ~p", [_E]), SS + } end; -create_strategy_state('sbrr', #strategy_state{agents='undefined'}=SS, AcctDb, QueueId) -> +create_strategy_state('sbrr', #strategy_state{agents='undefined'}=SS, AccountDb, QueueId) -> SBRRStrategyState = #{agent_id_map => #{} ,call_id_map => #{} ,rr_queue => pqueue4:new() ,skill_map => #{} }, - create_strategy_state('sbrr', SS#strategy_state{agents=SBRRStrategyState}, AcctDb, QueueId); -create_strategy_state('sbrr', SS, AcctDb, QueueId) -> - case kz_datamgr:get_results(AcctDb, <<"queues/agents_listing">>, [{'key', QueueId}]) of - {'ok', []} -> lager:debug("no agents around"), SS; - {'ok', JObjs} -> lists:foldl(fun update_sbrrss_with_agent/2, SS, JObjs); - {'error', _E} -> lager:debug("error creating strategy mi: ~p", [_E]), SS + create_strategy_state('sbrr', SS#strategy_state{agents=SBRRStrategyState}, AccountDb, QueueId); +create_strategy_state('sbrr', SS, AccountDb, QueueId) -> + case acdc_util:agents_in_queue(AccountDb, QueueId) of + [] -> lager:debug("no agents around"), SS; + {'error', _E} -> lager:debug("error creating strategy mi: ~p", [_E]), SS; + JObjs -> lists:foldl(fun update_sbrrss_with_agent/2, SS, JObjs) end. update_strategy_state(Srv, S, #strategy_state{agents=AgentQueue}) when S =:= 'rr' diff --git a/applications/acdc/src/acdc_queue_shared.erl b/applications/acdc/src/acdc_queue_shared.erl index cf5dea67cd3..b8bf38d22bf 100644 --- a/applications/acdc/src/acdc_queue_shared.erl +++ b/applications/acdc/src/acdc_queue_shared.erl @@ -33,7 +33,7 @@ -define(SERVER, ?MODULE). --record(state, {fsm_pid :: pid() +-record(state, {fsm_pid :: pid() | undefined ,deliveries = [] :: deliveries() }). -type state() :: #state{}. @@ -69,16 +69,18 @@ %% @end %%------------------------------------------------------------------------------ -spec start_link(kz_term:server_ref(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:api_integer()) -> kz_term:startlink_ret(). -start_link(FSMPid, AccountId, QueueId, Priority) -> +start_link(WorkerSup, _, AccountId, QueueId) -> + Priority = acdc_util:max_priority(kzs_util:format_account_db(AccountId), QueueId), gen_listener:start_link(?SERVER ,[{'bindings', ?SHARED_QUEUE_BINDINGS(AccountId, QueueId)} ,{'responders', ?RESPONDERS} ,{'queue_name', kapi_acdc_queue:shared_queue_name(AccountId, QueueId)} | ?SHARED_BINDING_OPTIONS(Priority) ] - ,[FSMPid] + ,[WorkerSup] ). + -spec ack(kz_term:server_ref(), gen_listener:basic_deliver()) -> 'ok'. ack(Srv, Delivery) -> gen_listener:ack(Srv, Delivery), @@ -104,11 +106,13 @@ deliveries(Srv) -> %% @end %%------------------------------------------------------------------------------ -spec init([pid()]) -> {'ok', state()}. -init([FSMPid]) -> +init([WorkerSup]) -> kz_log:put_callid(?DEFAULT_LOG_SYSTEM_ID), - lager:debug("shared queue proc started, sending messages to FSM ~p", [FSMPid]), - {'ok', #state{fsm_pid=FSMPid}}. + lager:debug("shared queue proc started"), + gen_listener:cast(self(), {'get_fsm_proc', WorkerSup}), + {'ok', #state{}}. + %%------------------------------------------------------------------------------ %% @private @@ -129,6 +133,10 @@ handle_call(_Request, _From, State) -> %% @end %%------------------------------------------------------------------------------ -spec handle_cast(any(), state()) -> kz_term:handle_cast_ret_state(state()). +handle_cast({'get_fsm_proc', WorkerSup}, State) -> + FSMPid = acdc_queue_worker_sup:fsm(WorkerSup), + lager:debug("sending messages to FSM ~p", [FSMPid]), + {'noreply', State#state{fsm_pid=FSMPid}}; handle_cast({'delivery', Delivery}, #state{deliveries=Ds}=State) -> {'noreply', State#state{deliveries=[Delivery|Ds]}}; handle_cast({'ack', Delivery}, #state{deliveries=Ds}=State) -> diff --git a/applications/acdc/src/acdc_queue_worker_sup.erl b/applications/acdc/src/acdc_queue_worker_sup.erl index 4c772b5304f..d9d4d2cde89 100644 --- a/applications/acdc/src/acdc_queue_worker_sup.erl +++ b/applications/acdc/src/acdc_queue_worker_sup.erl @@ -20,15 +20,18 @@ -export([start_link/3 ,stop/1 ,listener/1 - ,shared_queue/1, start_shared_queue/5 - ,fsm/1, start_fsm/3 + ,shared_queue/1 + ,fsm/1 ,status/1 ]). %% Supervisor callbacks -export([init/1]). --define(CHILDREN, [?WORKER_ARGS('acdc_queue_listener', [self() | Args])]). +-define(CHILDREN, [?WORKER_ARGS('acdc_queue_listener', [self() | Args]) + ,?WORKER_ARGS('acdc_queue_shared', [self() | Args]) + ,?WORKER_ARGS('acdc_queue_fsm', [self() | Args]) + ]). %%%============================================================================= %%% API functions @@ -59,10 +62,6 @@ shared_queue(WorkerSup) -> [P] -> P end. --spec start_shared_queue(pid(), pid(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:api_integer()) -> kz_term:sup_startchild_ret(). -start_shared_queue(WorkerSup, FSMPid, AccountId, QueueId, Priority) -> - supervisor:start_child(WorkerSup, ?WORKER_ARGS('acdc_queue_shared', [FSMPid, AccountId, QueueId, Priority])). - -spec fsm(pid()) -> kz_term:api_pid(). fsm(WorkerSup) -> case child_of_type(WorkerSup, 'acdc_queue_fsm') of @@ -70,11 +69,6 @@ fsm(WorkerSup) -> [P] -> P end. --spec start_fsm(pid(), pid(), kz_json:object()) -> kz_term:sup_startchild_ret(). -start_fsm(WorkerSup, MgrPid, QueueJObj) -> - ListenerPid = self(), - supervisor:start_child(WorkerSup, ?WORKER_ARGS('acdc_queue_fsm', [MgrPid, ListenerPid, QueueJObj])). - -spec child_of_type(pid(), atom()) -> [pid()]. child_of_type(WSup, T) -> [P || {Type, P,'worker', [_]} <- supervisor:which_children(WSup), T =:= Type]. diff --git a/applications/acdc/src/acdc_stats.erl b/applications/acdc/src/acdc_stats.erl index 066f11ca8ce..af790886a94 100644 --- a/applications/acdc/src/acdc_stats.erl +++ b/applications/acdc/src/acdc_stats.erl @@ -103,7 +103,7 @@ call_waiting(AccountId, QueueId, CallId, CallerIdName, CallerIdNumber, CallerPri ,{<<"Caller-Priority">>, CallerPriority} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"call_waiting">>, Prop), + call_state_change(AccountId, <<"call_waiting">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_call_waiting/1). -spec call_waiting(kz_term:api_binary() @@ -128,10 +128,10 @@ call_waiting(AccountId, QueueId, Position, CallId, CallerIdName, CallerIdNumber, ,{<<"Required-Skills">>, RequiredSkills} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"call_waiting">>, Prop), + call_state_change(AccountId, <<"call_waiting">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_call_waiting/1). --spec call_abandoned(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. +-spec call_abandoned(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok'. call_abandoned(AccountId, QueueId, CallId, Reason) -> Prop = props:filter_undefined( [{<<"Account-ID">>, AccountId} @@ -141,7 +141,7 @@ call_abandoned(AccountId, QueueId, CallId, Reason) -> ,{<<"Abandon-Timestamp">>, kz_time:current_tstamp()} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"call_abandoned">>, Prop), + call_state_change(AccountId, <<"call_abandoned">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_call_abandoned/1). -spec call_marked_callback(kz_term:ne_binary() @@ -157,7 +157,7 @@ call_marked_callback(AccountId, QueueId, CallId, CallerIdName) -> ,{<<"Caller-ID-Name">>, CallerIdName} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"call_marked_callback">>, Prop), + call_state_change(AccountId, <<"call_marked_callback">>, Prop), kz_amqp_worker:cast( Prop ,fun kapi_acdc_stats:publish_call_marked_callback/1 @@ -173,7 +173,7 @@ call_handled(AccountId, QueueId, CallId, AgentId) -> ,{<<"Handled-Timestamp">>, kz_time:current_tstamp()} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"call_handled">>, Prop), + call_state_change(AccountId, <<"call_handled">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_call_handled/1). -spec call_missed(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. @@ -187,7 +187,7 @@ call_missed(AccountId, QueueId, AgentId, CallId, ErrReason) -> ,{<<"Miss-Timestamp">>, kz_time:current_tstamp()} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"call_missed">>, Prop), + call_state_change(AccountId, <<"call_missed">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_call_missed/1). -spec call_processed(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. @@ -201,7 +201,7 @@ call_processed(AccountId, QueueId, AgentId, CallId, Initiator) -> ,{<<"Hung-Up-By">>, Initiator} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"call_processed">>, Prop), + call_state_change(AccountId, <<"call_processed">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_call_processed/1). -spec agent_ready(kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. @@ -213,7 +213,7 @@ agent_ready(AccountId, AgentId) -> ,{<<"Status">>, <<"ready">>} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"agent_ready">>, Prop), + call_state_change(AccountId, <<"agent_ready">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_status_ready/1). -spec agent_logged_in(kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. @@ -225,7 +225,7 @@ agent_logged_in(AccountId, AgentId) -> ,{<<"Status">>, <<"logged_in">>} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"agent_logged_in">>, Prop), + call_state_change(AccountId, <<"agent_logged_in">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_status_logged_in/1). -spec agent_logged_out(kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. @@ -237,7 +237,7 @@ agent_logged_out(AccountId, AgentId) -> ,{<<"Status">>, <<"logged_out">>} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"agent_logged_out">>, Prop), + call_state_change(AccountId, <<"agent_logged_out">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_status_logged_out/1). -spec agent_connecting(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. @@ -256,7 +256,7 @@ agent_connecting(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber) -> ,{<<"Caller-ID-Number">>, CallerIDNumber} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"agent_connecting">>, Prop), + call_state_change(AccountId, <<"agent_connecting">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_status_connecting/1). -spec agent_connected(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. @@ -275,7 +275,7 @@ agent_connected(AccountId, AgentId, CallId, CallerIDName, CallerIDNumber) -> ,{<<"Caller-ID-Number">>, CallerIDNumber} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"agent_connected">>, Prop), + call_state_change(AccountId, <<"agent_connected">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_status_connected/1). -spec agent_wrapup(kz_term:ne_binary(), kz_term:ne_binary(), pos_integer()) -> 'ok' | {'error', any()}. @@ -288,7 +288,7 @@ agent_wrapup(AccountId, AgentId, WaitTime) -> ,{<<"Wait-Time">>, WaitTime} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"agent_wrapup">>, Prop), + call_state_change(AccountId, <<"agent_wrapup">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_status_wrapup/1). -spec agent_paused(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:api_pos_integer(), kz_term:api_binary()) -> 'ok' | {'error', any()}. @@ -304,7 +304,7 @@ agent_paused(AccountId, AgentId, PauseTime, Alias) -> ,{<<"Pause-Alias">>, Alias} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"agent_paused">>, Prop), + call_state_change(AccountId, <<"agent_paused">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_status_paused/1). -spec agent_outbound(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:ne_binary()) -> 'ok' | {'error', any()}. @@ -317,7 +317,7 @@ agent_outbound(AccountId, AgentId, CallId) -> ,{<<"Call-ID">>, CallId} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), - edr_log_queue_event(AccountId, <<"agent_outbound">>, Prop), + call_state_change(AccountId, <<"agent_outbound">>, Prop), kz_amqp_worker:cast(Prop, fun kapi_acdc_stats:publish_status_outbound/1). -spec agent_statuses() -> kz_term:ne_binaries(). @@ -786,8 +786,9 @@ call_match_builder_fold(<<"Status">>, Status, {CallStat, Contstraints}) -> call_match_builder_fold(<<"Start-Range">>, Start, {CallStat, Contstraints}) -> Now = kz_time:current_tstamp(), Past = Now - ?CLEANUP_WINDOW, + Start1 = acdc_stats_util:apply_query_window_wiggle_room(Start, Past), - try kz_term:to_integer(Start) of + try kz_term:to_integer(Start1) of N when N < Past -> {'error', kz_json:from_list([{<<"Start-Range">>, <<"supplied value is too far in the past">>} ,{<<"Window-Size">>, ?CLEANUP_WINDOW} @@ -809,8 +810,9 @@ call_match_builder_fold(<<"Start-Range">>, Start, {CallStat, Contstraints}) -> call_match_builder_fold(<<"End-Range">>, End, {CallStat, Contstraints}) -> Now = kz_time:current_tstamp(), Past = Now - ?CLEANUP_WINDOW, + End1 = acdc_stats_util:apply_query_window_wiggle_room(End, Past), - try kz_term:to_integer(End) of + try kz_term:to_integer(End1) of N when N < Past -> {'error', kz_json:from_list([{<<"End-Range">>, <<"supplied value is too far in the past">>} ,{<<"Window-Size">>, ?CLEANUP_WINDOW} @@ -1635,9 +1637,12 @@ maybe_send_summary_stat(#call_stat{status=Status}=Stat) edr_log_stats_summary(AccountId, EdrJObj); maybe_send_summary_stat(_) -> 'false'. --spec edr_log_queue_event(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:proplist()) -> 'ok'. -edr_log_queue_event(AccountId, Event, Prop) -> - Body = kz_json:normalize(kz_json:from_list([{<<"Event">>, Event} | Prop])), +-spec call_state_change(kz_term:ne_binary(), kz_term:ne_binary(), kz_term:proplist()) -> 'ok'. +call_state_change(AccountId, Status, Prop) -> + Body = kz_json:normalize(kz_json:from_list([{<<"Event">>, <<"call_status_change">>} + ,{<<"Status">>, Status} + | Prop + ])), kz_edr:event(?APP_NAME, ?APP_VERSION, 'ok', 'info', Body, AccountId). -spec edr_log_stats_summary(kz_term:ne_binary(), kz_term:proplist()) -> 'ok'. diff --git a/applications/acdc/src/acdc_stats.hrl b/applications/acdc/src/acdc_stats.hrl index e02d1eb6fee..b140fb962b3 100644 --- a/applications/acdc/src/acdc_stats.hrl +++ b/applications/acdc/src/acdc_stats.hrl @@ -8,6 +8,9 @@ -define(STATS_QUERY_LIMITS_ENABLED, kapps_config:get_is_true(?CONFIG_CAT, <<"stats_query_limits_enabled">>, 'true')). -define(MAX_RESULT_SET, kapps_config:get_integer(?CONFIG_CAT, <<"max_result_set">>, 25)). +%% Wiggle room for queries in case the AMQP message is delayed a little +-define(QUERY_WINDOW_WIGGLE_ROOM_S, 5). + -record(agent_miss, {agent_id :: kz_term:api_binary() ,miss_reason :: kz_term:api_binary() ,miss_timestamp = kz_time:current_tstamp() :: pos_integer() diff --git a/applications/acdc/src/acdc_stats_util.erl b/applications/acdc/src/acdc_stats_util.erl index 77c263f219f..7f34bbf07e0 100644 --- a/applications/acdc/src/acdc_stats_util.erl +++ b/applications/acdc/src/acdc_stats_util.erl @@ -18,6 +18,7 @@ ,caller_id_number/2 ,get_query_limit/1 + ,apply_query_window_wiggle_room/2 ,db_name/1 ,prev_modb/1 @@ -73,6 +74,22 @@ get_query_limit(JObj, 'false') -> N -> N end. +%%------------------------------------------------------------------------------ +%% @doc If a query timestamp value is less than the minimum permitted by +%% validation, allow a little wiggle room in case the request just took a little +%% while to be processed. +%% @end +%%------------------------------------------------------------------------------ +-spec apply_query_window_wiggle_room(pos_integer(), pos_integer()) -> pos_integer(). +apply_query_window_wiggle_room(Timestamp, Minimum) -> + Offset = Minimum - Timestamp, + WithinWiggleRoom = Offset < ?QUERY_WINDOW_WIGGLE_ROOM_S, + case Offset =< 0 of + 'true' -> Timestamp; + 'false' when WithinWiggleRoom -> Minimum; + 'false' -> Timestamp + end. + -spec db_name(kz_term:ne_binary()) -> kz_term:ne_binary(). db_name(Account) -> kzs_util:format_account_mod_id(Account). diff --git a/applications/acdc/src/acdc_util.erl b/applications/acdc/src/acdc_util.erl index ab61bd24020..357d9491143 100644 --- a/applications/acdc/src/acdc_util.erl +++ b/applications/acdc/src/acdc_util.erl @@ -29,6 +29,8 @@ -include("acdc.hrl"). +-define(CB_AGENTS_LIST, <<"queues/agents_listing">>). + -define(CALL_EVENT_RESTRICTIONS, ['CHANNEL_CREATE' ,'CHANNEL_ANSWER' ,'CHANNEL_BRIDGE', 'CHANNEL_UNBRIDGE' @@ -36,7 +38,6 @@ ,'CHANNEL_DESTROY' ,'DTMF' ,'CHANNEL_EXECUTE_COMPLETE' - ,'PLAYBACK_STOP' ,'usurp_control' ]). @@ -88,12 +89,15 @@ send_cdr(Url, JObj, Retries) -> end. %% Returns the list of agents configured for the queue --spec agents_in_queue(kz_term:ne_binary(), kz_term:ne_binary()) -> kz_json:path(). +-spec agents_in_queue(kz_term:ne_binary(), kz_term:ne_binary()) -> kz_json:objects(). agents_in_queue(AcctDb, QueueId) -> - case kz_datamgr:get_results(AcctDb, <<"queues/agents_listing">>, [{'key', QueueId}]) of - {'ok', []} -> []; + case kz_datamgr:get_results(AcctDb, ?CB_AGENTS_LIST + ,[{'key', QueueId} + ,{'reduce', 'false'} + ]) + of {'error', _E} -> lager:debug("failed to lookup agents for ~s: ~p", [QueueId, _E]), []; - {'ok', As} -> [kz_json:get_value(<<"value">>, A) || A <- As] + {'ok', As} -> As end. -spec agent_devices(kz_term:ne_binary(), kz_term:ne_binary()) -> kz_json:objects(). diff --git a/applications/acdc/src/cb_agents.erl b/applications/acdc/src/cb_agents.erl index 4f41d5470cd..7406df35f27 100644 --- a/applications/acdc/src/cb_agents.erl +++ b/applications/acdc/src/cb_agents.erl @@ -306,31 +306,16 @@ publish_restart(Context, AgentId) -> %%------------------------------------------------------------------------------ -spec read(kz_term:api_binary(), cb_context:context()) -> cb_context:context(). read(UserId, Context) -> - case kz_datamgr:open_cache_doc(cb_context:db_name(Context), UserId) of - {'ok', Doc} -> validate_user_id(UserId, Context, Doc); - {'error', 'not_found'} -> - cb_context:add_system_error('bad_identifier' - ,kz_json:from_list([{<<"cause">>, UserId}]) - ,Context - ); - {'error', _R} -> crossbar_util:response_db_fatal(Context) - end. - --spec validate_user_id(kz_term:api_binary(), cb_context:context(), kz_json:object()) -> cb_context:context(). -validate_user_id(UserId, Context, Doc) -> - case kz_doc:is_soft_deleted(Doc) of - 'true' -> - Msg = kz_json:from_list([{<<"cause">>, UserId}]), - cb_context:add_system_error('bad_identifier', Msg, Context); - 'false'-> - cb_context:setters(Context - ,[{fun cb_context:set_user_id/2, UserId} - ,{fun cb_context:set_doc/2, Doc} - ,{fun cb_context:set_resp_status/2, 'success'} - ]) + Context1 = crossbar_doc:load(UserId, Context, ?TYPE_CHECK_OPTION(kzd_users:type())), + case cb_context:resp_status(Context1) of + 'success' -> + cb_context:setters(Context1 + ,[{fun cb_context:set_user_id/2, UserId} + ,{fun cb_context:set_resp_status/2, 'success'} + ]); + _Status -> Context1 end. - -define(CB_AGENTS_LIST, <<"users/crossbar_listing">>). -spec fetch_all_agent_statuses(cb_context:context()) -> cb_context:context(). fetch_all_agent_statuses(Context) -> @@ -392,12 +377,12 @@ fetch_all_current_agent_stats(Context) -> -spec fetch_all_current_stats(cb_context:context(), kz_term:api_binary()) -> cb_context:context(). fetch_all_current_stats(Context, AgentId) -> Now = kz_time:current_tstamp(), - Yday = Now - ?SECONDS_IN_DAY, + From = Now - min(?SECONDS_IN_DAY, ?ACDC_CLEANUP_WINDOW), Req = props:filter_undefined( [{<<"Account-ID">>, cb_context:account_id(Context)} ,{<<"Agent-ID">>, AgentId} - ,{<<"Start-Range">>, Yday} + ,{<<"Start-Range">>, From} ,{<<"End-Range">>, Now} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), @@ -626,7 +611,23 @@ add_miss(Miss, Acc, QueueId) -> %%------------------------------------------------------------------------------ -spec summary(cb_context:context()) -> cb_context:context(). summary(Context) -> - crossbar_view:load(Context, ?CB_LIST ,[{'mapper', crossbar_view:get_value_fun()}]). +%% crossbar_view:load(Context, ?CB_LIST ,[{'mapper', crossbar_view:get_value_fun()}]). +crossbar_view:load(Context, ?CB_LIST ,[{'mapper', fun normalize_view_results/2}]). + +%%------------------------------------------------------------------------------ +%% @private +%% @doc Normalizes the resuts of a view +%% @end +%%------------------------------------------------------------------------------ +-spec normalize_view_results(kz_json:object(), kz_json:objects()) -> + kz_json:objects(). +normalize_view_results(JObj, Acc) -> + [kz_json:set_value(<<"id">> + ,kz_doc:id(JObj) + ,kz_json:get_value(<<"value">>, JObj) + ) + | Acc + ]. -spec validate_status_change(cb_context:context()) -> cb_context:context(). validate_status_change(Context) -> diff --git a/applications/acdc/src/cb_queues.erl b/applications/acdc/src/cb_queues.erl index b8dae31cb92..1ecb603a8c9 100644 --- a/applications/acdc/src/cb_queues.erl +++ b/applications/acdc/src/cb_queues.erl @@ -60,7 +60,7 @@ -define(MOD_CONFIG_CAT, <<(?CONFIG_CAT)/binary, ".queues">>). -define(CB_LIST, <<"queues/crossbar_listing">>). --define(CB_AGENTS_LIST, <<"queues/agents_listing">>). %{agent_id, queue_id} +-define(CB_AGENTS_LIST, <<"queues/agents_listing">>). -define(STATS_PATH_TOKEN, <<"stats">>). -define(STATS_SUMMARY_PATH_TOKEN, <<"stats_summary">>). @@ -183,7 +183,8 @@ content_types_provided(Context, ?STATS_PATH_TOKEN) -> ,[{'to_json', ?JSON_CONTENT_TYPES} ,{'to_csv', ?CSV_CONTENT_TYPES} ]); -content_types_provided(Context, ?STATS_SUMMARY_PATH_TOKEN) -> Context. +content_types_provided(Context, ?STATS_SUMMARY_PATH_TOKEN) -> Context; +content_types_provided(Context, _) -> Context. %%------------------------------------------------------------------------------ %% @doc Check the request (request body, query string params, path tokens, etc) @@ -570,7 +571,9 @@ load_queue_agents(Id, Context) -> end. load_agent_roster(Id, Context) -> - crossbar_view:load(Context, ?CB_AGENTS_LIST, [{'key', Id},{'mapper', crossbar_view:get_value_fun()}]). + crossbar_view:load(Context, ?CB_AGENTS_LIST, [{'key', Id} + ,{'reduce', 'false'} + ,{'mapper', fun normalize_agents_results/2}]). add_queue_to_agents(Id, Context) -> add_queue_to_agents(Id, Context, cb_context:req_data(Context)). @@ -807,10 +810,15 @@ fetch_ranged_queue_stats(Context, From, To, 'false') -> -spec fetch_all_current_queue_stats(cb_context:context()) -> cb_context:context(). fetch_all_current_queue_stats(Context) -> lager:debug("querying for all recent stats"), + Now = kz_time:now_s(), + From = Now - min(?SECONDS_IN_DAY, ?ACDC_CLEANUP_WINDOW), + Req = props:filter_undefined( [{<<"Account-ID">>, cb_context:account_id(Context)} ,{<<"Status">>, cb_context:req_value(Context, <<"status">>)} ,{<<"Agent-ID">>, cb_context:req_value(Context, <<"agent_id">>)} + ,{<<"Start-Range">>, From} + ,{<<"End-Range">>, Now} | kz_api:default_headers(?APP_NAME, ?APP_VERSION) ]), fetch_from_amqp(Context, Req). @@ -854,6 +862,9 @@ fetch_from_amqp(Context, Req) -> summary(Context) -> crossbar_view:load(Context, ?CB_LIST ,[{'mapper', crossbar_view:get_value_fun()}]). +normalize_agents_results(JObj, Acc) -> + [kz_doc:id(JObj) | Acc]. + %%------------------------------------------------------------------------------ %% @private %% @doc Creates an entry in the acdc db of the account's participation in acdc diff --git a/applications/acdc/src/cf_acdc_member.erl b/applications/acdc/src/cf_acdc_member.erl index 15acee5cd84..9097c2153b8 100644 --- a/applications/acdc/src/cf_acdc_member.erl +++ b/applications/acdc/src/cf_acdc_member.erl @@ -38,7 +38,6 @@ ,config_data = [] :: kz_term:proplist() ,breakout_media :: kz_term:api_object() ,max_wait = 60 * ?MILLISECONDS_IN_SECOND :: max_wait() - ,silence_noop :: kz_term:api_binary() ,enter_as_callback :: boolean() ,queue_jobj :: kz_json:object() }). @@ -148,15 +147,15 @@ maybe_enter_queue(#member_call{call=Call maybe_enter_queue(#member_call{call=Call ,queue_id=QueueId ,max_wait=MaxWait + ,config_data=MemberCall }=MC ,'false') -> case kapps_call_command:b_channel_status(kapps_call:call_id(Call)) of {'ok', _} -> lager:info("asking for an agent, waiting up to ~p ms", [MaxWait]), - - NoopId = kapps_call_command:flush_dtmf(Call), + cf_exe:amqp_send(Call, MemberCall, fun kapi_acdc_queue:publish_member_call/1), + _ = kapps_call_command:flush_dtmf(Call), wait_for_bridge(MC#member_call{call=kapps_call:kvs_store('queue_id', QueueId, Call) - ,silence_noop=NoopId } ,#breakout_state{} ,MaxWait @@ -241,18 +240,6 @@ process_message(#member_call{call=Call}, _, _, Start, _Wait, _JObj, {<<"call_eve lager:info("member hungup while waiting in the queue (was there ~b s)", [kz_time:elapsed_s(Start)]), cancel_member_call(Call, ?MEMBER_HANGUP), cf_exe:stop(Call); -process_message(#member_call{call=Call - ,config_data=MemberCall - ,silence_noop=NoopId - }=MC, BreakoutState, Timeout, Start, Wait, JObj, {<<"call_event">>,<<"CHANNEL_EXECUTE_COMPLETE">>}) -> - case kz_json:get_first_defined([<<"Application-Name">> - ,[<<"Request">>, <<"Application-Name">>] - ], JObj) =:= <<"noop">> - andalso kz_json:get_value(<<"Application-Response">>, JObj) =:= NoopId of - 'true' -> cf_exe:amqp_send(Call, MemberCall, fun kapi_acdc_queue:publish_member_call/1); - 'false' -> 'ok' - end, - wait_for_bridge(MC, BreakoutState, kz_time:decr_timeout(Timeout, Wait), Start); process_message(#member_call{call=Call ,queue_id=QueueId }=MC, BreakoutState, Timeout, Start, Wait, JObj, {<<"member">>, <<"call_fail">>}) -> diff --git a/applications/acdc/src/kapi_acdc_stats.erl b/applications/acdc/src/kapi_acdc_stats.erl index 2c249316833..5522425e11f 100644 --- a/applications/acdc/src/kapi_acdc_stats.erl +++ b/applications/acdc/src/kapi_acdc_stats.erl @@ -980,21 +980,21 @@ unbind_q(Q, Props) -> unbind_q(Q, AccountId, QID, AID, props:get_value('restrict_to', Props)). unbind_q(Q, AccountId, QID, AID, 'undefined') -> - kz_amqp_util:unbind_q_from_kapps(Q, call_stat_routing_key(AccountId, QID)), - kz_amqp_util:unbind_q_from_kapps(Q, status_stat_routing_key(AccountId, AID)), - kz_amqp_util:unbind_q_from_kapps(Q, query_call_stat_routing_key(AccountId, QID)), + _ = kz_amqp_util:unbind_q_from_kapps(Q, call_stat_routing_key(AccountId, QID)), + _ = kz_amqp_util:unbind_q_from_kapps(Q, status_stat_routing_key(AccountId, AID)), + _ = kz_amqp_util:unbind_q_from_kapps(Q, query_call_stat_routing_key(AccountId, QID)), kz_amqp_util:unbind_q_from_kapps(Q, query_status_stat_routing_key(AccountId, AID)); unbind_q(Q, AccountId, QID, AID, ['call_stat'|L]) -> - kz_amqp_util:unbind_q_from_kapps(Q, call_stat_routing_key(AccountId, QID)), + _ = kz_amqp_util:unbind_q_from_kapps(Q, call_stat_routing_key(AccountId, QID)), unbind_q(Q, AccountId, QID, AID, L); unbind_q(Q, AccountId, QID, AID, ['status_stat'|L]) -> - kz_amqp_util:unbind_q_from_kapps(Q, status_stat_routing_key(AccountId, AID)), + _ = kz_amqp_util:unbind_q_from_kapps(Q, status_stat_routing_key(AccountId, AID)), unbind_q(Q, AccountId, QID, AID, L); unbind_q(Q, AccountId, QID, AID, ['query_call_stat'|L]) -> - kz_amqp_util:unbind_q_from_kapps(Q, query_call_stat_routing_key(AccountId, QID)), + _ = kz_amqp_util:unbind_q_from_kapps(Q, query_call_stat_routing_key(AccountId, QID)), unbind_q(Q, AccountId, QID, AID, L); unbind_q(Q, AccountId, QID, AID, ['query_status_stat'|L]) -> - kz_amqp_util:unbind_q_from_kapps(Q, query_status_stat_routing_key(AccountId, AID)), + _ = kz_amqp_util:unbind_q_from_kapps(Q, query_status_stat_routing_key(AccountId, AID)), unbind_q(Q, AccountId, QID, AID, L); unbind_q(Q, AccountId, QID, AID, [_|L]) -> unbind_q(Q, AccountId, QID, AID, L); diff --git a/applications/ecallmgr/priv/mod_kazoo/event_profiles/default.xml b/applications/ecallmgr/priv/mod_kazoo/event_profiles/default.xml index 6de2b6d52a4..1a0deb13539 100644 --- a/applications/ecallmgr/priv/mod_kazoo/event_profiles/default.xml +++ b/applications/ecallmgr/priv/mod_kazoo/event_profiles/default.xml @@ -14,8 +14,6 @@ - - diff --git a/applications/ecallmgr/priv/mod_kazoo/events/PLAYBACK_START.xml b/applications/ecallmgr/priv/mod_kazoo/events/PLAYBACK_START.xml deleted file mode 100644 index 2b8f4d0e24f..00000000000 --- a/applications/ecallmgr/priv/mod_kazoo/events/PLAYBACK_START.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/applications/ecallmgr/priv/mod_kazoo/events/PLAYBACK_STOP.xml b/applications/ecallmgr/priv/mod_kazoo/events/PLAYBACK_STOP.xml deleted file mode 100644 index 51b6b539d17..00000000000 --- a/applications/ecallmgr/priv/mod_kazoo/events/PLAYBACK_STOP.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/applications/ecallmgr/src/ecallmgr.hrl b/applications/ecallmgr/src/ecallmgr.hrl index 84a6150d0d0..2fab354ddf8 100644 --- a/applications/ecallmgr/src/ecallmgr.hrl +++ b/applications/ecallmgr/src/ecallmgr.hrl @@ -450,7 +450,6 @@ ,{'bridge', ['CHANNEL_BRIDGE', 'CHANNEL_UNBRIDGE']} ,{'media', ['DETECTED_TONE', 'DTMF','CHANNEL_PROGRESS','CHANNEL_PROGRESS_MEDIA']} ,{'record', ['RECORD_START', 'RECORD_STOP']} - ,{'playback', ['PLAYBACK_STOP']} ,{'callflow', ['ROUTE_WINNER', 'CHANNEL_EXECUTE_COMPLETE', 'CHANNEL_METAFLOW']} ,{'presence', ['PRESENCE_IN']} ,{'channel_full_update', ['CHANNEL_DATA','CHANNEL_SYNC','CALL_UPDATE']} diff --git a/applications/ecallmgr/src/ecallmgr_originate.erl b/applications/ecallmgr/src/ecallmgr_originate.erl index efb9dfde321..140fe6039b8 100644 --- a/applications/ecallmgr/src/ecallmgr_originate.erl +++ b/applications/ecallmgr/src/ecallmgr_originate.erl @@ -217,6 +217,7 @@ handle_cast({'build_originate_args'}, #state{originate_req=JObj ,action = ?ORIGINATE_PARK ,fetch_id=FetchId ,dialstrings='undefined' + ,control_pid = CtrlPid }=State) -> case kz_json:is_true(<<"Originate-Immediate">>, JObj) of 'true' -> gen_listener:cast(self(), {'originate_execute'}); @@ -225,7 +226,7 @@ handle_cast({'build_originate_args'}, #state{originate_req=JObj Endpoints = [update_endpoint(Endpoint, State) || Endpoint <- kz_json:get_ne_value(<<"Endpoints">>, JObj, []) ], - {'noreply', State#state{dialstrings=build_originate_args_from_endpoints(?ORIGINATE_PARK, Endpoints, JObj, FetchId)}}; + {'noreply', State#state{dialstrings=build_originate_args_from_endpoints(?ORIGINATE_PARK, Endpoints, JObj, FetchId, CtrlPid)}}; handle_cast({'build_originate_args'}, #state{originate_req=JObj ,action = Action ,app = ?ORIGINATE_EAVESDROP @@ -496,35 +497,37 @@ get_eavesdrop_action(JObj) -> end. -spec build_originate_args(kz_term:ne_binary(), state(), kz_json:object(), kz_term:ne_binary()) -> kz_term:api_binary(). -build_originate_args(Action, State, JObj, FetchId) -> +build_originate_args(Action, #state{control_pid=CtrlPid} = State, JObj, FetchId) -> case kz_json:get_value(<<"Endpoints">>, JObj, []) of [] -> lager:warning("no endpoints defined in originate request"), 'undefined'; [Endpoint] -> lager:debug("only one endpoint, don't create per-endpoint UUIDs"), - build_originate_args_from_endpoints(Action, [update_endpoint(Endpoint, State)], JObj, FetchId); + build_originate_args_from_endpoints(Action, [update_endpoint(Endpoint, State)], JObj, FetchId, CtrlPid); Endpoints -> lager:debug("multiple endpoints defined, assigning uuids to each"), UpdatedEndpoints = [update_endpoint(Endpoint, State) || Endpoint <- Endpoints], - build_originate_args_from_endpoints(Action, UpdatedEndpoints, JObj, FetchId) + build_originate_args_from_endpoints(Action, UpdatedEndpoints, JObj, FetchId, CtrlPid) end. --spec build_originate_args_from_endpoints(kz_term:ne_binary(), kz_json:objects(), kz_json:object(), kz_term:ne_binary()) -> +-spec build_originate_args_from_endpoints(kz_term:ne_binary(), kz_json:objects(), kz_json:object(), kz_term:ne_binary(), kz_term:ne_binary()) -> kz_term:ne_binary(). -build_originate_args_from_endpoints(Action, Endpoints, JObj, FetchId) -> +build_originate_args_from_endpoints(Action, Endpoints, JObj, FetchId, CtrlPid) -> lager:debug("building originate command arguments"), DialSeparator = ecallmgr_util:get_dial_separator(JObj, Endpoints), DialStrings = ecallmgr_util:build_bridge_string(Endpoints, DialSeparator), ChannelVars = get_channel_vars(JObj, FetchId), + CtrlQ = ecallmgr_call_control:queue_name(CtrlPid), - list_to_binary([ChannelVars, DialStrings, " ", Action]). + list_to_binary([ChannelVars, "[^^!Call-Control-Queue='", CtrlQ, "']", DialStrings, " ", Action]). -spec get_channel_vars(kz_json:object(), kz_term:ne_binary()) -> iolist(). get_channel_vars(JObj, FetchId) -> InteractionId = kz_json:get_value([<<"Custom-Channel-Vars">>, <>], JObj, ?CALL_INTERACTION_DEFAULT), + CCVs = [{<<"Fetch-ID">>, FetchId} ,{<<"Ecallmgr-Node">>, kz_term:to_binary(node())} ,{<>, InteractionId}