[svn-commits] rmudgett: branch rmudgett/bridge_phase r391903 - in /team/rmudgett/bridge_pha...
SVN commits to the Digium repositories
svn-commits at lists.digium.com
Fri Jun 14 17:05:31 CDT 2013
Author: rmudgett
Date: Fri Jun 14 17:05:30 2013
New Revision: 391903
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=391903
Log:
Add agent CLI commands.
* Added the following CLI commands:
agent logoff <agent-id> [soft]
agent show all
agent show online
agent show <agent-id>
Modified:
team/rmudgett/bridge_phase/apps/app_agent_pool.c
team/rmudgett/bridge_phase/configs/agents.conf.sample
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=391903&r1=391902&r2=391903
==============================================================================
--- team/rmudgett/bridge_phase/apps/app_agent_pool.c (original)
+++ team/rmudgett/bridge_phase/apps/app_agent_pool.c Fri Jun 14 17:05:30 2013
@@ -36,6 +36,8 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+#include "asterisk/cli.h"
+#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/channel.h"
#include "asterisk/config_options.h"
@@ -201,11 +203,7 @@
* \note The channel variable AGENTACKCALL overrides on login.
*/
int ack_call;
- /*!
- * \brief TRUE if agent can use DTMF to end a call.
- *
- * \note The channel variable AGENTENDCALL overrides on login.
- */
+ /*! \brief TRUE if agent can use DTMF to end a call. */
int end_call;
/*! TRUE if agent calls are recorded. */
int record_agent_calls;
@@ -478,6 +476,7 @@
aco_option_register(&cfg_info, "autologoff", ACO_EXACT, agent_types, "0", OPT_UINT_T, 0, FLDSET(struct agent_cfg, auto_logoff));
aco_option_register(&cfg_info, "ackcall", ACO_EXACT, agent_types, "no", OPT_BOOL_T, 1, FLDSET(struct agent_cfg, ack_call));
aco_option_register(&cfg_info, "acceptdtmf", ACO_EXACT, agent_types, "#", OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, dtmf_accept));
+/* BUGBUG endcall and enddtmf need to go away in favor of using the normal bridge disconnect DTMF feature. */
aco_option_register(&cfg_info, "endcall", ACO_EXACT, agent_types, "yes", OPT_BOOL_T, 1, FLDSET(struct agent_cfg, end_call));
aco_option_register(&cfg_info, "enddtmf", ACO_EXACT, agent_types, "*", OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, dtmf_end));
aco_option_register(&cfg_info, "wrapuptime", ACO_EXACT, agent_types, "0", OPT_UINT_T, 0, FLDSET(struct agent_cfg, wrapup_time));
@@ -533,9 +532,12 @@
/*! Login override time after a call in ms before the agent can get a new call. */
unsigned int override_wrapup_time;
/*! Login override if agent needs to ack a call to accept it. */
- int override_ack_call:1;
+ unsigned int override_ack_call:1;
/*! Login override if agent can use DTMF to end a call. */
- int override_end_call:1;
+ unsigned int override_end_call:1;
+
+ /*! TRUE if the agent is requested to logoff when the current call ends. */
+ unsigned int deferred_logoff:1;
/*! Mark and sweep config update to determine if an agent is dead. */
unsigned int the_mark:1;
@@ -562,17 +564,83 @@
struct timeval last_disconnect;
/*! Agent is logged in with this channel. (Holds ref) (NULL if not logged in.) */
- struct ast_channel *chan;
+ struct ast_channel *logged;
/*! Active config values from config file. (Holds ref) */
struct agent_cfg *cfg;
};
+/*!
+ * \brief Lock the agent.
+ *
+ * \param agent Agent to lock
+ *
+ * \return Nothing
+ */
+#define agent_lock(agent) _agent_lock(agent, __FILE__, __PRETTY_FUNCTION__, __LINE__, #agent)
+static inline void _agent_lock(struct agent_pvt *agent, const char *file, const char *function, int line, const char *var)
+{
+ __ao2_lock(agent, AO2_LOCK_REQ_MUTEX, file, function, line, var);
+}
+
+/*!
+ * \brief Unlock the agent.
+ *
+ * \param agent Agent to unlock
+ *
+ * \return Nothing
+ */
+#define agent_unlock(agent) _agent_unlock(agent, __FILE__, __PRETTY_FUNCTION__, __LINE__, #agent)
+static inline void _agent_unlock(struct agent_pvt *agent, const char *file, const char *function, int line, const char *var)
+{
+ __ao2_unlock(agent, file, function, line, var);
+}
+
+/*!
+ * \internal
+ * \brief Obtain the agent logged in channel lock if it exists.
+ * \since 12.0.0
+ *
+ * \param agent Pointer to the LOCKED agent_pvt.
+ *
+ * \note Assumes the agent lock is already obtained.
+ *
+ * \return Nothing
+ */
+static struct ast_channel *agent_lock_logged(struct agent_pvt *agent)
+{
+ struct ast_channel *logged;
+
+ for (;;) {
+ if (!agent->logged) { /* No owner. Nothing to do. */
+ return NULL;
+ }
+
+ /* If we don't ref the logged, it could be killed when we unlock the agent. */
+ logged = ast_channel_ref(agent->logged);
+
+ /* Locking logged requires us to lock channel, then agent. */
+ agent_unlock(agent);
+ ast_channel_lock(logged);
+ agent_lock(agent);
+
+ /* Check if logged changed during agent unlock period */
+ if (logged != agent->logged) {
+ /* Channel changed. Unref and do another pass. */
+ ast_channel_unlock(logged);
+ ast_channel_unref(logged);
+ } else {
+ /* Channel stayed the same. Return it. */
+ return logged;
+ }
+ }
+}
+
static void agent_pvt_destructor(void *vdoomed)
{
struct agent_pvt *doomed = vdoomed;
- if (doomed->chan) {
- doomed->chan = ast_channel_unref(doomed->chan);
+ if (doomed->logged) {
+ doomed->logged = ast_channel_unref(doomed->logged);
}
ao2_cleanup(doomed->cfg);
doomed->cfg = NULL;
@@ -643,7 +711,7 @@
* Usage:
* found = ao2_find(agents, agent, OBJ_POINTER);
* found = ao2_find(agents, "agent-id", OBJ_KEY);
- * found = ao2_find(agents, agent->chan, 0);
+ * found = ao2_find(agents, agent->logged, 0);
*/
static int agent_pvt_cmp(void *obj, void *arg, int flags)
{
@@ -657,7 +725,7 @@
cmp = CMP_MATCH;
break;
default:
- if (agent->chan == arg) {
+ if (agent->logged == arg) {
cmp = CMP_MATCH;
} else {
cmp = 0;
@@ -694,9 +762,9 @@
{
struct agent_pvt *agent = obj;
- ao2_lock(agent);
+ agent_lock(agent);
agent->the_mark = 1;
- ao2_unlock(agent);
+ agent_unlock(agent);
return 0;
}
@@ -710,11 +778,11 @@
struct agent_pvt *agent = obj;
int cmp = 0;
- ao2_lock(agent);
+ agent_lock(agent);
if (agent->the_mark) {
agent->the_mark = 0;
agent->dead = 1;
- if (!agent->chan) {
+ if (!agent->logged) {
/* Agent isn't logged in at this time. Destroy it now. */
cmp = CMP_MATCH;
}
@@ -722,7 +790,7 @@
/* Resurect a dead agent if it hasn't left yet or is still on a call. */
agent->dead = 0;
}
- ao2_unlock(agent);
+ agent_unlock(agent);
return cmp;
}
@@ -745,9 +813,9 @@
RAII_VAR(struct agent_pvt *, agent, ao2_find(agents, cfg->username, OBJ_KEY), ao2_cleanup);
if (agent) {
- ao2_lock(agent);
+ agent_lock(agent);
agent->the_mark = 0;
- ao2_unlock(agent);
+ agent_unlock(agent);
continue;
}
agent = agent_pvt_new(cfg);
@@ -760,8 +828,301 @@
agents_sweep();
}
+static int agent_logoff(const char *agent_id, int soft)
+{
+ struct ast_channel *logged;
+ RAII_VAR(struct agent_pvt *, agent, ao2_find(agents, agent_id, OBJ_KEY), ao2_cleanup);
+
+ if (!agent) {
+ return -1;
+ }
+
+ agent_lock(agent);
+ logged = agent_lock_logged(agent);
+ if (logged) {
+ if (soft) {
+ agent->deferred_logoff = 1;
+ } else {
+ ast_softhangup(logged, AST_SOFTHANGUP_EXPLICIT);
+ }
+ ast_channel_unlock(logged);
+ ast_channel_unref(logged);
+ }
+ agent_unlock(agent);
+ return 0;
+}
+
+struct agent_complete {
+ /*! Nth match to return. */
+ int state;
+ /*! Which match currently on. */
+ int which;
+};
+
+static int complete_agent_search(void *obj, void *arg, void *data, int flags)
+{
+ struct agent_complete *search = data;
+
+ if (++search->which > search->state) {
+ return CMP_MATCH;
+ }
+ return 0;
+}
+
+static char *complete_agent(const char *word, int state)
+{
+ char *ret;
+ struct agent_pvt *agent;
+ struct agent_complete search = {
+ .state = state,
+ };
+
+ agent = ao2_callback_data(agents, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+ complete_agent_search, (char *) word, &search);
+ if (!agent) {
+ return NULL;
+ }
+ ret = ast_strdup(agent->username);
+ ao2_ref(agent, -1);
+ return ret;
+}
+
+static int complete_agent_logoff_search(void *obj, void *arg, void *data, int flags)
+{
+ struct agent_pvt *agent = obj;
+ struct agent_complete *search = data;
+
+ if (!agent->logged) {
+ return 0;
+ }
+ if (++search->which > search->state) {
+ return CMP_MATCH;
+ }
+ return 0;
+}
+
+static char *complete_agent_logoff(const char *word, int state)
+{
+ char *ret;
+ struct agent_pvt *agent;
+ struct agent_complete search = {
+ .state = state,
+ };
+
+ agent = ao2_callback_data(agents, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
+ complete_agent_logoff_search, (char *) word, &search);
+ if (!agent) {
+ return NULL;
+ }
+ ret = ast_strdup(agent->username);
+ ao2_ref(agent, -1);
+ return ret;
+}
+
+static void agent_show_requested(struct ast_cli_args *a, int online_only)
+{
+#define FORMAT_HDR "%-8s %-20s %-11s %-30s %s\n"
+#define FORMAT_ROW "%-8s %-20s %-11s %-30s %s\n"
+
+ struct ao2_iterator iter;
+ struct agent_pvt *agent;
+ struct ast_str *out = ast_str_alloca(512);
+ unsigned int agents_total = 0;
+ unsigned int agents_logged_in = 0;
+ unsigned int agents_talking = 0;
+
+ ast_cli(a->fd, FORMAT_HDR, "Agent-ID", "Name", "State", "Channel", "Talking with");
+ iter = ao2_iterator_init(agents, 0);
+ for (; (agent = ao2_iterator_next(&iter)); ao2_ref(agent, -1)) {
+ struct ast_channel *logged;
+
+ ++agents_total;
+
+ agent_lock(agent);
+ logged = agent_lock_logged(agent);
+ if (logged) {
+ const char *talking_with;
+
+ ++agents_logged_in;
+
+ talking_with = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
+ if (!ast_strlen_zero(talking_with)) {
+ ++agents_talking;
+ } else {
+ talking_with = "";
+ }
+ ast_str_set(&out, 0, FORMAT_ROW, agent->username, agent->cfg->full_name,
+ ast_devstate_str(agent->state), ast_channel_name(logged), talking_with);
+ ast_channel_unlock(logged);
+ ast_channel_unref(logged);
+ } else {
+ ast_str_set(&out, 0, FORMAT_ROW, agent->username, agent->cfg->full_name,
+ ast_devstate_str(agent->state), "", "");
+ }
+ agent_unlock(agent);
+
+ if (!online_only || logged) {
+ ast_cli(a->fd, "%s", ast_str_buffer(out));
+ }
+ }
+ ao2_iterator_destroy(&iter);
+
+ ast_cli(a->fd, "\nDefined agents: %u, Logged in: %u, Talking: %u\n",
+ agents_total, agents_logged_in, agents_talking);
+
+#undef FORMAT_HDR
+#undef FORMAT_ROW
+}
+
+static char *agent_handle_show_online(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "agent show online";
+ e->usage =
+ "Usage: agent show online\n"
+ " Provides summary information for logged in agents.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ agent_show_requested(a, 1);
+
+ return CLI_SUCCESS;
+}
+
+static char *agent_handle_show_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "agent show all";
+ e->usage =
+ "Usage: agent show all\n"
+ " Provides summary information for all agents.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ agent_show_requested(a, 0);
+
+ return CLI_SUCCESS;
+}
+
+static char *agent_handle_show_specific(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct agent_pvt *agent;
+ struct ast_channel *logged;
+ struct ast_str *out = ast_str_alloca(4096);
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "agent show";
+ e->usage =
+ "Usage: agent show <agent-id>\n"
+ " Show information about the <agent-id> agent\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_agent(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 3) {
+ return CLI_SHOWUSAGE;
+ }
+
+ agent = ao2_find(agents, a->argv[2], OBJ_KEY);
+ if (!agent) {
+ ast_cli(a->fd, "Agent '%s' not found\n", a->argv[2]);
+ return CLI_SUCCESS;
+ }
+
+ agent_lock(agent);
+ logged = agent_lock_logged(agent);
+ ast_str_set(&out, 0, "Id: %s\n", agent->username);
+ ast_str_append(&out, 0, "Name: %s\n", agent->cfg->full_name);
+ ast_str_append(&out, 0, "Beep: %s\n", agent->cfg->beep_sound);
+ ast_str_append(&out, 0, "MOH: %s\n", agent->cfg->moh);
+ ast_str_append(&out, 0, "RecordCalls: %s\n", AST_CLI_YESNO(agent->cfg->record_agent_calls));
+ ast_str_append(&out, 0, "SaveCallsIn: %s\n", agent->cfg->save_calls_in);
+ ast_str_append(&out, 0, "State: %s\n", ast_devstate_str(agent->state));
+ if (logged) {
+ const char *talking_with;
+
+ ast_str_append(&out, 0, "LoggedInChannel: %s\n", ast_channel_name(logged));
+ ast_str_append(&out, 0, "LoggedInTime: %ld\n", (long) agent->start_login);
+ talking_with = pbx_builtin_getvar_helper(logged, "BRIDGEPEER");
+ if (!ast_strlen_zero(talking_with)) {
+ ast_str_append(&out, 0, "TalkingWith: %s\n", talking_with);
+ ast_str_append(&out, 0, "CallStarted: %ld\n", (long) agent->start_call);
+ }
+ ast_channel_unlock(logged);
+ ast_channel_unref(logged);
+ }
+ agent_unlock(agent);
+ ao2_ref(agent, -1);
+
+ ast_cli(a->fd, "%s", ast_str_buffer(out));
+
+ return CLI_SUCCESS;
+}
+
+static char *agent_handle_logoff_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "agent logoff";
+ e->usage =
+ "Usage: agent logoff <agent-id> [soft]\n"
+ " Sets an agent as no longer logged in.\n"
+ " If 'soft' is specified, do not hangup existing calls.\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 2) {
+ return complete_agent_logoff(a->word, a->n);
+ } else if (a->pos == 3 && a->n == 0
+ && (ast_strlen_zero(a->word)
+ || !strncasecmp("soft", a->word, strlen(a->word)))) {
+ return ast_strdup("soft");
+ }
+ return NULL;
+ }
+
+ if (a->argc < 3 || 4 < a->argc) {
+ return CLI_SHOWUSAGE;
+ }
+ if (a->argc == 4 && strcasecmp(a->argv[3], "soft")) {
+ return CLI_SHOWUSAGE;
+ }
+
+ if (!agent_logoff(a->argv[2], a->argc == 4)) {
+ ast_cli(a->fd, "Logging out %s\n", a->argv[2]);
+ }
+
+ return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry cli_agents[] = {
+ AST_CLI_DEFINE(agent_handle_show_online, "Show status of online agents"),
+ AST_CLI_DEFINE(agent_handle_show_all, "Show status of all agents"),
+ AST_CLI_DEFINE(agent_handle_show_specific, "Show information about an agent"),
+ AST_CLI_DEFINE(agent_handle_logoff_cmd, "Sets an agent offline"),
+};
+
static int unload_module(void)
{
+ ast_cli_unregister_multiple(cli_agents, ARRAY_LEN(cli_agents));
ast_devstate_prov_del("Agent");
destroy_config();
ao2_ref(agents, -1);
@@ -787,6 +1148,9 @@
/* Setup to provide Agent:agent-id device state. */
res |= ast_devstate_prov_add("Agent", agent_pvt_devstate_get);
+
+ /* CLI Commands */
+ res |= ast_cli_register_multiple(cli_agents, ARRAY_LEN(cli_agents));
if (res) {
unload_module();
Modified: team/rmudgett/bridge_phase/configs/agents.conf.sample
URL: http://svnview.digium.com/svn/asterisk/team/rmudgett/bridge_phase/configs/agents.conf.sample?view=diff&rev=391903&r1=391902&r2=391903
==============================================================================
--- team/rmudgett/bridge_phase/configs/agents.conf.sample (original)
+++ team/rmudgett/bridge_phase/configs/agents.conf.sample Fri Jun 14 17:05:30 2013
@@ -38,9 +38,9 @@
; Default is "#".
;acceptdtmf=#
;
+;BUGBUG endcall and enddtmf need to go away in favor of the normal bridge disconnect feature.
; Define endcall to allow the agent to hangup a call with a DTMF key
; sequence.
-; The channel variable AGENTENDCALL overrides on login.
; Default is "yes".
;endcall=yes
;
More information about the svn-commits
mailing list