Index: apps/app_queue.c =================================================================== --- apps/app_queue.c (revision 340416) +++ apps/app_queue.c (working copy) @@ -99,6 +99,7 @@ #include "asterisk/callerid.h" #include "asterisk/cel.h" #include "asterisk/data.h" +#include "asterisk/queue.h" /* Define, to debug reference counts on queues, without debugging reference counts on queue members */ /* #define REF_DEBUG_ONLY_QUEUES */ @@ -1021,10 +1022,12 @@ struct queue_ent { struct call_queue *parent; /*!< What queue is our parent */ + char advroute_agent[80]; /*!< Agent selected by Advance routing app */ char moh[80]; /*!< Name of musiconhold to be used */ char announce[PATH_MAX]; /*!< Announcement to play for member when call is answered */ char context[AST_MAX_CONTEXT]; /*!< Context when user exits queue */ char digits[AST_MAX_EXTENSION]; /*!< Digits entered while in queue */ + int advroute_enabled; /*!< Advance routing enabled for the queue entry */ int valid_digits; /*!< Digits entered correspond to valid extension. Exited */ int pos; /*!< Where we are in the queue */ int prio; /*!< Our priority */ @@ -1154,6 +1157,7 @@ unsigned int announceholdtime:2; unsigned int announceposition:3; int strategy:4; + int advroute_enabled:4; unsigned int maskmemberstatus:1; unsigned int realtime:1; unsigned int found:1; @@ -1730,6 +1734,7 @@ q->autopause = QUEUE_AUTOPAUSE_OFF; q->timeoutpriority = TIMEOUT_PRIORITY_APP; q->autopausedelay = 0; + q->advroute_enabled = 0; if (!q->members) { if (q->strategy == QUEUE_STRATEGY_LINEAR || q->strategy == QUEUE_STRATEGY_RRORDERED) /* linear strategy depends on order, so we have to place all members in a single bucket */ @@ -2073,6 +2078,8 @@ return; } q->strategy = strategy; + } else if (!strcasecmp(param, "advancerouting")) { + q->advroute_enabled = ast_true(val) ? 1 : 0; } else if (!strcasecmp(param, "joinempty")) { parse_empty_options(val, &q->joinempty, 1); } else if (!strcasecmp(param, "leavewhenempty")) { @@ -2446,6 +2453,78 @@ return q; } +/*! + * \note Route the call to the selected agent. + */ +int route_select(const char * queuename, const char * uniqueid, const char * agentid) +{ + struct call_queue * q; + struct queue_ent * qe; + int res = -1; /* queue not found */ + if (!(q = load_realtime_queue(queuename))) + return res; + + res = -4; /* call not found */ + ao2_lock(q); + if (q->advroute_enabled) { + for (qe = q->head; qe; qe = qe->next) { + if (!strcmp(qe->chan->uniqueid, uniqueid)) { + if (qe->advroute_enabled) { + ast_copy_string(qe->advroute_agent, agentid, sizeof(qe->advroute_agent)); + ast_queue_log(queuename, uniqueid, agentid, "ROUTE_SELECT", "%ld", (long)time(NULL) - qe->start); + res = 0; /* success */ + break; + } else { + res = -3; /* not routable */ + break; + } + } + } + } + else { + res = -2; /* advance routing is not enabled. */ + } + + ao2_unlock(q); + return res; +} + +/*! + * \note Resume the call for default queue routing + */ +int route_end(const char * queuename, const char * uniqueid) +{ + struct call_queue * q; + struct queue_ent * qe; + int res = -1; /* queue not found */ + if (!(q = load_realtime_queue(queuename))) + return res; + + res = -4; /* call not found */ + ao2_lock(q); + if (q->advroute_enabled) { + for (qe = q->head; qe; qe = qe->next) { + if (!strcmp(qe->chan->uniqueid, uniqueid)) { + if (qe->advroute_enabled) { + qe->advroute_enabled = 0; + ast_queue_log(queuename, uniqueid, "NONE", "ROUTE_END", "%ld", (long)time(NULL) - qe->start); + res = 0; /* success */ + break; + } else { + res = -3; /* not routable */ + break; + } + } + } + } + else { + res = -2; /* advance routing is not enabled. */ + } + + ao2_unlock(q); + return res; +} + static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value) { int ret = -1; @@ -2568,6 +2647,7 @@ ast_copy_string(qe->moh, q->moh, sizeof(qe->moh)); ast_copy_string(qe->announce, q->announce, sizeof(qe->announce)); ast_copy_string(qe->context, q->context, sizeof(qe->context)); + qe->advroute_enabled = q->advroute_enabled; q->count++; res = 0; ast_manager_event(qe->chan, EVENT_FLAG_CALL, "Join", @@ -2586,6 +2666,14 @@ S_COR(qe->chan->connected.id.number.valid, qe->chan->connected.id.number.str, "unknown"),/* XXX somewhere else it is */ S_COR(qe->chan->connected.id.name.valid, qe->chan->connected.id.name.str, "unknown"), q->name, qe->pos, q->count, qe->chan->uniqueid ); + + if (q->advroute_enabled) { + manager_event(EVENT_FLAG_ROUTE, "RouteRequest", "Queue: %s\r\nUniqueid: %s\r\nChannel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\n", + q->name, qe->chan->uniqueid, qe->chan->name, + S_COR(qe->chan->caller.id.number.valid, qe->chan->caller.id.number.str, "unknown"),/* XXX somewhere else it is */ + S_COR(qe->chan->caller.id.name.valid, qe->chan->caller.id.name.str, "unknown") ); + ast_log(LOG_NOTICE, "RouteRequest Q:%s U:%s C:%s\n", q->name, qe->chan->uniqueid, qe->chan->name); + } ast_debug(1, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos ); } ao2_unlock(q); @@ -2837,6 +2925,14 @@ char posstr[20]; q->count--; + if (qe->advroute_enabled && !ast_strlen_zero(qe->advroute_agent)) { + qe->advroute_agent[0] = '\0'; + manager_event(EVENT_FLAG_ROUTE, "RouteResult", + "Result: Success\r\nUniqueid: %s\r\nChannel: %s\r\n", + qe->chan->uniqueid, qe->chan->name); + ast_log(LOG_NOTICE, "RouteResult Result:Success U:%s C:%s\n", qe->chan->uniqueid, qe->chan->name); + } + /* Take us out of the queue */ ast_manager_event(qe->chan, EVENT_FLAG_CALL, "Leave", "Channel: %s\r\nQueue: %s\r\nCount: %d\r\nPosition: %d\r\nUniqueid: %s\r\n", @@ -3085,6 +3181,13 @@ ast_cdr_busy(qe->chan->cdr); } tmp->stillgoing = 0; + if (verify_advroute_enabled() && !strcasecmp(qe->advroute_agent, tmp->member->membername)) { + manager_event(EVENT_FLAG_ROUTE, "RouteResult", + "Result: Fail\r\nUniqueid: %s\r\nChannel: %s\r\nAgentid: %s\r\nReason: Paused\r\n", + qe->chan->uniqueid, qe->chan->name, qe->advroute_agent); + ast_log(LOG_NOTICE, "RouteResult Result:Fail U:%s C:%s A:%s R:Paused\n", qe->chan->uniqueid, qe->chan->name, qe->advroute_agent); + qe->advroute_agent[0] = '\0'; + } return 0; } @@ -3115,11 +3218,18 @@ ast_cdr_busy(qe->chan->cdr); } tmp->stillgoing = 0; + if (verify_advroute_enabled() && !strcasecmp(qe->advroute_agent, tmp->member->membername)) { + manager_event(EVENT_FLAG_ROUTE, "RouteResult", + "Result: Fail\r\nUniqueid: %s\r\nChannel: %s\r\nAgentid: %s\r\nReason: Busy\r\n", + qe->chan->uniqueid, qe->chan->name, qe->advroute_agent); + ast_log(LOG_NOTICE, "RouteResult Result:Fail U:%s C:%s A:%s R:Busy\n", qe->chan->uniqueid, qe->chan->name, qe->advroute_agent); + qe->advroute_agent[0] = '\0'; + } return 0; } } - if (use_weight && compare_weight(qe->parent,tmp->member)) { + if ((!verify_advroute_enabled() || !qe->advroute_enabled) && use_weight && compare_weight(qe->parent,tmp->member)) { ast_debug(1, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface); if (qe->chan->cdr) { ast_cdr_busy(qe->chan->cdr); @@ -3129,6 +3239,14 @@ return 0; } + if (verify_advroute_enabled() && qe->advroute_enabled) { + if (strcasecmp(qe->advroute_agent, tmp ->interface)) { + ast_log(LOG_DEBUG, "%s not Advance routing selected agent.\n", tmp->interface); + tmp->stillgoing = 0; + return 0; + } + } + ast_copy_string(tech, tmp->interface, sizeof(tech)); if ((location = strchr(tech, '/'))) *location++ = '\0'; @@ -3450,6 +3568,13 @@ /*! \brief RNA == Ring No Answer. Common code that is executed when we try a queue member and they don't answer. */ static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername, int pause) { + if (qe->parent->advroute_enabled) { + qe->advroute_agent[0] = '\0'; + manager_event(EVENT_FLAG_ROUTE, "RNA", + "Queue: %s\r\nUniqueid: %s\r\nChannel: %s\r\nAgent: %s\r\nReason: %s\r\n", + qe->parent->name, qe->chan->uniqueid, qe->chan->name, membername, "RINGNOANSWER"); + } + ast_verb(3, "Nobody picked up in %d ms\n", rnatime); /* Stop ringing, and resume MOH if specified */ @@ -3952,34 +4077,58 @@ int res; int avl; int idx = 0; - /* This needs a lock. How many members are available to be served? */ - ao2_lock(qe->parent); + if (verify_advroute_enabled() && qe->advroute_enabled) { + /* This needs a lock. How many members are available to be served? */ + ao2_lock(qe->parent); + ch = qe->parent->head; + while ((ch) && (ch != qe)) { + ch = ch->next; + } + + if (ch && ast_strlen_zero(ch->advroute_agent)) { + ch = NULL; + } + + if (ch) { + if (option_debug) { + ast_log(LOG_DEBUG, "Advance Routing, It's our turn (%s).\n", qe->chan->name); + } + res = 1; + } else { + if (option_debug) { + ast_log(LOG_DEBUG, "Advance Routing, It's not our turn (%s).\n", qe->chan->name); + } + res = 0; + } + + ao2_unlock(qe->parent); + } else { + /* This needs a lock. How many members are available to be served? */ + ao2_lock(qe->parent); + avl = num_available_members(qe->parent); + ch = qe->parent->head; - avl = num_available_members(qe->parent); - - ch = qe->parent->head; - - ast_debug(1, "There %s %d available %s.\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member"); - - while ((idx < avl) && (ch) && (ch != qe)) { - if (!ch->pending) + ast_debug(1, "There %s %d available %s.\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member"); + while ((idx < avl) && (ch) && (ch != qe)) { + if (!ch->pending) idx++; - ch = ch->next; + ch = ch->next; + } + + ao2_unlock(qe->parent); + + /* If the queue entry is within avl [the number of available members] calls from the top ... + * Autofill and position check added to support autofill=no (as only calls + * from the front of the queue are valid when autofill is disabled) + */ + if (ch && idx < avl && (qe->parent->autofill || qe->pos == 1)) { + ast_debug(1, "It's our turn (%s).\n", qe->chan->name); + res = 1; + } else { + ast_debug(1, "It's not our turn (%s).\n", qe->chan->name); + res = 0; + } } - - ao2_unlock(qe->parent); - /* If the queue entry is within avl [the number of available members] calls from the top ... - * Autofill and position check added to support autofill=no (as only calls - * from the front of the queue are valid when autofill is disabled) - */ - if (ch && idx < avl && (qe->parent->autofill || qe->pos == 1)) { - ast_debug(1, "It's our turn (%s).\n", qe->chan->name); - res = 1; - } else { - ast_debug(1, "It's not our turn (%s).\n", qe->chan->name); - res = 0; - } - return res; } Index: include/asterisk/manager.h =================================================================== --- include/asterisk/manager.h (revision 340416) +++ include/asterisk/manager.h (working copy) @@ -85,6 +85,7 @@ #define EVENT_FLAG_CC (1 << 15) /* Call Completion events */ #define EVENT_FLAG_AOC (1 << 16) /* Advice Of Charge events */ #define EVENT_FLAG_TEST (1 << 17) /* Test event used to signal the Asterisk Test Suite */ +#define EVENT_FLAG_ROUTE (1 << 18) /* Advance routing */ /*@} */ /*! \brief Export manager structures */ @@ -205,6 +206,11 @@ */ int astman_verify_session_writepermissions(uint32_t ident, int perm); +/*! + * \brief Verify a session's advance call routing permissions + */ +int verify_advroute_enabled (void); + /*! \brief External routines may send asterisk manager events this way * \param category Event category, matches manager authorization \param event Event name Index: include/asterisk/queue.h =================================================================== --- include/asterisk/queue.h (revision 0) +++ include/asterisk/queue.h (revision 0) @@ -0,0 +1,47 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2005, Digium, Inc. + * + * Mark Spencer + * + * 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. + */ + +#ifndef _ASTERISK_QUEUE_H +#define _ASTERISK_QUEUE_H + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +/*! + \file + \brief Provides the advance routing capability + \ref queue.c queue code file + */ + +/*! + * \brief Routes the call to selected agent. + */ +int route_select(const char * queuename, const char * uniqueid, const char * agentid); + +/*! + * \brief Resume the call for default queue routing. + */ +int route_end(const char * queuename, const char * uniqueid); + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* _ASTERISK_QUEUE_H */ + Index: main/manager.c =================================================================== --- main/manager.c (revision 340416) +++ main/manager.c (working copy) @@ -81,6 +81,7 @@ #include "asterisk/features.h" #include "asterisk/security_events.h" #include "asterisk/aoc.h" +#include "asterisk/queue.h" #include "asterisk/stringfields.h" /*** DOCUMENTATION @@ -869,6 +870,46 @@ manager.conf will be present upon starting a new session. + + + Routes the call to selected agent. + + + + + Name of the queue which is joined by the call. + + + Uniqueu id of the call. + + + Agent id to whom call will be routed. + + + + When advance routing is enabled, advance routing app selects an + agent for the call and instructs the queue for routing through RouteSelect + + + + + Resume the call for default queue routing. + + + + + Name of the queue which is joined by the call. + + + Uniqueu id of the call. + + + + When advance routing is enabled, advance routing app can refuse + to select an agent for the call through RouteEnd. In this case default + call queue routing will be resumed. + + ***/ enum error_type { @@ -925,6 +966,7 @@ static int allowmultiplelogin = 1; static int timestampevents; static int httptimeout = 60; +static int advroute_enabled = 0; static int broken_events_action = 0; static int manager_enabled = 0; static int webmanager_enabled = 0; @@ -1201,6 +1243,7 @@ { EVENT_FLAG_AGENT, "agent" }, { EVENT_FLAG_USER, "user" }, { EVENT_FLAG_CONFIG, "config" }, + { EVENT_FLAG_ROUTE, "route" }, { EVENT_FLAG_DTMF, "dtmf" }, { EVENT_FLAG_REPORTING, "reporting" }, { EVENT_FLAG_CDR, "cdr" }, @@ -2472,7 +2515,10 @@ s->session->sessionstart = time(NULL); s->session->sessionstart_tv = ast_tvnow(); set_eventmask(s, astman_get_header(m, "Events")); - + if (s->session->writeperm & EVENT_FLAG_ROUTE) { + advroute_enabled = 1; + } + report_auth_success(s); AST_RWLIST_UNLOCK(&users); @@ -5069,6 +5115,9 @@ astman_append(&s, "Asterisk Call Manager/%s\r\n", AMI_VERSION); /* welcome prompt */ for (;;) { if ((res = do_message(&s)) < 0 || s.write_error) { + if (s.session->writeperm & EVENT_FLAG_ROUTE) { + advroute_enabled = 0; + } break; } } @@ -5576,6 +5625,107 @@ return result; } +/*! + * \brief Verify a session's advance call routing permissions + */ +int verify_advroute_enabled() +{ + return advroute_enabled; +} + +/*! \brief Routes the call to selected agent. + * + * \details + * When advance routing is enabled, advance routing app selects an + * agent for the call and instructs the queue for routing; through + * RouteSelect i.e. an AMI action. + * + * \param s manager session to send the response back to advance routing app . + * \param m message to get the action's parameters. + * + * \return Returns 0 + */ +static int action_routeselect(struct mansession *s, const struct message *m) +{ + int routeres; + const char *QueueName = astman_get_header(m, "Queue"); + const char *UniqueId = astman_get_header(m, "UniqueID"); + const char *AgentId = astman_get_header(m, "AgentID"); + if (ast_strlen_zero(QueueName) || ast_strlen_zero(UniqueId) || ast_strlen_zero(AgentId)) { + return 0; /* missing queue, uniqueid or agentid */ + } + + ast_log(LOG_NOTICE, "RouteSelect Q:%s U:%s A:%s\n", QueueName, UniqueId, AgentId); + routeres = route_select(QueueName, UniqueId, AgentId); + switch (routeres) { + case 0: + astman_send_response(s, m, "Success", "Trying"); + break; + case -1: + astman_send_error(s, m, "Queue Not Found"); + break; + case -2: + astman_send_error(s, m, "Advance Routing Not Enabled"); + break; + case -3: + astman_send_error(s, m, "Not Routable"); + break; + case -4: + astman_send_error(s, m, "Call Not Found"); + break; + default: + astman_send_error(s, m, "UnKnown"); + break; + }; + return 0; +} + +/*! \brief Resume the call for default queue routing. + * + * \details + * When advance routing is enabled, advance routing app can refuse + * to select an agent for the call; through RouteEnd i.e. an AMI action. + * In this case default call queue routing will be resumed. + * + * \param s manager session to send the response back to advance routing app . + * \param m message to get the action's parameters. + * + * \return Returns 0 + */ +static int action_routeend(struct mansession *s, const struct message *m) +{ + int routeres; + const char *QueueName = astman_get_header(m, "Queue"); + const char *UniqueId = astman_get_header(m, "UniqueId"); + if (ast_strlen_zero(QueueName) || ast_strlen_zero(UniqueId)) { + return 0; /* missing queue or uniqueid */ + } + + ast_log(LOG_NOTICE, "RouteEnd Q:%s U:%s\n", QueueName, UniqueId); + routeres = route_end(QueueName, UniqueId); + switch (routeres) { + case 0: + astman_send_response(s, m, "Success", "Done"); + break; + case -1: + astman_send_error(s, m, "Queue Not Found"); + break; + case -2: + astman_send_error(s, m, "Advance Routing Not Enabled"); + break; + case -3: + astman_send_error(s, m, "Not Routable"); + break; + case -4: + astman_send_error(s, m, "Call Not Found"); + break; + default: + astman_send_error(s, m, "UnKnown"); + break; + }; + return 0; +} + /* * convert to xml with various conversion: * mode & 1 -> lowercase; @@ -6659,6 +6809,8 @@ ast_manager_register_xml("ModuleCheck", EVENT_FLAG_SYSTEM, manager_modulecheck); ast_manager_register_xml("AOCMessage", EVENT_FLAG_AOC, action_aocmessage); ast_manager_register_xml("Filter", EVENT_FLAG_SYSTEM, action_filter); + ast_manager_register_xml("RouteSelect", EVENT_FLAG_ROUTE, action_routeselect); + ast_manager_register_xml("RouteEnd", EVENT_FLAG_ROUTE, action_routeend); ast_cli_register_multiple(cli_manager, ARRAY_LEN(cli_manager)); ast_extension_state_add(NULL, NULL, manager_state_cb, NULL); Index: main/Makefile =================================================================== --- main/Makefile (revision 340416) +++ main/Makefile (working copy) @@ -27,6 +27,8 @@ # objects use it. OBJS+=stdtime/localtime.o ifneq ($(firstword $(subst :, ,$(WEAKREF))),1) +OBJS+=../apps/app_queue.o +OBJS+=../res/res_monitor.o OBJS+=../res/res_adsi.o endif