[asterisk-commits] rmudgett: branch rmudgett/bridge_phase r391903 - in /team/rmudgett/bridge_pha...

SVN commits to the Asterisk project asterisk-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 asterisk-commits mailing list