[svn-commits] rmudgett: trunk r393631 - in /trunk: ./ apps/ channels/ configs/ include/aste...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Wed Jul 3 18:55:55 CDT 2013


Author: rmudgett
Date: Wed Jul  3 18:55:53 2013
New Revision: 393631

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=393631
Log:
Add BUGBUG note for ASTERISK-22009

Added:
    trunk/apps/app_agent_pool.c   (with props)
Removed:
    trunk/channels/chan_agent.c
Modified:
    trunk/CHANGES
    trunk/UPGRADE.txt
    trunk/configs/agents.conf.sample
    trunk/configs/queues.conf.sample
    trunk/include/asterisk/bridging.h
    trunk/include/asterisk/config_options.h
    trunk/include/asterisk/stasis_channels.h
    trunk/main/bridging.c
    trunk/main/config_options.c
    trunk/main/stasis_channels.c

Modified: trunk/CHANGES
URL: http://svnview.digium.com/svn/asterisk/trunk/CHANGES?view=diff&rev=393631&r1=393630&r2=393631
==============================================================================
--- trunk/CHANGES (original)
+++ trunk/CHANGES Wed Jul  3 18:55:53 2013
@@ -14,10 +14,19 @@
 Applications
 ------------------
 
+AgentLogin
+------------------
+ * The application no longer does agent authentication.  The dialplan needs to
+   perform this function before running AgentLogin.  If the agent is already
+   logged in, dialplan will continue with the AGENT_STATUS channel variable
+   set to ALREADY_LOGGED_IN.
+
 AgentMonitorOutgoing
 ------------------
  * The 'c' option has been removed. It is not possible to modify the name of a
    channel involved in a CDR.
+ * Application removed.  It was a holdover from when AgentCallbackLogin was
+   removed.
 
 ForkCDR
 ------------------
@@ -244,8 +253,8 @@
    of "CallerID" and "ConnectedID" to avoid confusion with similarly named
    parameters in the channel snapshot.
 
- * The "Agentlogin" and "Agentlogoff" events have been renamed "AgentLogin" and
-   "AgentLogoff" respectively.
+ * The AMI events "Agentlogin" and "Agentlogoff" have been renamed
+   "AgentLogin" and "AgentLogoff" respectively.
 
  * The "Channel" key used in the "AlarmClear", "Alarm", and "DNDState" has been
    renamed "DAHDIChannel" since it does not convey an Asterisk channel name.
@@ -423,6 +432,21 @@
    and pretending otherwise helps no one.
  * The AGENTUPDATECDR channel variable has also been removed, for the same
    reason as the updatecdr option.
+ * The driver is no longer a Data retrieval API data provider for the
+   AMI DataGet action.
+ * The endcall and enddtmf configuration options are removed.  Use the
+   dialplan function CHANNEL(dtmf-features) to set DTMF features on the agent
+   channel before calling AgentLogin.
+ * chan_agent is removed and replaced with AgentLogin and AgentRequest dialplan
+   applications.  Agents are connected with callers using the new AgentRequest
+   dialplan application.  The Agents:<agent-id> device state is available to
+   monitor the status of an agent.  See agents.conf.sample for valid
+   configuration options.
+
+chan_bridge
+------------------
+ * chan_bridge is removed and its functionality is incorporated into ConfBridge
+   itself.
 
 chan_local
 ------------------

Modified: trunk/UPGRADE.txt
URL: http://svnview.digium.com/svn/asterisk/trunk/UPGRADE.txt?view=diff&rev=393631&r1=393630&r2=393631
==============================================================================
--- trunk/UPGRADE.txt (original)
+++ trunk/UPGRADE.txt Wed Jul  3 18:55:53 2013
@@ -24,6 +24,8 @@
 AgentMonitorOutgoing
  - The 'c' option has been removed. It is not possible to modify the name of a
    channel involved in a CDR.
+ - Application removed.  It was a holdover from when AgentCallbackLogin was
+   removed.
 
 NoCDR:
  - This application is deprecated. Please use the CDR_PROP function instead.
@@ -124,6 +126,15 @@
    and pretending otherwise helps no one.
  - The AGENTUPDATECDR channel variable has also been removed, for the same
    reason as the updatecdr option.
+ - chan_agent is removed and replaced with AgentLogin and AgentRequest dialplan
+   applications.  Agents are connected with callers using the new AgentRequest
+   dialplan application.  The Agents:<agent-id> device state is available to
+   monitor the status of an agent.  See agents.conf.sample for valid
+   configuration options.
+
+chan_bridge
+ - chan_bridge is removed and its functionality is incorporated into ConfBridge
+   itself.
 
 chan_dahdi:
  - Analog port dialing and deferred DTMF dialing for PRI now distinguishes

Added: trunk/apps/app_agent_pool.c
URL: http://svnview.digium.com/svn/asterisk/trunk/apps/app_agent_pool.c?view=auto&rev=393631
==============================================================================
--- trunk/apps/app_agent_pool.c (added)
+++ trunk/apps/app_agent_pool.c Wed Jul  3 18:55:53 2013
@@ -1,0 +1,2486 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013 Digium, Inc.
+ *
+ * Richard Mudgett <rmudgett at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Call center agent pool.
+ *
+ * \author Richard Mudgett <rmudgett at digium.com>
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ * \arg \ref Config_agent
+ */
+/*** MODULEINFO
+	<support_level>core</support_level>
+ ***/
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/pbx.h"
+#include "asterisk/module.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_basic.h"
+#include "asterisk/config_options.h"
+#include "asterisk/features_config.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/stasis_channels.h"
+
+/*** DOCUMENTATION
+	<application name="AgentLogin" language="en_US">
+		<synopsis>
+			Login an agent.
+		</synopsis>
+		<syntax argsep=",">
+			<parameter name="AgentId" required="true" />
+			<parameter name="options">
+				<optionlist>
+					<option name="s">
+						<para>silent login - do not announce the login ok segment after
+						agent logged on.</para>
+					</option>
+				</optionlist>
+			</parameter>
+		</syntax>
+		<description>
+			<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 <variable>AGENT_STATUS</variable> set.</para>
+			<para>Before logging in, you can setup on the real agent channel the
+			CHANNEL(dtmf-features) an agent will have when talking to a caller
+			and you can setup on the channel running this application the
+			CONNECTEDLINE() information the agent will see while waiting for a
+			caller.</para>
+			<para><variable>AGENT_STATUS</variable> enumeration values:</para>
+			<enumlist>
+				<enum name = "INVALID"><para>The specified agent is invalid.</para></enum>
+				<enum name = "ALREADY_LOGGED_IN"><para>The agent is already logged in.</para></enum>
+			</enumlist>
+			<note><para>The Agents:<replaceable>AgentId</replaceable> device state is
+			available to monitor the status of the agent.</para></note>
+		</description>
+		<see-also>
+			<ref type="application">Authenticate</ref>
+			<ref type="application">Queue</ref>
+			<ref type="application">AddQueueMember</ref>
+			<ref type="application">RemoveQueueMember</ref>
+			<ref type="application">PauseQueueMember</ref>
+			<ref type="application">UnpauseQueueMember</ref>
+			<ref type="function">AGENT</ref>
+			<ref type="function">CHANNEL(dtmf-features)</ref>
+			<ref type="function">CONNECTEDLINE()</ref>
+			<ref type="filename">agents.conf</ref>
+			<ref type="filename">queues.conf</ref>
+		</see-also>
+	</application>
+	<application name="AgentRequest" language="en_US">
+		<synopsis>
+			Request an agent to connect with the channel.
+		</synopsis>
+		<syntax argsep=",">
+			<parameter name="AgentId" required="true" />
+		</syntax>
+		<description>
+			<para>Request an agent to connect with the channel.  Failure to find and
+			alert an agent will continue in the dialplan with <variable>AGENT_STATUS</variable> set.</para>
+			<para><variable>AGENT_STATUS</variable> enumeration values:</para>
+			<enumlist>
+				<enum name = "INVALID"><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>
+				<enum name = "ERROR"><para>Alerting the agent failed.</para></enum>
+			</enumlist>
+		</description>
+		<see-also>
+			<ref type="application">AgentLogin</ref>
+		</see-also>
+	</application>
+	<function name="AGENT" language="en_US">
+		<synopsis>
+			Gets information about an Agent
+		</synopsis>
+		<syntax argsep=":">
+			<parameter name="AgentId" required="true" />
+			<parameter name="item">
+				<para>The valid items to retrieve are:</para>
+				<enumlist>
+					<enum name="status">
+						<para>(default) The status of the agent (LOGGEDIN | LOGGEDOUT)</para>
+					</enum>
+					<enum name="password">
+						<para>Deprecated.  The dialplan handles any agent authentication.</para>
+					</enum>
+					<enum name="name">
+						<para>The name of the agent</para>
+					</enum>
+					<enum name="mohclass">
+						<para>MusicOnHold class</para>
+					</enum>
+					<enum name="channel">
+						<para>The name of the active channel for the Agent (AgentLogin)</para>
+					</enum>
+					<enum name="fullchannel">
+						<para>The untruncated name of the active channel for the Agent (AgentLogin)</para>
+					</enum>
+				</enumlist>
+			</parameter>
+		</syntax>
+		<description></description>
+	</function>
+	<manager name="Agents" language="en_US">
+		<synopsis>
+			Lists agents and their status.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+		</syntax>
+		<description>
+			<para>Will list info about all defined agents.</para>
+		</description>
+	</manager>
+	<manager name="AgentLogoff" language="en_US">
+		<synopsis>
+			Sets an agent as no longer logged in.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Agent" required="true">
+				<para>Agent ID of the agent to log off.</para>
+			</parameter>
+			<parameter name="Soft">
+				<para>Set to <literal>true</literal> to not hangup existing calls.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Sets an agent as no longer logged in.</para>
+		</description>
+	</manager>
+	<configInfo name="app_agent_pool" language="en_US">
+		<synopsis>Agent pool applications</synopsis>
+		<description>
+			<note><para>Option changes take effect on agent login or after an agent
+			disconnects from a call.</para></note>
+		</description>
+		<configFile name="agents.conf">
+			<configObject name="global">
+				<synopsis>Unused, but reserved.</synopsis>
+			</configObject>
+			<configObject name="agent-id">
+				<synopsis>Configure an agent for the pool.</synopsis>
+				<description>
+					<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+				</description>
+				<configOption name="ackcall">
+					<synopsis>Enable to require the agent to acknowledge a call.</synopsis>
+					<description>
+						<para>Enable to require the agent to give a DTMF acknowledgement
+						when the agent receives a call.</para>
+						<note><para>The option is overridden by <variable>AGENTACKCALL</variable> on agent login.</para></note>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+					</description>
+				</configOption>
+				<configOption name="acceptdtmf">
+					<synopsis>DTMF key sequence the agent uses to acknowledge a call.</synopsis>
+					<description>
+						<note><para>The option is overridden by <variable>AGENTACCEPTDTMF</variable> on agent login.</para></note>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+					</description>
+				</configOption>
+				<configOption name="autologoff">
+					<synopsis>Time the agent has to acknowledge a call before being logged off.</synopsis>
+					<description>
+						<para>Set how many seconds a call for the agent has to wait for the
+						agent to acknowledge the call before the agent is automatically
+						logged off.  If set to zero then the call will wait forever for
+						the agent to acknowledge.</para>
+						<note><para>The option is overridden by <variable>AGENTAUTOLOGOFF</variable> on agent login.</para></note>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+					</description>
+				</configOption>
+				<configOption name="wrapuptime">
+					<synopsis>Minimum time the agent has between calls.</synopsis>
+					<description>
+						<para>Set the minimum amount of time in milliseconds after
+						disconnecting a call before the agent can receive a new call.</para>
+						<note><para>The option is overridden by <variable>AGENTWRAPUPTIME</variable> on agent login.</para></note>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+					</description>
+				</configOption>
+				<configOption name="musiconhold">
+					<synopsis>Music on hold class the agent listens to between calls.</synopsis>
+					<description>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+					</description>
+				</configOption>
+				<configOption name="recordagentcalls">
+					<synopsis>Enable to automatically record calls the agent takes.</synopsis>
+					<description>
+						<para>Enable recording calls the agent takes automatically by
+						invoking the automixmon DTMF feature when the agent connects
+						to a caller.  See <filename>features.conf.sample</filename> for information about
+						the automixmon feature.</para>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+					</description>
+				</configOption>
+				<configOption name="custom_beep">
+					<synopsis>Sound file played to alert the agent when a call is present.</synopsis>
+					<description>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+					</description>
+				</configOption>
+				<configOption name="fullname">
+					<synopsis>A friendly name for the agent used in log messages.</synopsis>
+					<description>
+						<xi:include xpointer="xpointer(/docs/configInfo[@name='app_agent_pool']/description/note)" />
+					</description>
+				</configOption>
+			</configObject>
+		</configFile>
+	</configInfo>
+ ***/
+
+/* ------------------------------------------------------------------- */
+
+#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)
+
+/*! Number of seconds to wait for local channel optimizations to complete. */
+#define LOGIN_WAIT_TIMEOUT_TIME		5
+
+static const char app_agent_login[] = "AgentLogin";
+static const char app_agent_request[] = "AgentRequest";
+
+/*! Agent config parameters. */
+struct agent_cfg {
+	AST_DECLARE_STRING_FIELDS(
+		/*! Identification of the agent.  (agents config container key) */
+		AST_STRING_FIELD(username);
+		/*! Name of agent for logging and querying purposes */
+		AST_STRING_FIELD(full_name);
+
+		/*!
+		 * \brief DTMF string for an agent to accept a call.
+		 *
+		 * \note The channel variable AGENTACCEPTDTMF overrides on login.
+		 */
+		AST_STRING_FIELD(dtmf_accept);
+		/*! Beep sound file to use.  Alert the agent a call is waiting. */
+		AST_STRING_FIELD(beep_sound);
+		/*! MOH class to use while agent waiting for call. */
+		AST_STRING_FIELD(moh);
+	);
+	/*!
+	 * \brief Number of seconds for agent to ack a call before being logged off.
+	 *
+	 * \note The channel variable AGENTAUTOLOGOFF overrides on login.
+	 * \note If zero then timer is disabled.
+	 */
+	unsigned int auto_logoff;
+	/*!
+	 * \brief Time after a call in ms before the agent can get a new call.
+	 *
+	 * \note The channel variable AGENTWRAPUPTIME overrides on login.
+	 */
+	unsigned int wrapup_time;
+	/*!
+	 * \brief TRUE if agent needs to ack a call to accept it.
+	 *
+	 * \note The channel variable AGENTACKCALL overrides on login.
+	 */
+	int ack_call;
+	/*! TRUE if agent calls are automatically recorded. */
+	int record_agent_calls;
+};
+
+/*!
+ * \internal
+ * \brief Agent config ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int agent_cfg_sort_cmp(const void *obj_left, const void *obj_right, int flags)
+{
+	const struct agent_cfg *cfg_left = obj_left;
+	const struct agent_cfg *cfg_right = obj_right;
+	const char *right_key = obj_right;
+	int cmp;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	default:
+	case OBJ_POINTER:
+		right_key = cfg_right->username;
+		/* Fall through */
+	case OBJ_KEY:
+		cmp = strcmp(cfg_left->username, right_key);
+		break;
+	case OBJ_PARTIAL_KEY:
+		cmp = strncmp(cfg_left->username, right_key, strlen(right_key));
+		break;
+	}
+	return cmp;
+}
+
+static void agent_cfg_destructor(void *vdoomed)
+{
+	struct agent_cfg *doomed = vdoomed;
+
+	ast_string_field_free_memory(doomed);
+}
+
+static void *agent_cfg_alloc(const char *name)
+{
+	struct agent_cfg *cfg;
+
+	cfg = ao2_alloc_options(sizeof(*cfg), agent_cfg_destructor,
+		AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!cfg || ast_string_field_init(cfg, 64)) {
+		return NULL;
+	}
+	ast_string_field_set(cfg, username, name);
+	return cfg;
+}
+
+static void *agent_cfg_find(struct ao2_container *agents, const char *username)
+{
+	return ao2_find(agents, username, OBJ_KEY);
+}
+
+/*! Agents configuration */
+struct agents_cfg {
+	/*! Master configured agents container. */
+	struct ao2_container *agents;
+};
+
+static struct aco_type agent_type = {
+	.type = ACO_ITEM,
+	.name = "agent-id",
+	.category_match = ACO_BLACKLIST,
+	.category = "^(general|agents)$",
+	.item_alloc = agent_cfg_alloc,
+	.item_find = agent_cfg_find,
+	.item_offset = offsetof(struct agents_cfg, agents),
+};
+
+static struct aco_type *agent_types[] = ACO_TYPES(&agent_type);
+
+/* The general category is reserved, but unused */
+static struct aco_type general_type = {
+	.type = ACO_GLOBAL,
+	.name = "global",
+	.category_match = ACO_WHITELIST,
+	.category = "^general$",
+};
+
+static struct aco_file agents_conf = {
+	.filename = "agents.conf",
+	.types = ACO_TYPES(&general_type, &agent_type),
+};
+
+static AO2_GLOBAL_OBJ_STATIC(cfg_handle);
+
+static void agents_cfg_destructor(void *vdoomed)
+{
+	struct agents_cfg *doomed = vdoomed;
+
+	ao2_cleanup(doomed->agents);
+	doomed->agents = NULL;
+}
+
+/*!
+ * \internal
+ * \brief Create struct agents_cfg object.
+ * \since 12.0.0
+ *
+ * \note A lock is not needed for the object or any secondary
+ * created cfg objects.  These objects are immutable after the
+ * config is loaded and applied.
+ *
+ * \retval New struct agents_cfg object.
+ * \retval NULL on error.
+ */
+static void *agents_cfg_alloc(void)
+{
+	struct agents_cfg *cfg;
+
+	cfg = ao2_alloc_options(sizeof(*cfg), agents_cfg_destructor,
+		AO2_ALLOC_OPT_LOCK_NOLOCK);
+	if (!cfg) {
+		return NULL;
+	}
+	cfg->agents = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK,
+		AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, agent_cfg_sort_cmp, NULL);
+	if (!cfg->agents) {
+		ao2_ref(cfg, -1);
+		cfg = NULL;
+	}
+	return cfg;
+}
+
+static void agents_post_apply_config(void);
+
+CONFIG_INFO_STANDARD(cfg_info, cfg_handle, agents_cfg_alloc,
+	.files = ACO_FILES(&agents_conf),
+	.post_apply_config = agents_post_apply_config,
+);
+
+static void destroy_config(void)
+{
+	ao2_global_obj_release(cfg_handle);
+	aco_info_destroy(&cfg_info);
+}
+
+static int load_config(void)
+{
+	if (aco_info_init(&cfg_info)) {
+		return -1;
+	}
+
+	/* Agent options */
+	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, 1, STRFLDSET(struct agent_cfg, dtmf_accept));
+	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, "wrapuptime", ACO_EXACT, agent_types, "0", OPT_UINT_T, 0, FLDSET(struct agent_cfg, wrapup_time));
+	aco_option_register(&cfg_info, "musiconhold", ACO_EXACT, agent_types, "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct agent_cfg, moh));
+	aco_option_register(&cfg_info, "recordagentcalls", ACO_EXACT, agent_types, "no", OPT_BOOL_T, 1, FLDSET(struct agent_cfg, record_agent_calls));
+	aco_option_register(&cfg_info, "custom_beep", ACO_EXACT, agent_types, "beep", OPT_STRINGFIELD_T, 1, STRFLDSET(struct agent_cfg, beep_sound));
+	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;
+	}
+
+	return 0;
+
+error:
+	destroy_config();
+	return -1;
+}
+
+enum agent_state {
+	/*! The agent is defined but an agent is not present. */
+	AGENT_STATE_LOGGED_OUT,
+	/*! Forced initial login wait to allow any local channel optimizations to happen. */
+	AGENT_STATE_PROBATION_WAIT,
+	/*! The agent is ready for a call. */
+	AGENT_STATE_READY_FOR_CALL,
+	/*! The agent has a call waiting to connect. */
+	AGENT_STATE_CALL_PRESENT,
+	/*! The agent needs to ack the call. */
+	AGENT_STATE_CALL_WAIT_ACK,
+	/*! The agent is connected with a call. */
+	AGENT_STATE_ON_CALL,
+	/*! The agent is resting between calls. */
+	AGENT_STATE_CALL_WRAPUP,
+	/*! The agent is being kicked out. */
+	AGENT_STATE_LOGGING_OUT,
+};
+
+/*! Agent config option override flags. */
+enum agent_override_flags {
+	AGENT_FLAG_ACK_CALL = (1 << 0),
+	AGENT_FLAG_DTMF_ACCEPT = (1 << 1),
+	AGENT_FLAG_AUTO_LOGOFF = (1 << 2),
+	AGENT_FLAG_WRAPUP_TIME = (1 << 3),
+};
+
+/*! \brief Structure representing an agent. */
+struct agent_pvt {
+	AST_DECLARE_STRING_FIELDS(
+		/*! Identification of the agent.  (agents container key) */
+		AST_STRING_FIELD(username);
+		/*! Login override DTMF string for an agent to accept a call. */
+		AST_STRING_FIELD(override_dtmf_accept);
+	);
+	/*! Connected line information to send when reentering the holding bridge. */
+	struct ast_party_connected_line waiting_colp;
+	/*! Flags show if settings were overridden by channel vars. */
+	unsigned int flags;
+	/*! Login override number of seconds for agent to ack a call before being logged off. */
+	unsigned int override_auto_logoff;
+	/*! 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. */
+	unsigned int override_ack_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;
+	/*!
+	 * \brief TRUE if the agent is no longer configured and is being destroyed.
+	 *
+	 * \note Agents cannot log in if they are dead.
+	 */
+	unsigned int dead:1;
+
+	/*! Agent control state variable. */
+	enum agent_state state;
+	/*! Custom device state of agent. */
+	enum ast_device_state devstate;
+
+	/*! When agent first logged in */
+	time_t login_start;
+	/*! When agent login probation started. */
+	time_t probation_start;
+	/*! When call started */
+	time_t call_start;
+	/*! When ack timer started */
+	struct timeval ack_time;
+	/*! When last disconnected */
+	struct timeval last_disconnect;
+
+	/*! Caller is waiting in this bridge for agent to join. (Holds ref) */
+	struct ast_bridge *caller_bridge;
+	/*! Agent is logged in with this channel. (Holds ref) (NULL if not logged in.) */
+	struct ast_channel *logged;
+	/*! Active config values from config file. (Holds ref) */
+	struct agent_cfg *cfg;
+};
+
+/*! Container of defined agents. */
+static struct ao2_container *agents;
+
+/*!
+ * \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;
+		}
+	}
+}
+
+/*!
+ * \internal
+ * \brief Get the Agent:agent_id device state.
+ * \since 12.0.0
+ *
+ * \param agent_id Username of the agent.
+ *
+ * \details
+ * Search the agents container for the agent and return the
+ * current state.
+ *
+ * \return Device state of the agent.
+ */
+static enum ast_device_state agent_pvt_devstate_get(const char *agent_id)
+{
+	RAII_VAR(struct agent_pvt *, agent, ao2_find(agents, agent_id, OBJ_KEY), ao2_cleanup);
+
+	if (agent) {
+		return agent->devstate;
+	}
+	return AST_DEVICE_INVALID;
+}
+
+/*!
+ * \internal
+ * \brief Request an agent device state be updated.
+ * \since 12.0.0
+ *
+ * \param agent_id Which agent needs the device state updated.
+ *
+ * \return Nothing
+ */
+static void agent_devstate_changed(const char *agent_id)
+{
+	ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "Agent:%s", agent_id);
+}
+
+static void agent_pvt_destructor(void *vdoomed)
+{
+	struct agent_pvt *doomed = vdoomed;
+
+	/* Make sure device state reflects agent destruction. */
+	if (!ast_strlen_zero(doomed->username)) {
+		ast_debug(1, "Agent %s: Destroyed.\n", doomed->username);
+		agent_devstate_changed(doomed->username);
+	}
+
+	ast_party_connected_line_free(&doomed->waiting_colp);
+	if (doomed->caller_bridge) {
+		ast_bridge_destroy(doomed->caller_bridge);
+		doomed->caller_bridge = NULL;
+	}
+	if (doomed->logged) {
+		doomed->logged = ast_channel_unref(doomed->logged);
+	}
+	ao2_cleanup(doomed->cfg);
+	doomed->cfg = NULL;
+	ast_string_field_free_memory(doomed);
+}
+
+static struct agent_pvt *agent_pvt_new(struct agent_cfg *cfg)
+{
+	struct agent_pvt *agent;
+
+	agent = ao2_alloc(sizeof(*agent), agent_pvt_destructor);
+	if (!agent) {
+		return NULL;
+	}
+	if (ast_string_field_init(agent, 32)) {
+		ao2_ref(agent, -1);
+		return NULL;
+	}
+	ast_string_field_set(agent, username, cfg->username);
+	ast_party_connected_line_init(&agent->waiting_colp);
+	ao2_ref(cfg, +1);
+	agent->cfg = cfg;
+	agent->devstate = AST_DEVICE_UNAVAILABLE;
+	return agent;
+}
+
+/*!
+ * \internal
+ * \brief Agents ao2 container sort function.
+ * \since 12.0.0
+ *
+ * \param obj_left pointer to the (user-defined part) of an object.
+ * \param obj_right pointer to the (user-defined part) of an object.
+ * \param flags flags from ao2_callback()
+ *   OBJ_POINTER - if set, 'obj_right', is an object.
+ *   OBJ_KEY - if set, 'obj_right', is a search key item that is not an object.
+ *   OBJ_PARTIAL_KEY - if set, 'obj_right', is a partial search key item that is not an object.
+ *
+ * \retval <0 if obj_left < obj_right
+ * \retval =0 if obj_left == obj_right
+ * \retval >0 if obj_left > obj_right
+ */
+static int agent_pvt_sort_cmp(const void *obj_left, const void *obj_right, int flags)
+{
+	const struct agent_pvt *agent_left = obj_left;
+	const struct agent_pvt *agent_right = obj_right;
+	const char *right_key = obj_right;
+	int cmp;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	default:
+	case OBJ_POINTER:
+		right_key = agent_right->username;
+		/* Fall through */
+	case OBJ_KEY:
+		cmp = strcmp(agent_left->username, right_key);
+		break;
+	case OBJ_PARTIAL_KEY:
+		cmp = strncmp(agent_left->username, right_key, strlen(right_key));
+		break;
+	}
+	return cmp;
+}
+
+/*!
+ * \internal
+ * \brief ao2_find() callback function.
+ * \since 12.0.0
+ *
+ * Usage:
+ * found = ao2_find(agents, agent, OBJ_POINTER);
+ * found = ao2_find(agents, "agent-id", OBJ_KEY);
+ * found = ao2_find(agents, agent->logged, 0);
+ */
+static int agent_pvt_cmp(void *obj, void *arg, int flags)
+{
+	const struct agent_pvt *agent = obj;
+	int cmp;
+
+	switch (flags & (OBJ_POINTER | OBJ_KEY | OBJ_PARTIAL_KEY)) {
+	case OBJ_POINTER:
+	case OBJ_KEY:
+	case OBJ_PARTIAL_KEY:
+		cmp = CMP_MATCH;
+		break;
+	default:
+		if (agent->logged == arg) {
+			cmp = CMP_MATCH;
+		} else {
+			cmp = 0;
+		}
+		break;
+	}
+	return cmp;
+}
+
+static int agent_mark(void *obj, void *arg, int flags)
+{
+	struct agent_pvt *agent = obj;
+
+	agent_lock(agent);
+	agent->the_mark = 1;
+	agent_unlock(agent);
+	return 0;
+}
+
+static void agents_mark(void)
+{
+	ao2_callback(agents, 0, agent_mark, NULL);
+}
+
+static int agent_sweep(void *obj, void *arg, int flags)
+{
+	struct agent_pvt *agent = obj;
+	int cmp = 0;
+
+	agent_lock(agent);
+	if (agent->the_mark) {
+		agent->the_mark = 0;
+		agent->dead = 1;
+		/* Unlink dead agents immediately. */
+		cmp = CMP_MATCH;
+	}
+	agent_unlock(agent);
+	return cmp;
+}
+
+static void agents_sweep(void)
+{
+	struct ao2_iterator *iter;
+	struct agent_pvt *agent;
+	struct ast_channel *logged;
+
+	iter = ao2_callback(agents, OBJ_MULTIPLE | OBJ_UNLINK, agent_sweep, NULL);
+	if (!iter) {
+		return;
+	}
+	for (; (agent = ao2_iterator_next(iter)); ao2_ref(agent, -1)) {
+		agent_lock(agent);
+		if (agent->logged) {
+			logged = ast_channel_ref(agent->logged);
+		} else {
+			logged = NULL;
+		}
+		agent_unlock(agent);
+		if (!logged) {
+			continue;
+		}
+		ast_log(LOG_NOTICE,
+			"Forced logoff of agent %s(%s).  Agent no longer configured.\n",
+			agent->username, ast_channel_name(logged));
+		ast_softhangup(logged, AST_SOFTHANGUP_EXPLICIT);
+		ast_channel_unref(logged);
+	}
+	ao2_iterator_destroy(iter);
+}
+
+static void agents_post_apply_config(void)
+{
+	struct ao2_iterator iter;
+	struct agent_cfg *cfg;
+	RAII_VAR(struct agents_cfg *, cfgs, ao2_global_obj_ref(cfg_handle), ao2_cleanup);
+
+	ast_assert(cfgs != NULL);
+
+	agents_mark();
+	iter = ao2_iterator_init(cfgs->agents, 0);
+	for (; (cfg = ao2_iterator_next(&iter)); ao2_ref(cfg, -1)) {
+		RAII_VAR(struct agent_pvt *, agent, ao2_find(agents, cfg->username, OBJ_KEY), ao2_cleanup);
+
+		if (agent) {
+			agent_lock(agent);
+			agent->the_mark = 0;
+			if (!agent->logged) {
+				struct agent_cfg *cfg_old;
+
+				/* Replace the config of agents not logged in. */
+				cfg_old = agent->cfg;
+				ao2_ref(cfg, +1);
+				agent->cfg = cfg;
+				ao2_cleanup(cfg_old);
+			}
+			agent_unlock(agent);
+			continue;
+		}
+		agent = agent_pvt_new(cfg);
+		if (!agent) {
+			continue;
+		}
+		ao2_link(agents, agent);
+		ast_debug(1, "Agent %s: Created.\n", agent->username);
+		agent_devstate_changed(agent->username);
+	}
+	ao2_iterator_destroy(&iter);
+	agents_sweep();
+}
+
+static int agent_logoff_request(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;
+}
+
+/*! Agent holding bridge instance holder. */
+static AO2_GLOBAL_OBJ_STATIC(agent_holding);
+
+/*! Agent holding bridge deferred creation lock. */
+AST_MUTEX_DEFINE_STATIC(agent_holding_lock);
+
+/*!
+ * \internal
+ * \brief Connect the agent with the waiting caller.
+ * \since 12.0.0
+ *
+ * \param bridge_channel Agent channel connecting to the caller.
+ * \param agent Which agent is connecting to the caller.
+ *
+ * \note The agent is locked on entry and not locked on exit.
+ *
+ * \return Nothing
+ */
+static void agent_connect_caller(struct ast_bridge_channel *bridge_channel, struct agent_pvt *agent)
+{
+	struct ast_bridge *caller_bridge;
+	int record_agent_calls;
+	int res;
+
+	record_agent_calls = agent->cfg->record_agent_calls;
+	caller_bridge = agent->caller_bridge;
+	agent->caller_bridge = NULL;
+	agent->state = AGENT_STATE_ON_CALL;
+	time(&agent->call_start);
+	agent_unlock(agent);
+
+	if (!caller_bridge) {
+		/* Reset agent. */
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+		return;
+	}
+	res = ast_bridge_move(caller_bridge, bridge_channel->bridge, bridge_channel->chan,
+		NULL, 0);
+	if (res) {
+		/* Reset agent. */
+		ast_bridge_destroy(caller_bridge);
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+		return;
+	}
+	ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_ANSWER, NULL, 0);
+
+	if (record_agent_calls) {
+		struct ast_bridge_features_automixmonitor options = {
+			.start_stop = AUTO_MONITOR_START,
+			};
+
+		/*
+		 * The agent is in the new bridge so we can invoke the
+		 * mixmonitor hook to only start recording.
+		 */
+		ast_bridge_features_do(AST_BRIDGE_BUILTIN_AUTOMIXMON, caller_bridge,
+			bridge_channel, &options);
+	}
+}
+
+static int bridge_agent_hold_ack(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	struct agent_pvt *agent = hook_pvt;
+
+	agent_lock(agent);
+	switch (agent->state) {
+	case AGENT_STATE_CALL_WAIT_ACK:
+		/* Connect to caller now. */
+		ast_debug(1, "Agent %s: Acked call.\n", agent->username);
+		agent_connect_caller(bridge_channel, agent);/* Will unlock agent. */
+		return 0;
+	default:
+		break;
+	}
+	agent_unlock(agent);
+	return 0;
+}
+
+static int bridge_agent_hold_heartbeat(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	struct agent_pvt *agent = hook_pvt;
+	int probation_timedout = 0;
+	int ack_timedout = 0;
+	int wrapup_timedout = 0;
+	int deferred_logoff;
+	unsigned int wrapup_time;
+	unsigned int auto_logoff;
+
+	agent_lock(agent);
+	deferred_logoff = agent->deferred_logoff;
+	if (deferred_logoff) {
+		agent->state = AGENT_STATE_LOGGING_OUT;
+	}
+
+	switch (agent->state) {
+	case AGENT_STATE_PROBATION_WAIT:
+		probation_timedout =
+			LOGIN_WAIT_TIMEOUT_TIME <= (time(NULL) - agent->probation_start);
+		if (probation_timedout) {
+			/* Now ready for a caller. */
+			agent->state = AGENT_STATE_READY_FOR_CALL;
+			agent->devstate = AST_DEVICE_NOT_INUSE;
+		}
+		break;
+	case AGENT_STATE_CALL_WAIT_ACK:
+		/* Check ack call time. */
+		auto_logoff = agent->cfg->auto_logoff;
+		if (ast_test_flag(agent, AGENT_FLAG_AUTO_LOGOFF)) {
+			auto_logoff = agent->override_auto_logoff;
+		}
+		if (auto_logoff) {
+			auto_logoff *= 1000;
+			ack_timedout = ast_tvdiff_ms(ast_tvnow(), agent->ack_time) > auto_logoff;
+			if (ack_timedout) {
+				agent->state = AGENT_STATE_LOGGING_OUT;
+			}
+		}
+		break;
+	case AGENT_STATE_CALL_WRAPUP:
+		/* Check wrapup time. */
+		wrapup_time = agent->cfg->wrapup_time;
+		if (ast_test_flag(agent, AGENT_FLAG_WRAPUP_TIME)) {
+			wrapup_time = agent->override_wrapup_time;
+		}
+		wrapup_timedout = ast_tvdiff_ms(ast_tvnow(), agent->last_disconnect) > wrapup_time;
+		if (wrapup_timedout) {
+			agent->state = AGENT_STATE_READY_FOR_CALL;
+			agent->devstate = AST_DEVICE_NOT_INUSE;
+		}
+		break;
+	default:
+		break;
+	}
+	agent_unlock(agent);
+
+	if (deferred_logoff) {
+		ast_debug(1, "Agent %s: Deferred logoff.\n", agent->username);
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+	} else if (probation_timedout) {
+		ast_debug(1, "Agent %s: Login complete.\n", agent->username);
+		agent_devstate_changed(agent->username);
+	} else if (ack_timedout) {
+		ast_debug(1, "Agent %s: Ack call timeout.\n", agent->username);
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+	} else if (wrapup_timedout) {
+		ast_debug(1, "Agent %s: Wrapup timeout. Ready for new call.\n", agent->username);
+		agent_devstate_changed(agent->username);
+	}
+
+	return 0;
+}
+
+static void agent_after_bridge_cb(struct ast_channel *chan, void *data);
+static void agent_after_bridge_cb_failed(enum ast_after_bridge_cb_reason reason, void *data);
+
+/*!
+ * \internal
+ * \brief ast_bridge agent_hold push method.
+ * \since 12.0.0
+ *
+ * \param self Bridge to operate upon.
+ * \param bridge_channel Bridge channel to push.
+ * \param swap Bridge channel to swap places with if not NULL.
+ *
+ * \note On entry, self is already locked.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int bridge_agent_hold_push(struct ast_bridge *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
+{
+	int res = 0;
+	unsigned int wrapup_time;
+	char dtmf[AST_FEATURE_MAX_LEN];
+	struct ast_channel *chan;
+	const char *moh_class;
+	RAII_VAR(struct agent_pvt *, agent, NULL, ao2_cleanup);
+
+	chan = bridge_channel->chan;
+
+	agent = ao2_find(agents, swap ? swap->chan : chan, 0);
+	if (!agent) {
+		/* Could not find the agent. */
+		return -1;
+	}
+
+	/* Setup agent entertainment */
+	agent_lock(agent);
+	moh_class = ast_strdupa(agent->cfg->moh);
+	agent_unlock(agent);
+	res |= ast_channel_add_bridge_role(chan, "holding_participant");
+	res |= ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
+	res |= ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", moh_class);
+
+	/* Add DTMF acknowledge hook. */
+	dtmf[0] = '\0';
+	agent_lock(agent);
+	if (ast_test_flag(agent, AGENT_FLAG_ACK_CALL)
+		? agent->override_ack_call : agent->cfg->ack_call) {
+		const char *dtmf_accept;
+
+		dtmf_accept = ast_test_flag(agent, AGENT_FLAG_DTMF_ACCEPT)
+			? agent->override_dtmf_accept : agent->cfg->dtmf_accept;

[... 2164 lines stripped ...]



More information about the svn-commits mailing list