[svn-commits] file: branch file/app_agents r180850 - in /team/file/app_agents: apps/ configs/

SVN commits to the Digium repositories svn-commits at lists.digium.com
Tue Mar 10 13:20:21 CDT 2009


Author: file
Date: Tue Mar 10 13:20:17 2009
New Revision: 180850

URL: http://svn.digium.com/svn-view/asterisk?view=rev&rev=180850
Log:
Bring back app_agents.

Added:
    team/file/app_agents/apps/app_agents.c   (with props)
    team/file/app_agents/configs/agents2.conf.sample   (with props)

Added: team/file/app_agents/apps/app_agents.c
URL: http://svn.digium.com/svn-view/asterisk/team/file/app_agents/apps/app_agents.c?view=auto&rev=180850
==============================================================================
--- team/file/app_agents/apps/app_agents.c (added)
+++ team/file/app_agents/apps/app_agents.c Tue Mar 10 13:20:17 2009
@@ -1,0 +1,1078 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2008, Digium, Inc.
+ *
+ * Joshua Colp <jcolp 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
+ *
+ * \author Joshua Colp <jcolp at digium.com>
+ *
+ * \brief Agent Application
+ * 
+ * \ingroup applications
+ */
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include <fcntl.h>
+#include <sys/signal.h>
+
+#include "asterisk/lock.h"
+#include "asterisk/channel.h"
+#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/pbx.h"
+#include "asterisk/file.h"
+#include "asterisk/cli.h"
+#include "asterisk/app.h"
+#include "asterisk/manager.h"
+#include "asterisk/stringfields.h"
+#include "asterisk/devicestate.h"
+#include "asterisk/astobj2.h"
+#include "asterisk/bridging.h"
+#include "asterisk/monitor.h"
+#include "asterisk/dial.h"
+#include "asterisk/astdb.h"
+
+/* Maximum size of the name of an agent */
+#define MAX_AGENT_NAME 128
+
+/* Maximum length of agent PIN */
+#define MAX_AGENT_PIN 16
+
+/* Number of buckets to use for storing agents */
+#define MAX_AGENT_BUCKETS 53
+
+/* Maximum length of filenames */
+#define MAX_FILENAME_LEN 32
+
+/* Maximum length of a callback dialing address */
+#define MAX_CALLBACK_LEN 32
+
+/* Default maximum number of tries for entering the agent PIN */
+#define MAX_PIN_TRIES 3
+
+/* How long to wait before giving up on acknowledgement from an agent (in ms) */
+#define MAX_ACK_TIME 5000
+
+/* DTMF key used to end a call */
+#define DTMF_KEY_END '*'
+
+/* DTMF key used to accept a call */
+#define DTMF_KEY_ACK '#'
+
+/* Default sound file using for sound when caller is connected to agent, or when acknowledgement is needed */
+#define SOUND_FILE_BEEP "beep"
+
+/* Default file format extension for recordings */
+#define DEFAULT_RECORDING_FORMAT "wav"
+
+/* Name of the astdb family to use for persistency */
+#define ASTDB_FAMILY "AppAgents"
+
+/* Configuration file */
+static const char config_file[] = "agents2.conf";
+
+/* Dialplan application documentation */
+static const char app[] = "AgentLogin2";
+static const char synopsis[] = "Call Agent Login";
+static const char descrip[] =
+"  AgentLogin2([Agent Name][,options]):\n"
+"Asks the agent to login to the system.  Always returns -1."
+"The option string may contain zero or more of the following characters:\n"
+"       l -- force logout - this will log out a callback agent if logged in\n"
+"       u -- skip pin - do not authenticate the agent\n"
+"       s -- silent - do not play login or logoff sound files\n"; 
+
+static const char app2[] = "AgentCall2";
+static const char synopsis2[] = "Talk to an agent";
+static const char descrip2[] =
+"  AgentCall2(Agent Name):\n"
+"Attempts to talk to the given agent. Always returns -1.";
+
+static const char mandescr_agents2[] =
+"Description: Will list info about all possible agents.\n"
+"Variables: NONE\n";
+
+static const char mandescr_agents2_available[] =
+"Description: Will list info about all available agents.\n"
+"Variables: NONE\n";
+
+/*! \brief State that the agent should be in */
+enum agent2_state {
+	AGENT2_STATE_NONE = 0,      /*!< Agent is not present */
+	AGENT2_STATE_WAIT,          /*!< Agent is waiting for someone to call */
+	AGENT2_STATE_CALL,          /*!< A caller wants to talk to the agent */
+	AGENT2_STATE_TALK,          /*!< Agent is talking to caller */
+	AGENT2_STATE_WRAP,          /*!< Agent is wrapping up */
+	AGENT2_STATE_HANGUP,        /*!< Agent has hung up */
+};
+
+/*! \brief Type of agent */
+enum agent2_type {
+	AGENT2_TYPE_WAITER = 0, /*!< Agent has to call in and wait for calls */
+	AGENT2_TYPE_CALLBACK,   /*!< Agent logs in and calls are then placed to them */
+};
+
+/*! \brief Structure representing an agent */
+struct agent2_agent {
+	enum agent2_type type;                   /*!< What type the agent is */
+	enum agent2_state state;                 /*!< State that the agent is currently in */
+	char name[MAX_AGENT_NAME];               /*!< Name of the agent */
+	char pin[MAX_AGENT_PIN];                 /*!< PIN used to authenticate as the agent */
+	char moh[MAX_MUSICCLASS];                /*!< What music on hold to play to the agent */
+	char beep[MAX_FILENAME_LEN];             /*!< Sound file to play to agent before connecting to caller */
+	char record_format[MAX_FILENAME_LEN];    /*!< Format of the file to record in */
+	char record_directory[MAX_FILENAME_LEN]; /*!< Directory to put recordings in */
+	char location[AST_MAX_EXTENSION];        /*!< Extension that a callback agent can be reached at */
+	char context[AST_MAX_CONTEXT];           /*!< Context that is used to reach the callback agent */
+	unsigned int prune:1;                    /*!< Marker used for pruning old agents when reloading */
+	unsigned int endcall:1;                  /*!< Marker used to indicate the agent can hang up the call by pressing a DTMF key */
+	unsigned int record:1;                   /*!< Marker used to indicate that calls should be recorded */
+	unsigned int ackcall:1;                  /*!< Marker used to indicate that the agent has to press a DTMF key to accept the call */
+	unsigned int persist:1;                  /*!< Marker used to indicate that login status of callback agent should persist across restarts */
+	int wrapuptime;                          /*!< Length of time after a call before accepting another */
+	int maxtries;                            /*!< Maximum number of tries that the PIN may be entered */
+	struct ast_bridge *bridge;               /*!< Bridge that bridges the caller and agent together */
+	struct timeval end;                      /*!< Time at which the last call was ended */
+	struct ast_dial *dial;                   /*!< Dialing structure used for callback agents */
+};
+
+/*! \brief Flags used for login application arguments */
+enum {
+	OPTION_LOGIN_LOGOUT = (1 << 0),   /*!< Force log out of the agent */
+	OPTION_LOGIN_SKIP_PIN = (1 << 1), /*!< Do not ask the agent to enter a pin */
+	OPTION_LOGIN_SILENT = (1 << 2),   /*!< Do not announce that the login logged in or logged out */
+} login_option_flags;
+
+/*! \brief Options for login application */
+AST_APP_OPTIONS(login_options, {
+	AST_APP_OPTION('l', OPTION_LOGIN_LOGOUT),
+	AST_APP_OPTION('u', OPTION_LOGIN_SKIP_PIN),
+	AST_APP_OPTION('s', OPTION_LOGIN_SILENT),	
+});
+
+/*! \brief Container to hold agents */
+static struct ao2_container *agents;
+
+/*! \brief Hashing function used for agents */
+static int agent_hash_cb(const void *obj, const int flags)
+{
+	const struct agent2_agent *agent = obj;
+	return ast_str_hash(agent->name);
+}
+
+/*! \brief Comparison function used for agents */
+static int agent_cmp_cb(void *obj, void *arg, int flags)
+{
+	const struct agent2_agent *agent0 = obj, *agent1 = arg;
+	return (!strcasecmp(agent0->name, agent1->name) ? CMP_MATCH : 0);
+}
+
+/*! \brief Function to convert agent state to plain text */
+static const char *agent2_state2text(enum agent2_state state)
+{
+	if (state == AGENT2_STATE_NONE) {
+		return "Not Logged In";
+	} else if (state == AGENT2_STATE_WAIT) {
+		return "Waiting For Caller";
+	} else if (state == AGENT2_STATE_CALL) {
+		return "Being Called";
+	} else if (state == AGENT2_STATE_TALK) {
+		return "Talking To Caller";
+	} else if (state == AGENT2_STATE_WRAP) {
+		return "Wrapping Up";
+	} else if (state == AGENT2_STATE_HANGUP) {
+		return "Hungup";
+	} else {
+		return "";
+	}
+}
+
+/*! \brief Function to convert agent type to plain text */
+static const char *agent2_type2text(enum agent2_type type)
+{
+	if (type == AGENT2_TYPE_WAITER) {
+		return "Waiter";
+	} else if (type == AGENT2_TYPE_CALLBACK) {
+		return "Callback";
+	} else {
+		return "";
+	}
+}
+
+/*! \brief Function to convert between agent state and device state */
+static enum ast_device_state agent2_state2devicestate(enum agent2_state state)
+{
+	if (state == AGENT2_STATE_NONE || state == AGENT2_STATE_HANGUP) {
+		return AST_DEVICE_UNAVAILABLE;
+	} else if (state == AGENT2_STATE_WAIT) {
+		return AST_DEVICE_NOT_INUSE;
+	} else if (state == AGENT2_STATE_CALL) {
+		return AST_DEVICE_RINGING;
+	} else if (state == AGENT2_STATE_TALK || state == AGENT2_STATE_WRAP) {
+		return AST_DEVICE_INUSE;
+	}
+
+	return AST_DEVICE_INVALID;
+}
+
+/*! \brief Function called to begin recording of an agent */
+static void agent2_monitor_start(struct agent2_agent *agent, struct ast_channel *chan)
+{
+	if (agent->record) {
+		char filename[MAX_FILENAME_LEN];
+		
+		snprintf(filename, sizeof(filename), "%sagent-%s-%s-%d", S_OR(agent->record_directory, ""), agent->name, chan->uniqueid, (int)ast_random());
+		ast_monitor_start(chan, S_OR(agent->record_format, DEFAULT_RECORDING_FORMAT), filename, 1, X_REC_IN | X_REC_OUT);
+		ast_monitor_setjoinfiles(chan, 1);
+	}
+}
+
+/*! \brief Function called to see if an agent wants to accept the call */
+static int agent2_accept_call(struct agent2_agent *agent, struct ast_channel *chan)
+{
+	int res;
+
+	if (!agent->ackcall) {
+		return 1;
+	}
+
+	if ((res = ast_stream_and_wait(chan, SOUND_FILE_BEEP, (const char*)DTMF_KEY_ACK)) && (res != DTMF_KEY_ACK)) {
+		return 0;
+	}
+
+	/* Wait longer for the DTMF to come in */
+	return ast_waitfordigit(chan, MAX_ACK_TIME) == DTMF_KEY_ACK ? 1 : 0;
+}
+
+/*! \brief Function called to change the state of an agent */
+static void agent2_change_state(struct agent2_agent *agent, enum agent2_state state)
+{
+	ast_debug(1, "Transitioning agent '%s' from state %d to state %d\n", agent->name, agent->state, state);
+	agent->state = state;
+	ast_devstate_changed(agent2_state2devicestate(state), "Agent:%s", agent->name);
+	return;
+}
+
+/*! \brief Function called when an agent wants to hang up the call using DTMF */
+static int agent2_hangup(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
+{
+	bridge_channel->state = AST_BRIDGE_CHANNEL_STATE_HANGUP;
+	return 0;
+}
+
+/*! \brief Function called to log in a waiter agent */
+static void agent2_waiter_login(struct agent2_agent *agent, struct ast_channel *chan, int silent)
+{
+	struct timeval login_time;
+	long session_duration = 0;
+
+	/* See if the agent is already logged in */
+	if (agent->state != AGENT2_STATE_NONE) {
+		ast_stream_and_wait(chan, "agent-alreadyon", "");
+		return;
+	}
+
+	/* Attempt to create a bridge that will be used to bridge the agent and caller together */
+	if (!(agent->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE))) {
+		ast_stream_and_wait(chan, "agent-alreadyon", "");
+		return;
+	}
+
+	login_time = ast_tvnow();
+
+	/* Make it known this agent logged in */
+	manager_event(EVENT_FLAG_AGENT, "AgentLogin",
+		      "Agent: %s\r\n"
+		      "Channel: %s\r\n"
+		      "Uniqueid: %s\r\n",
+		      agent->name, chan->name, chan->uniqueid);
+	ast_queue_log("NONE", chan->uniqueid, agent->name, "AGENTLOGIN", "%s", chan->name);
+	ast_verb(2, "Agent '%s' logged in on '%s'\n", agent->name, chan->name);
+
+	if (!silent) {
+		ast_stream_and_wait(chan, "agent-loginok", "");
+	}
+	agent2_change_state(agent, AGENT2_STATE_WAIT);
+	ast_indicate_data(chan, AST_CONTROL_HOLD, S_OR(agent->moh, NULL), !ast_strlen_zero(agent->moh) ? strlen(agent->moh) + 1 : 0);
+
+	/* Head into a loop waiting for us to hang up */
+	while (agent->state != AGENT2_STATE_HANGUP) {
+		if (agent->state == AGENT2_STATE_WAIT || agent->state == AGENT2_STATE_WRAP) {
+			int to = 100;
+			struct ast_frame *frame;
+
+			ao2_unlock(agent);
+
+			if ((ast_waitfor(chan, to) < 0) || !(frame = ast_read(chan))) {
+				agent->state = AGENT2_STATE_HANGUP;
+				continue;
+			}
+
+			ast_frfree(frame);
+
+			ao2_lock(agent);
+
+			if (agent->state == AGENT2_STATE_WRAP && (ast_tvdiff_ms(ast_tvnow(), agent->end) >= agent->wrapuptime)) {
+				agent2_change_state(agent, AGENT2_STATE_WAIT);
+			}
+		} else if (agent->state == AGENT2_STATE_CALL) {
+			ast_indicate(chan, AST_CONTROL_UNHOLD);
+			if (agent2_accept_call(agent, chan)) {
+				if (!ast_strlen_zero(agent->beep)) {
+					ast_stream_and_wait(chan, agent->beep, "");
+				}
+				agent2_change_state(agent, AGENT2_STATE_TALK);
+			} else {
+				ast_indicate_data(chan, AST_CONTROL_HOLD, S_OR(agent->moh, NULL), !ast_strlen_zero(agent->moh) ? strlen(agent->moh) + 1 : 0);
+			}
+		} else if (agent->state == AGENT2_STATE_TALK) {
+			enum ast_bridge_channel_state bridge_res;
+			struct ast_bridge_features features;
+
+			ast_bridge_features_init(&features);
+			if (agent->endcall) {
+				ast_bridge_features_hook(&features, (const char*)DTMF_KEY_END, agent2_hangup, NULL);
+			}
+
+			agent2_monitor_start(agent, chan);
+
+			ao2_unlock(agent);
+
+			bridge_res = ast_bridge_join(agent->bridge, chan, NULL, &features);
+
+			ast_bridge_features_cleanup(&features);
+
+			ao2_lock(agent);
+
+			ast_monitor_stop(chan, 1);
+
+			if (bridge_res == AST_BRIDGE_CHANNEL_STATE_END) {
+				agent->state = AGENT2_STATE_HANGUP;
+			} else {
+				ast_indicate_data(chan, AST_CONTROL_HOLD, S_OR(agent->moh, NULL), !ast_strlen_zero(agent->moh) ? strlen(agent->moh) + 1 : 0);
+				if (agent->wrapuptime) {
+					agent->state = AGENT2_STATE_WRAP;
+					agent->end = ast_tvnow();
+				} else {
+					agent2_change_state(agent, AGENT2_STATE_WAIT);
+				}
+			}
+		}
+	}
+
+	ast_indicate(chan, AST_CONTROL_UNHOLD);
+	agent2_change_state(agent, AGENT2_STATE_NONE);
+
+	session_duration = (ast_tvdiff_ms(ast_tvnow(), login_time) / 1000);
+
+	/* Drop the bridge since we no longer need it */
+	ast_bridge_destroy(agent->bridge);
+	agent->bridge = NULL;
+
+	/* Make it known this agent logged off */
+	manager_event(EVENT_FLAG_AGENT, "AgentLogoff",
+		      "Agent: %s\r\n"
+		      "Logintime: %ld\r\n"
+		      "Uniqueid: %s\r\n",
+		      agent->name, session_duration, chan->uniqueid);
+	ast_queue_log("NONE", chan->uniqueid, agent->name, "AGENTLOGOFF", "%s|%ld", chan->name, session_duration);
+	ast_verb(2, "Agent '%s' logged out\n", agent->name);
+
+	return;
+}
+
+/*! \brief Function called to log in a callback agent */
+static void agent2_callback_login(struct agent2_agent *agent, struct ast_channel *chan, int silent)
+{
+	/* Try to get the agent to input the destination at which they can be reached */
+	for (;;) {
+		if (ast_app_getdata(chan, "agent-newlocation", agent->location, sizeof(agent->location), 0)) {
+			return;
+		}
+		/* See if the location actually exists */
+		if (ast_exists_extension(chan, S_OR(agent->context, "default"), agent->location, 1, NULL)) {
+			break;
+		}
+		if (ast_stream_and_wait(chan, "invalid", "")) {
+			return;
+		}
+	}
+
+	/* Make it known this callback agent logged in */
+	manager_event(EVENT_FLAG_AGENT, "AgentCallbackLogin",
+		      "Agent: %s\r\n"
+		      "Channel: %s\r\n"
+		      "Uniqueid: %s\r\n",
+		      agent->name, chan->name, chan->uniqueid);
+	ast_queue_log("NONE", chan->uniqueid, agent->name, "AGENTCALLBACKLOGIN", "%s", chan->name);
+	ast_verb(2, "Agent '%s' logged in on '%s' available at '%s@%s'\n", agent->name, chan->name, agent->location, S_OR(agent->context, "default"));
+
+	if (!silent) {
+		ast_stream_and_wait(chan, "agent-loginok", "");
+	}
+	agent2_change_state(agent, AGENT2_STATE_WAIT);
+
+	/* If the agent has persistency enabled write this data out to the astdb */
+	if (agent->persist) {
+		ast_db_put(ASTDB_FAMILY, agent->name, agent->location);
+	}
+
+	return;
+}
+
+/*! \brief Function called to log off a callback agent */
+static void agent2_callback_logoff(struct agent2_agent *agent, struct ast_channel *chan, int silent)
+{
+	/* Make it known this callback agent logged off */
+	manager_event(EVENT_FLAG_AGENT, "AgentCallbackLogoff",
+		      "Agent: %s\r\n"
+		      "Channel: %s\r\n"
+		      "Uniqueid: %s\r\n",
+		      agent->name, chan->name, chan->uniqueid);
+	ast_queue_log("NONE", chan->uniqueid, agent->name, "AGENTCALLBACKLOGOFF", "%s", chan->name);
+	ast_verb(2, "Agent '%s' logged out\n", agent->name);
+
+	if (!silent) {
+		ast_stream_and_wait(chan, "agent-loggedoff", "");
+	}
+	agent2_change_state(agent, AGENT2_STATE_NONE);
+
+	/* If the agent has persistency enabled delete their record */
+	if (agent->persist) {
+		ast_db_del(ASTDB_FAMILY, agent->name);
+	}
+
+	return;
+}
+
+/*! \brief Function called to log in as an agent */
+static int agent2_login(struct ast_channel *chan, void *data)
+{
+	struct ast_flags flags = { 0, };
+	char *parse;
+	AST_DECLARE_APP_ARGS(args,
+			     AST_APP_ARG(agent_name);
+			     AST_APP_ARG(options);
+		);
+	int res, attempts = 0;
+	struct agent2_agent *agent;
+
+	/* If the channel has not already been answered then do so */
+	if (chan->_state != AST_STATE_UP) {
+		ast_answer(chan);
+	}
+
+	/* Parse out the agent name or options if present */
+	if (!ast_strlen_zero(data)) {
+		parse = ast_strdupa(data);
+		AST_STANDARD_APP_ARGS(args, parse);
+		if (args.argc == 2) {
+			ast_app_parse_options(login_options, &flags, NULL, args.options);
+		}
+	}
+
+	do {
+		struct agent2_agent tmp;
+		int max_tries = MAX_PIN_TRIES;
+
+		/* Try to get an agent name */
+		if (!ast_strlen_zero(args.agent_name)) {
+			ast_copy_string(tmp.name, args.agent_name, sizeof(tmp.name));
+		} else if ((res = ast_app_getdata(chan, attempts ? "agent-incorrect" : "agent-user", tmp.name, sizeof(tmp.name) - 1, 0))) {
+			return res;
+		}
+
+		/* See if we can actually find an agent with the username */
+		if ((agent = ao2_find(agents, &tmp, OBJ_POINTER))) {
+			max_tries = agent->maxtries;
+		}
+
+		/* Go for the actual PIN number now */
+		if (!ast_test_flag(&flags, OPTION_LOGIN_SKIP_PIN) && (res = ast_app_getdata(chan, "agent-pass", tmp.pin, sizeof(tmp.pin) - 1, 0))) {
+			if (agent) {
+				ao2_ref(agent, -1);
+			}
+			return res;
+		}
+
+		if (agent) {
+			if (ast_test_flag(&flags, OPTION_LOGIN_SKIP_PIN) || !strcasecmp(agent->pin, tmp.pin)) {
+				break;
+			}
+			ao2_ref(agent, -1);
+		}
+
+		/* If they have exceeded the number of tries send them out */
+		if (++attempts == max_tries) {
+			return -1;
+		}
+	} while (1);
+
+	ao2_lock(agent);
+
+	/* Bounce it off to the right login or logoff function */
+	if (agent->type == AGENT2_TYPE_WAITER) {
+		agent2_waiter_login(agent, chan, ast_test_flag(&flags, OPTION_LOGIN_SILENT));
+	} else if (agent->type == AGENT2_TYPE_CALLBACK) {
+		if (!ast_test_flag(&flags, OPTION_LOGIN_LOGOUT) && agent->state == AGENT2_STATE_NONE) {
+			agent2_callback_login(agent, chan, ast_test_flag(&flags, OPTION_LOGIN_SILENT));
+		} else {
+			agent2_callback_logoff(agent, chan, ast_test_flag(&flags, OPTION_LOGIN_SILENT));
+		}
+	}
+
+	ao2_unlock(agent);
+
+	ao2_ref(agent, -1);
+
+	return -1;
+}
+
+/*! \brief Function used to call a waiter agent */
+static void agent2_waiter_call(struct agent2_agent *agent, struct ast_channel *chan)
+{
+	/* Since a channel is already up with the other agent we just have to change the state of the agent and wait for them to talk to us */
+	agent2_change_state(agent, AGENT2_STATE_CALL);
+
+	/* Wait for a response from the agent */
+	while (agent->state == AGENT2_STATE_CALL) {
+		int to = 100;
+		struct ast_frame *frame;
+
+		ao2_unlock(agent);
+
+		/* Absorb frames from our own channel */
+		if ((ast_waitfor(chan, to) < 0) || !(frame = ast_read(chan))) {
+			agent2_change_state(agent, AGENT2_STATE_WAIT);
+		}
+
+		ao2_lock(agent);
+	}
+
+	/* If they do not want to talk to us walk away */
+	if (agent->state != AGENT2_STATE_TALK) {
+		ao2_unlock(agent);
+		ao2_ref(agent, -1);
+		return;
+	}
+
+	/* Tell the caller the agent answered by answering their own channel */
+	ast_answer(chan);
+
+	ao2_unlock(agent);
+
+	/* Now all is well... bridge them together */
+	ast_bridge_join(agent->bridge, chan, NULL, NULL);
+
+	ao2_ref(agent, -1);
+
+	return;
+}
+
+/*! \brief Callback for finding an agent based on dialing structure */
+static int agent2_find_by_dial(void *obj, void *arg, int flags)
+{
+	struct agent2_agent *agent = obj;
+	struct ast_dial *dial = arg;
+
+	return agent->dial == dial ? CMP_MATCH : 0;
+}
+
+/*! \brief Callback used for async callback agent dialing state notification */
+static void agent2_outgoing_state_callback(struct ast_dial *dial)
+{
+	struct agent2_agent *agent = ao2_callback(agents, 0, agent2_find_by_dial, dial);
+
+	/* If no agent was found just return now, no need to go on */
+	if (!agent) {
+		return;
+	}
+
+	ao2_lock(agent);
+
+	switch (ast_dial_state(dial)) {
+	case AST_DIAL_RESULT_HANGUP:
+	case AST_DIAL_RESULT_INVALID:
+	case AST_DIAL_RESULT_FAILED:
+	case AST_DIAL_RESULT_TIMEOUT:
+	case AST_DIAL_RESULT_UNANSWERED:
+		agent2_change_state(agent, AGENT2_STATE_WAIT);
+		break;
+	case AST_DIAL_RESULT_ANSWERED:
+		if (agent2_accept_call(agent, ast_dial_answered(dial))) {
+			agent2_change_state(agent, AGENT2_STATE_TALK);
+			agent2_monitor_start(agent, ast_dial_answered(dial));
+			ast_bridge_impart(agent->bridge, ast_dial_answered_steal(dial), NULL, NULL);
+		} else {
+			agent2_change_state(agent, AGENT2_STATE_WAIT);
+		}
+		break;
+	case AST_DIAL_RESULT_TRYING:
+	case AST_DIAL_RESULT_RINGING:
+	case AST_DIAL_RESULT_PROGRESS:
+	case AST_DIAL_RESULT_PROCEEDING:
+		break;
+	}
+
+	ao2_unlock(agent);
+	ao2_ref(agent, -1);
+
+	return;
+}
+
+/*! \brief Function used to call a callback agent */
+static void agent2_callback_call(struct agent2_agent *agent, struct ast_channel *chan)
+{
+	char dial_string[AST_MAX_EXTENSION + AST_MAX_CONTEXT];
+
+	/* Create a dial structure so we can try to get the agent */
+	if (!(agent->dial = ast_dial_create())) {
+		return;
+	}
+
+	/* Create a bridge so that the agent and caller can hopefully eventually talk */
+	if (!(agent->bridge = ast_bridge_new(AST_BRIDGE_CAPABILITY_1TO1MIX, AST_BRIDGE_FLAG_DISSOLVE))) {
+		ast_dial_destroy(agent->dial);
+		agent->dial = NULL;
+		return;
+	}
+
+	/* Prepare the dialing structure and get it started */
+	ast_dial_set_state_callback(agent->dial, agent2_outgoing_state_callback);
+	snprintf(dial_string, sizeof(dial_string), "%s@%s", agent->location, S_OR(agent->context, "default"));
+	if ((ast_dial_append(agent->dial, "Local", dial_string) == -1) || (ast_dial_run(agent->dial, chan, 1) != AST_DIAL_RESULT_TRYING)) {
+		ast_dial_destroy(agent->dial);
+		agent->dial = NULL;
+		ast_bridge_destroy(agent->bridge);
+		agent->bridge = NULL;
+		return;
+	}
+
+	/* Now that we are actually dialing change the state */
+	agent2_change_state(agent, AGENT2_STATE_CALL);
+
+	/* Wait for a response from the dialing attempt */
+	while (agent->state == AGENT2_STATE_CALL) {
+		int to = 100;
+		struct ast_frame *frame;
+
+		ao2_unlock(agent);
+
+		/* Absorb frames from our own channel */
+		if ((ast_waitfor(chan, to) < 0) || !(frame = ast_read(chan))) {
+			agent2_change_state(agent, AGENT2_STATE_WAIT);
+		}
+
+		ao2_lock(agent);
+	}
+
+	/* If they want to talk bridge them in */
+	if (agent->state == AGENT2_STATE_TALK) {
+		/* Answer the channel and join them into the bridge */
+		ast_answer(chan);
+
+		ao2_unlock(agent);
+
+		ast_bridge_join(agent->bridge, chan, NULL, NULL);
+
+		ao2_lock(agent);
+
+		/* Only change the state back to wait if it was not changed by something else */
+		if (agent->state == AGENT2_STATE_TALK) {
+			agent2_change_state(agent, AGENT2_STATE_WAIT);
+		}
+	}
+
+	/* Do actual clean up of the structures we used in this operation */
+	ast_dial_destroy(agent->dial);
+	agent->dial = NULL;
+	ast_bridge_destroy(agent->bridge);
+	agent->bridge = NULL;
+
+	ao2_unlock(agent);
+	ao2_ref(agent, -1);
+	
+	return;
+}
+
+/*! \brief Function used to talk to an agent */
+static int agent2_call(struct ast_channel *chan, void *data)
+{
+	struct agent2_agent *agent, tmp;
+	
+	if (ast_strlen_zero(data)) {
+		ast_log(LOG_WARNING, "%s requires an argument (agent name)\n", app2);
+		return -1;
+	}
+
+	ast_copy_string(tmp.name, data, sizeof(tmp.name));
+
+	/* Look for an agent matching this name */
+	if (!(agent = ao2_find(agents, &tmp, OBJ_POINTER))) {
+		return -1;
+	}
+
+	ao2_lock(agent);
+
+	/* See if the agent is not available and if someone else is already talking to them */
+	if (agent->state != AGENT2_STATE_WAIT) {
+		ao2_unlock(agent);
+		ao2_ref(agent, -1);
+		return -1;
+	}
+
+	if (agent->type == AGENT2_TYPE_WAITER) {
+		agent2_waiter_call(agent, chan);
+	} else if (agent->type == AGENT2_TYPE_CALLBACK) {
+		agent2_callback_call(agent, chan);
+	}
+
+	return -1;
+}
+
+/*! \brief Function called to get the device state of an agent */
+static enum ast_device_state agent2_state(const char *data)
+{
+	struct agent2_agent *agent, tmp;
+	enum ast_device_state res;
+
+	ast_copy_string(tmp.name, data, sizeof(tmp.name));
+
+	if (!(agent = ao2_find(agents, &tmp, OBJ_POINTER))) {
+		return AST_DEVICE_INVALID;
+	}
+
+	res = agent2_state2devicestate(agent->state);
+
+	ao2_ref(agent, -1);
+
+	return res;
+}
+
+/*! \brief Function which builds or updates an agent */
+static void load_config_agent(const char *name, struct ast_variable *var, int reload)
+{
+	struct agent2_agent *agent = NULL;
+	int new = 0;
+
+	/* If we are reloading see if an agent already exists */
+	if (reload) {
+		struct agent2_agent tmp;
+
+		ast_copy_string(tmp.name, name, sizeof(tmp.name));
+
+		/* If we find the agent make sure it does not get unlinked during pruning */
+		if ((agent = ao2_find(agents, &tmp, OBJ_POINTER))) {
+			agent->prune = 0;
+		}
+	}
+
+	/* If no agent yet exists create it and mark as new */
+	if (!agent) {
+		if (!(agent = ao2_alloc(sizeof(*agent), NULL))) {
+			return;
+		}
+		/* Set sane parameters and initialize things */
+		ast_copy_string(agent->name, name, sizeof(agent->name));
+		agent->type = AGENT2_TYPE_WAITER;
+		ast_copy_string(agent->beep, SOUND_FILE_BEEP, sizeof(agent->beep));
+		agent->maxtries = MAX_PIN_TRIES;
+		new = 1;
+	}
+
+	/* Iterate through the variables and set them to the proper thing */
+	for (; var; var = var->next) {
+		CV_START(var->name, var->value);
+		CV_STR("pin", agent->pin);
+		CV_STR("musiconhold", agent->moh);
+		CV_BOOL("endcall", agent->endcall);
+		CV_F("wrapuptime", agent->wrapuptime = atoi(var->value));
+		CV_STR("sound_beep", agent->beep);
+		CV_F("maxtries", agent->maxtries = atoi(var->value));
+		CV_BOOL("record", agent->record);
+		CV_STR("record_format", agent->record_format);
+		CV_STR("record_directory", agent->record_directory);
+		CV_F("type", agent->type = !strcasecmp(var->value, "callback") ? AGENT2_TYPE_CALLBACK : AGENT2_TYPE_WAITER);
+		CV_BOOL("ackcall", agent->ackcall);
+		CV_BOOL("persist", agent->persist);
+		CV_STR("context", agent->context);
+		CV_END;
+	}
+
+	/* If this is a new agent link it into the agents table */
+	if (new) {
+		ast_verbose(VERBOSE_PREFIX_2 "Brought agent '%s' into existence\n", name);
+		ao2_link(agents, agent);
+		/* If the agent has persistency enabled see if a location exists, and if so mark them as waiting */
+		if (agent->persist && !ast_db_get(ASTDB_FAMILY, agent->name, agent->location, sizeof(agent->location))) {
+			agent2_change_state(agent, AGENT2_STATE_WAIT);
+		} else {
+			agent2_change_state(agent, AGENT2_STATE_NONE);
+		}
+	}
+
+	ao2_ref(agent, -1);
+
+	return;
+}
+
+/*! \brief Function called to mark agents as needing pruning */
+static int agent_set_prune(void *obj, void *arg, int flags)
+{
+	struct agent2_agent *agent = obj;
+
+	agent->prune = 1;
+
+	return 0;
+}
+
+/*! \brief Function used as a callback to see if an agent is marked as needing pruning */
+static int agent_marked_prune(void *obj, void *arg, int flags)
+{
+	struct agent2_agent *agent = obj;
+
+	return agent->prune ? CMP_MATCH : 0;
+}
+
+/*! \brief Function which parses and configures agents */
+static void load_config(int reload)
+{
+	struct ast_config *cfg = NULL;
+	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+	const char *cat = NULL;
+
+	if (!(cfg = ast_config_load(config_file, config_flags))) {
+		ast_log(LOG_ERROR, "app_agents configuration file '%s' not found\n", config_file);
+		return;
+	} else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
+		return;
+	}
+
+	/* Mark agents for pruning */
+	if (reload) {
+		ao2_callback(agents, OBJ_NODATA, agent_set_prune, NULL);
+	}
+
+	/* Look for agents */
+	while ((cat = ast_category_browse(cfg, cat))) {
+		if (!strcasecmp(cat, "general")) {
+			continue;
+		}
+
+		load_config_agent(cat, ast_variable_browse(cfg, cat), reload);
+	}
+
+	/* Drop any agents that should be pruned */
+	if (reload) {
+		struct agent2_agent *agent;
+
+		while ((agent = ao2_callback(agents, OBJ_UNLINK, agent_marked_prune, NULL))) {
+			agent2_change_state(agent, AGENT2_STATE_HANGUP);
+			ao2_ref(agent, -1);
+		}
+	}
+
+	ast_config_destroy(cfg);
+
+	return;
+}
+
+/*! \brief CLI command to show all agents or information on a specific agent */
+static char *agents2_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct agent2_agent *agent;
+	struct ao2_iterator i;
+	int total_available = 0, total_unavailable = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "agents2 show";
+		e->usage =
+			"Usage: agents2 show\n"
+			"       Provides summary information on agents.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	i = ao2_iterator_init(agents, 0);
+	
+	for (; (agent = ao2_iterator_next(&i)); ao2_ref(agent, -1)) {
+		ast_cli(a->fd, "%s(%s) %s\n", agent->name, agent2_type2text(agent->type), agent2_state2text(agent->state));
+		if (agent->state == AGENT2_STATE_NONE || agent->state == AGENT2_STATE_HANGUP) {
+			total_unavailable++;
+		} else {
+			total_available++;
+		}
+	}
+	
+	ast_cli(a->fd, "%d agents configured [%d available , %d unavailable]\n\n", (total_available+total_unavailable), total_available, total_unavailable);
+
+	return CLI_SUCCESS;
+}
+
+/*! \brief CLI command to show all available agents */
+static char *agents2_show_available(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	struct agent2_agent *agent;
+	struct ao2_iterator i;
+	int total_available = 0;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "agents2 show available";
+		e->usage =
+			"Usage: agents2 show available\n"
+			"       Provides a list of all available agents.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	i = ao2_iterator_init(agents, 0);
+	
+	for (; (agent = ao2_iterator_next(&i)); ao2_ref(agent, -1)) {
+		if (!(agent->state == AGENT2_STATE_NONE || agent->state == AGENT2_STATE_HANGUP)) {
+			ast_cli(a->fd, "%s(%s) %s\n", agent->name, agent2_type2text(agent->type), agent2_state2text(agent->state));
+			total_available++;
+		}
+	}
+	
+	ast_cli(a->fd, "%d agents available\n\n", total_available);
+
+	return CLI_SUCCESS;
+}
+
+/*! \brief Lists agents and their status to the Manager API. */
+static int action_agents2(struct mansession *s, const struct message *m)
+{
+	struct agent2_agent *agent;
+	struct ao2_iterator i;
+	const char *id = astman_get_header(m,"ActionID");
+	char idText[256] = "";
+
+	if (!ast_strlen_zero(id))
+		snprintf(idText, sizeof(idText) ,"ActionID: %s\r\n", id);
+	astman_send_ack(s, m, "Agents2 will follow");
+
+	i = ao2_iterator_init(agents, 0);
+
+	for (; (agent = ao2_iterator_next(&i)); ao2_ref(agent, -1)) {
+                astman_append(s, "Event: Agents2\r\n"
+                        "Agent: %s\r\n"
+                        "Type: %s\r\n"
+                        "Status: %s\r\n"
+                        "%s"
+                        "\r\n",
+                        agent->name, agent2_type2text(agent->type), agent2_state2text(agent->state), idText);
+	}
+	
+	astman_append(s, "Event: Agents2Complete\r\n"
+                "%s"
+                "\r\n",idText);
+	return 0;
+}
+
+/*! \brief Lists available agents and their status to the Manager API. */
+static int action_agents2_available(struct mansession *s, const struct message *m)
+{
+	struct agent2_agent *agent;
+	struct ao2_iterator i;
+	const char *id = astman_get_header(m,"ActionID");
+	char idText[256] = "";
+
+	if (!ast_strlen_zero(id))
+		snprintf(idText, sizeof(idText) ,"ActionID: %s\r\n", id);
+	astman_send_ack(s, m, "Agents2Available will follow");
+
+	i = ao2_iterator_init(agents, 0);
+
+	for (; (agent = ao2_iterator_next(&i)); ao2_ref(agent, -1)) {
+		if (!(agent->state == AGENT2_STATE_NONE || agent->state == AGENT2_STATE_HANGUP)) {
+                	astman_append(s, "Event: Agents2Available\r\n"
+                        	"Agent: %s\r\n"
+                        	"Type: %s\r\n"
+                        	"Status: %s\r\n"
+                        	"%s"
+                        	"\r\n",
+                        	agent->name, agent2_type2text(agent->type), agent2_state2text(agent->state), idText);
+		}
+	}
+	
+	astman_append(s, "Event: Agents2AvailableComplete\r\n"
+                "%s"
+                "\r\n",idText);
+	return 0;
+}
+
+/*! \brief CLI commands to interact with things */
+static struct ast_cli_entry cli_agents2[] = {
+        AST_CLI_DEFINE(agents2_show, "Show status of agents"),
+        AST_CLI_DEFINE(agents2_show_available, "Show status of available agents"),
+};
+
+/*! \brief Function called when module should be reloaded */
+static int reload_module(void)
+{
+	load_config(1);
+	return 0;
+}
+
+/*! \brief Function called when module is loaded */
+static int load_module(void)
+{
+	if (!(agents = ao2_container_alloc(MAX_AGENT_BUCKETS, agent_hash_cb, agent_cmp_cb))) {
+		return AST_MODULE_LOAD_DECLINE;
+	}
+
+	load_config(0);
+
+	ast_devstate_prov_add("Agent", agent2_state);
+
+	ast_register_application(app, agent2_login, synopsis, descrip);
+	ast_register_application(app2, agent2_call, synopsis2, descrip2);
+
+	ast_manager_register2("Agents2", EVENT_FLAG_AGENT, action_agents2, "Lists agents and their status", mandescr_agents2);
+	ast_manager_register2("Agents2Available", EVENT_FLAG_AGENT, action_agents2_available, "Lists available agents and their status", mandescr_agents2_available);
+
+	ast_cli_register_multiple(cli_agents2, sizeof(cli_agents2) / sizeof(struct ast_cli_entry));
+
+	return AST_MODULE_LOAD_SUCCESS;
+}
+
+/*! \brief Function called when module is unloaded */
+static int unload_module(void)
+{
+	ast_cli_unregister_multiple(cli_agents2, sizeof(cli_agents2) / sizeof(struct ast_cli_entry));
+	ast_unregister_application(app);
+	ast_unregister_application(app2);
+	ast_manager_unregister("Agents2");
+	ast_manager_unregister("Agents2Available");
+	ast_devstate_prov_del("Agent");
+	ao2_ref(agents, -1);
+
+	return 0;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Agents Application",
+                .load = load_module,
+                .unload = unload_module,
+                .reload = reload_module,
+		);

Propchange: team/file/app_agents/apps/app_agents.c
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/file/app_agents/apps/app_agents.c
------------------------------------------------------------------------------
    svn:keywords = 'Author Date Id Revision'

Propchange: team/file/app_agents/apps/app_agents.c
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: team/file/app_agents/configs/agents2.conf.sample
URL: http://svn.digium.com/svn-view/asterisk/team/file/app_agents/configs/agents2.conf.sample?view=auto&rev=180850
==============================================================================
--- team/file/app_agents/configs/agents2.conf.sample (added)
+++ team/file/app_agents/configs/agents2.conf.sample Tue Mar 10 13:20:17 2009
@@ -1,0 +1,101 @@
+;
+; Application Agent configuration
+;
+
+; [name of agent]
+;
+; Type of agent. This can be set to either waiter or callback.
+; This setting is required.
+; Defaults to waiter.
+; type = <waiter | callback>
+;
+; PIN number used to log in as the above agent
+; Applicable to both waiter and callback.
+; This setting is required.
+; pin = <pin number>
+;
+; Music class that the agent will be subjected to while waiting for a caller
+; Applicable to watier only.
+; This setting is optional.
+; musiconhold = <music class>
+;
+; Whether the agent is able to disconnect a caller using the '*' DTMF key.
+; Applicable to waiter only.
+; This setting is optional.
+; Defaults to no.
+; endcall = <yes | no>
+;
+; Amount of time the agent is given in between calls.
+; The value is specified in milliseconds.
+; Applicable to waiter only.
+; This setting is optional.
+; Defaults to 0.
+; wrapuptime = <number of milliseconds>
+;
+; Sound played to the agent when a caller is about to be connected to
+; them. This can be disabled by setting the value to nothing.
+; Applicable to waiter only.
+; This setting is optional.
+; Defaults to beep.
+; sound_beep = <sound file>
+;
+; Maximum number of agent login attempts before the call is hung up.
+; Applicable to waiter and callback.
+; This setting is optional.
+; Defaults to 3.
+; maxtries = <maximum number of attempts>
+;
+; Whether to record calls to the agent
+; Applicable to waiter and callback.
+; This setting is optional.
+; Defaults to no.
+; record = <yes | no>
+;
+; File format extension to use for recordings
+; Applicable to waiter and callback.
+; This setting is optional.
+; Defaults to wav.
+; record_format = <file format extension>
+;
+; Directory to put recordings into
+; Applicable to waiter and callback.
+; This setting is optional.
+; Defaults to /var/spool/asterisk/monitor
+; record_directory = <directory>
+;
+; Whether to require the agent to press the '#' DTMF key to accept a call.
+; Applicable to waiter and callback.
+; This setting is optional.
+; Defaults to no.
+; ackcall = <yes | no>
+;
+; Whether to store location information in the Asterisk database on callback agents.
+; Applicable to callback.
+; This setting is optional.
+; Defaults to no.
+; persist = <yes | no>
+;
+; Context to use when dialing the location of the callback agent.
+; Applicable to callback.
+; This setting is optional.
+; Defaults to default.
+; context = <context to use>
+
+; Examples:
+;
+; Dave's Waiter Agent Entry
+; [1000]
+; type = waiter
+; pin = 1234
+; musiconhold = default
+; endcall = yes
+; wrapuptime = 5000
+; sound_beep = beep
+; maxtries = 3
+;
+; Joe's Callback Agent Entry
+; [1001]
+; type = callback
+; pin = 1234
+; ackcall = yes
+; context = users

Propchange: team/file/app_agents/configs/agents2.conf.sample
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: team/file/app_agents/configs/agents2.conf.sample
------------------------------------------------------------------------------
    svn:keywords = 'Author Date Id Revision'

Propchange: team/file/app_agents/configs/agents2.conf.sample
------------------------------------------------------------------------------
    svn:mime-type = text/plain




More information about the svn-commits mailing list