[asterisk-commits] rmudgett: branch rmudgett/bridge_phase r393023 - /team/rmudgett/bridge_phase/...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Wed Jun 26 16:57:45 CDT 2013
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) {
More information about the asterisk-commits
mailing list