[asterisk-commits] file: branch file/app_agents r180850 - in /team/file/app_agents: apps/ configs/
SVN commits to the Asterisk project
asterisk-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 asterisk-commits
mailing list