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

Reply via email to