Author: rmudgett Date: Wed Jun 26 16:57:43 2013 New Revision: 393023 URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=393023 Log: Fill in AgentRequest and more.
Modified: team/rmudgett/bridge_phase/apps/app_agent_pool.c Modified: team/rmudgett/bridge_phase/apps/app_agent_pool.c URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_phase/apps/app_agent_pool.c?view=diff&rev=393023&r1=393022&r2=393023 ============================================================================== --- team/rmudgett/bridge_phase/apps/app_agent_pool.c (original) +++ team/rmudgett/bridge_phase/apps/app_agent_pool.c Wed Jun 26 16:57:43 2013 @@ -66,12 +66,20 @@ </parameter> </syntax> <description> - <para>Login an agent to the system. Any agent authentication is assumed to - already be done by dialplan. If the agent is already logged in, the - application will continue in the dialplan with <variable>AGENT_STATUS</variable> set - to <literal>ALREADY_LOGGED_IN</literal>. - While logged in, the agent can receive calls and will hear a <literal>beep</literal> - when a new call comes in.</para> + <para> + Login an agent to the system. Any agent authentication is assumed to + already be done by dialplan. While logged in, the agent can receive calls + and will hear a configurable <literal>beep</literal> sound when a new call + comes in for the agent. Login failures will continue in the dialplan + with AGENT_STATUS set. + </para> + <para> + AGENT_STATUS enumeration values: + </para> + <enumlist> + <enum name = "NOT_EXIST"><para>The specified agent is invalid.</para></enum> + <enum name = "ALREADY_LOGGED_IN"><para>The agent is already logged in.</para></enum> + </enumlist> </description> <see-also> <ref type="application">Authenticate</ref> @@ -92,10 +100,21 @@ </synopsis> <syntax argsep=","> <parameter name="AgentId" required="true" /> - <parameter name="timeout"> - <para>Specifies the number of seconds to wait for an available agent.</para> - </parameter> </syntax> + <description> + <para> + Request an agent to connect with the channel. + </para> + <para> + AGENT_STATUS enumeration values for this application when it continues + in the dialplan: + </para> + <enumlist> + <enum name = "NOT_EXIST"><para>The specified agent is invalid.</para></enum> + <enum name = "NOT_LOGGED_IN"><para>The agent is not available.</para></enum> + <enum name = "BUSY"><para>The agent is on another call.</para></enum> + </enumlist> + </description> <see-also> <ref type="application">AgentLogin</ref> </see-also> @@ -195,6 +214,9 @@ #define AST_MAX_BUF 256 +/*! Maximum wait time (in ms) for the custom_beep file to play announcing the caller. */ +#define CALLER_SAFETY_TIMEOUT_TIME (2 * 60 * 1000) + static const char app_agent_login[] = "AgentLogin"; static const char app_agent_request[] = "AgentRequest"; @@ -447,7 +469,7 @@ return -1; } - ast_string_field_set(cfg, beep_sound, ""); + ast_string_field_set(cfg, beep_sound, var->value); return 0; } @@ -473,7 +495,7 @@ aco_option_register(&cfg_info, "recordformat", ACO_EXACT, agent_types, "wav", OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, record_format)); aco_option_register_custom(&cfg_info, "savecallsin", ACO_EXACT, agent_types, "", agent_savecallsin_handler, 0); aco_option_register_custom(&cfg_info, "custom_beep", ACO_EXACT, agent_types, "beep", agent_custom_beep_handler, 0); - aco_option_register(&cfg_info, "fullname", ACO_EXACT, agent_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, full_name)); + aco_option_register(&cfg_info, "fullname", ACO_EXACT, agent_types, NULL, OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, full_name)); if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) { goto error; @@ -910,6 +932,7 @@ { // struct agent_pvt *agent = hook_pvt; + /* Connect to caller now. */ /*! \todo BUGBUG bridge_agent_hold_ack() not written */ return 0; } @@ -1060,11 +1083,8 @@ ast_channel_unref(agent->logged); agent->logged = ast_channel_ref(chan); agent_unlock(agent); - } - - ast_assert(bridge_channel->bridge_pvt == NULL); - ao2_ref(agent, +1); - bridge_channel->bridge_pvt = agent; + return 0; + } agent_lock(agent); switch (agent->state) { @@ -1076,7 +1096,27 @@ ast_debug(1, "Agent %s: Login complete.\n", agent->username); agent_devstate_changed(agent->username); break; + case AGENT_STATE_READY_FOR_CALL: + /* + * Likely someone manally kicked us out of the holding bridge + * and we came right back in. + */ + agent_unlock(agent); + break; default: + /* Unexpected agent state. */ + ast_assert(0); + /* Fall through */ + case AGENT_STATE_CALL_PRESENT: + case AGENT_STATE_CALL_WAIT_ACK: + agent->state = AGENT_STATE_READY_FOR_CALL; + agent->devstate = AST_DEVICE_NOT_INUSE; + agent_unlock(agent); + ast_debug(1, "Agent %s: Call abort recovery complete.\n", agent->username); + agent_devstate_changed(agent->username); + break; + case AGENT_STATE_ON_CALL: + case AGENT_STATE_CALL_WRAPUP: wrapup_time = agent->cfg->wrapup_time; if (ast_test_flag(agent, AGENT_FLAG_WRAPUP_TIME)) { wrapup_time = agent->override_wrapup_time; @@ -1116,12 +1156,7 @@ */ static void bridge_agent_hold_pull(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel) { - struct agent_pvt *agent = bridge_channel->bridge_pvt; - ast_channel_remove_bridge_role(bridge_channel->chan, "holding_participant"); - - ao2_cleanup(agent); - bridge_channel->bridge_pvt = NULL; } /*! @@ -1287,6 +1322,7 @@ caller_bridge = NULL; agent->state = AGENT_STATE_LOGGED_OUT; agent->devstate = AST_DEVICE_UNAVAILABLE; + ast_clear_flag(agent, AST_FLAGS_ALL); agent_unlock(agent); agent_devstate_changed(agent->username); @@ -1324,6 +1360,7 @@ struct agent_cfg *cfg_new; struct agent_cfg *cfg_old; struct ast_bridge *holding; + struct ast_bridge *caller_bridge; holding = ao2_global_obj_ref(agent_holding); if (!holding) { @@ -1358,8 +1395,17 @@ agent_lock(agent); cfg_old = agent->cfg; agent->cfg = cfg_new; + + agent->last_disconnect = ast_tvnow(); + + /* Clear out any caller bridge before rejoining the holding bridge. */ + caller_bridge = agent->caller_bridge; + agent->caller_bridge = NULL; agent_unlock(agent); - ao2_cleanup(cfg_old); + ao2_ref(cfg_old, -1); + if (caller_bridge) { + ast_bridge_destroy(caller_bridge); + } if (agent->state == AGENT_STATE_LOGGING_OUT || agent->deferred_logoff @@ -1375,9 +1421,154 @@ } /*! - * Called by the AgentRequest application (from the dial plan). - * - * \brief Application to locate an agent to talk with. + * \internal + * \brief Get the lock on the agent bridge_channel and return it. + * \since 12.0.0 + * + * \param agent Whose bridge_chanel to get. + * + * \retval bridge_channel on success (Reffed and locked). + * \retval NULL on error. + */ +static struct ast_bridge_channel *agent_bridge_channel_get_lock(struct agent_pvt *agent) +{ + struct ast_channel *logged; + struct ast_bridge_channel *bc; + + for (;;) { + agent_lock(agent); + logged = agent->logged; + if (!logged) { + agent_unlock(agent); + return NULL; + } + ast_channel_ref(logged); + agent_unlock(agent); + + ast_channel_lock(logged); + bc = ast_channel_get_bridge_channel(logged); + ast_channel_unlock(logged); + ast_channel_unref(logged); + if (!bc) { + if (agent->logged != logged) { + continue; + } + return NULL; + } + + ast_bridge_channel_lock(bc); + if (bc->chan != logged || agent->logged != logged) { + ast_bridge_channel_unlock(bc); + ao2_ref(bc, -1); + continue; + } + return bc; + } +} + +static void caller_abort_agent(struct agent_pvt *agent) +{ + struct ast_bridge_channel *logged; + + logged = agent_bridge_channel_get_lock(agent); + if (!logged) { + struct ast_bridge *caller_bridge; + + ast_debug(1, "Agent '%s' no longer logged in.\n", agent->username); + + agent_lock(agent); + caller_bridge = agent->caller_bridge; + agent->caller_bridge = NULL; + agent_unlock(agent); + if (caller_bridge) { + ast_bridge_destroy(caller_bridge); + } + return; + } + + /* Kick the agent out of the holding bridge to reset it. */ + ast_bridge_change_state_nolock(logged, AST_BRIDGE_CHANNEL_STATE_END); + ast_bridge_channel_unlock(logged); +} + +static int caller_safety_timeout(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + struct agent_pvt *agent = hook_pvt; + + if (agent->state == AGENT_STATE_CALL_PRESENT) { + ast_verb(3, "Agent '%s' did not respond. Safety timeout.\n", agent->username); + ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END); + caller_abort_agent(agent); + } + + return -1; +} + +static void agent_alert(struct ast_bridge_channel *bridge_channel, const void *payload, size_t payload_size) +{ + const char *agent_id = payload; + const char *playfile; + RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup); + + agent = ao2_find(agents, agent_id, OBJ_KEY); + if (!agent) { + ast_debug(1, "Agent '%s' does not exist. Where did it go?\n", agent_id); + return; + } + + /* Alert the agent. */ + agent_lock(agent); + playfile = ast_strdupa(agent->cfg->beep_sound); + agent_unlock(agent); + ast_stream_and_wait(bridge_channel->chan, playfile, AST_DIGIT_NONE); + + agent_lock(agent); + switch (agent->state) { + case AGENT_STATE_CALL_PRESENT: + if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL) + ? agent->override_ack_call : agent->cfg->ack_call) { + agent->state = AGENT_STATE_CALL_WAIT_ACK; + agent->ack_time = ast_tvnow(); + } else { + /* Connect to caller now. */ +/* BUGBUG need to finish here. */ + } + break; + default: + break; + } + agent_unlock(agent); + + /*! \todo BUGBUG agent_alert() not written */ +} + +static int send_alert_to_agent(struct ast_bridge_channel *bridge_channel, const char *agent_id) +{ + return ast_bridge_channel_queue_callback(bridge_channel, agent_alert, agent_id, + strlen(agent_id) + 1); +} + +static int send_colp_to_agent(struct ast_bridge_channel *bridge_channel, struct ast_party_connected_line *connected) +{ + struct ast_set_party_connected_line update = { + .id.name = 1, + .id.number = 1, + .id.subaddress = 1, + }; + unsigned char data[1024]; /* This should be large enough */ + size_t datalen; + + datalen = ast_connected_line_build_data(data, sizeof(data), connected, &update); + if (datalen == (size_t) -1) { + return 0; + } + + return ast_bridge_channel_queue_control_data(bridge_channel, + AST_CONTROL_CONNECTED_LINE, data, datalen); +} + +/*! + * \brief Dialplan AgentRequest application to locate an agent to talk with. * * \param chan Channel wanting to talk with an agent. * \param data Application parameters @@ -1387,23 +1578,122 @@ */ static int agent_request_exec(struct ast_channel *chan, const char *data) { + struct ast_bridge *caller_bridge; + struct ast_bridge_channel *logged; + char *parse; + int res; + struct ast_bridge_features caller_features; + struct ast_party_connected_line connected; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(agent_id); + AST_APP_ARG(other); /* Any remaining unused arguments */ + ); + + RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup); + if (bridge_agent_hold_deferred_create()) { return -1; } -/* BUGBUG need to deal with COLP to agents when a call is pending. */ -/* - * Need to look at the agent->state to determine if can request the agent or not. - * - * The agent may not have gotten pushed into the holding bridge yet if just look at agent->logged. - * - * if agent->state == AGENT_STATE_READY_FOR_CALL - * - * After custom_beep plays, the beep callback needs to determine if call must be acked or not. - * if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL) ? agent->override_ack_call : agent->cfg->ack_call) - */ - - /*! \todo BUGBUG agent_request_exec() not written */ + parse = ast_strdupa(data ?: ""); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.agent_id)) { + ast_log(LOG_WARNING, "AgentRequest requires an AgentId\n"); + return -1; + } + + /* Find the agent. */ + agent = ao2_find(agents, args.agent_id, OBJ_KEY); + if (!agent) { + ast_verb(3, "Agent '%s' does not exist.\n", args.agent_id); + pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_EXIST"); + return 0; + } + + if (ast_bridge_features_init(&caller_features)) { + return -1; + } + + /* Add safety timeout hook. */ + ao2_ref(agent, +1); + if (ast_bridge_interval_hook(&caller_features, CALLER_SAFETY_TIMEOUT_TIME, + caller_safety_timeout, agent, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) { + ao2_ref(agent, -1); + ast_bridge_features_cleanup(&caller_features); + return -1; + } + + caller_bridge = ast_bridge_basic_new(); + if (!caller_bridge) { + ast_bridge_features_cleanup(&caller_features); + return -1; + } + + /* Get COLP for agent. */ + ast_party_connected_line_init(&connected); + ast_channel_lock(chan); + ast_connected_line_copy_from_caller(&connected, ast_channel_caller(chan)); + ast_channel_unlock(chan); + + agent_lock(agent); + switch (agent->state) { + case AGENT_STATE_LOGGED_OUT: + case AGENT_STATE_LOGGING_OUT: + agent_unlock(agent); + ast_party_connected_line_free(&connected); + ast_bridge_destroy(caller_bridge); + ast_bridge_features_cleanup(&caller_features); + ast_verb(3, "Agent '%s' not logged in.\n", agent->username); + pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_LOGGED_IN"); + return 0; + case AGENT_STATE_READY_FOR_CALL: + ao2_ref(caller_bridge, +1); + agent->caller_bridge = caller_bridge; + agent->state = AGENT_STATE_CALL_PRESENT; + agent->devstate = AST_DEVICE_INUSE; + break; + default: + agent_unlock(agent); + ast_party_connected_line_free(&connected); + ast_bridge_destroy(caller_bridge); + ast_bridge_features_cleanup(&caller_features); + ast_verb(3, "Agent '%s' is busy.\n", agent->username); + pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "BUSY"); + return 0; + } + agent_unlock(agent); + agent_devstate_changed(agent->username); + + logged = agent_bridge_channel_get_lock(agent); + if (!logged) { + ast_party_connected_line_free(&connected); + ast_bridge_destroy(caller_bridge); + ast_bridge_features_cleanup(&caller_features); + ast_verb(3, "Agent '%s' not logged in.\n", agent->username); + pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_LOGGED_IN"); + return 0; + } + + send_colp_to_agent(logged, &connected); + ast_party_connected_line_free(&connected); + + res = send_alert_to_agent(logged, agent->username); + ast_bridge_channel_unlock(logged); + ao2_ref(logged, -1); + if (res) { + ast_bridge_destroy(caller_bridge); + ast_bridge_features_cleanup(&caller_features); + ast_verb(3, "Agent '%s': Failed to alert the agent.\n", agent->username); + pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "ERROR"); + caller_abort_agent(agent); + return 0; + } + + ast_queue_control(chan, AST_CONTROL_RINGING); + ast_bridge_join(caller_bridge, chan, NULL, &caller_features, NULL, 1); + ast_bridge_features_cleanup(&caller_features); + return -1; } @@ -1473,9 +1763,7 @@ END_OPTIONS); /*! - * Called by the AgentLogin application (from the dial plan). - * - * \brief Application to log in an agent. + * \brief Dialplan AgentLogin application to log in an agent. * * \param chan Channel attempting to login as an agent. * \param data Application parameters @@ -1520,7 +1808,7 @@ agent = ao2_find(agents, args.agent_id, OBJ_KEY); if (!agent) { ast_verb(3, "Agent '%s' does not exist.\n", args.agent_id); - pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "UNKNOWN_AGENT"); + pbx_builtin_setvar_helper(chan, "AGENT_STATUS", "NOT_EXIST"); return 0; } @@ -2084,6 +2372,7 @@ res |= ast_register_application_xml(app_agent_login, agent_login_exec); res |= ast_register_application_xml(app_agent_request, agent_request_exec); +/* BUGBUG agent call recording not written. */ /* BUGBUG bridge channel swap hook not written. */ if (res) { -- _____________________________________________________________________ -- Bandwidth and Colocation Provided by http://www.api-digital.com -- svn-commits mailing list To UNSUBSCRIBE or update options visit: http://lists.digium.com/mailman/listinfo/svn-commits