aboutsummaryrefslogtreecommitdiffstats
path: root/1.4.23-rc4/apps/app_queue.c
diff options
context:
space:
mode:
Diffstat (limited to '1.4.23-rc4/apps/app_queue.c')
-rw-r--r--1.4.23-rc4/apps/app_queue.c5140
1 files changed, 0 insertions, 5140 deletions
diff --git a/1.4.23-rc4/apps/app_queue.c b/1.4.23-rc4/apps/app_queue.c
deleted file mode 100644
index 474ba9d88..000000000
--- a/1.4.23-rc4/apps/app_queue.c
+++ /dev/null
@@ -1,5140 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 1999 - 2006, Digium, Inc.
- *
- * Mark Spencer <markster@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief True call queues with optional send URL on answer
- *
- * \author Mark Spencer <markster@digium.com>
- *
- * \arg Config in \ref Config_qu queues.conf
- *
- * \par Development notes
- * \note 2004-11-25: Persistent Dynamic Members added by:
- * NetNation Communications (www.netnation.com)
- * Kevin Lindsay <kevinl@netnation.com>
- *
- * Each dynamic agent in each queue is now stored in the astdb.
- * When asterisk is restarted, each agent will be automatically
- * readded into their recorded queues. This feature can be
- * configured with the 'persistent_members=<1|0>' setting in the
- * '[general]' category in queues.conf. The default is on.
- *
- * \note 2004-06-04: Priorities in queues added by inAccess Networks (work funded by Hellas On Line (HOL) www.hol.gr).
- *
- * \note These features added by David C. Troy <dave@toad.net>:
- * - Per-queue holdtime calculation
- * - Estimated holdtime announcement
- * - Position announcement
- * - Abandoned/completed call counters
- * - Failout timer passed as optional app parameter
- * - Optional monitoring of calls, started when call is answered
- *
- * Patch Version 1.07 2003-12-24 01
- *
- * Added servicelevel statistic by Michiel Betel <michiel@betel.nl>
- * Added Priority jumping code for adding and removing queue members by Jonathan Stanton <asterisk@doilooklikeicare.com>
- *
- * Fixed to work with CVS as of 2004-02-25 and released as 1.07a
- * by Matthew Enger <m.enger@xi.com.au>
- *
- * \ingroup applications
- */
-
-/*** MODULEINFO
- <depend>res_monitor</depend>
- ***/
-
-#include "asterisk.h"
-
-ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
-
-#include <stdlib.h>
-#include <errno.h>
-#include <unistd.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <sys/time.h>
-#include <sys/signal.h>
-#include <netinet/in.h>
-
-#include "asterisk/lock.h"
-#include "asterisk/file.h"
-#include "asterisk/logger.h"
-#include "asterisk/channel.h"
-#include "asterisk/pbx.h"
-#include "asterisk/options.h"
-#include "asterisk/app.h"
-#include "asterisk/linkedlists.h"
-#include "asterisk/module.h"
-#include "asterisk/translate.h"
-#include "asterisk/say.h"
-#include "asterisk/features.h"
-#include "asterisk/musiconhold.h"
-#include "asterisk/cli.h"
-#include "asterisk/manager.h"
-#include "asterisk/config.h"
-#include "asterisk/monitor.h"
-#include "asterisk/utils.h"
-#include "asterisk/causes.h"
-#include "asterisk/astdb.h"
-#include "asterisk/devicestate.h"
-#include "asterisk/stringfields.h"
-#include "asterisk/astobj2.h"
-#include "asterisk/global_datastores.h"
-
-/* Please read before modifying this file.
- * There are three locks which are regularly used
- * throughout this file, the queue list lock, the lock
- * for each individual queue, and the interface list lock.
- * Please be extra careful to always lock in the following order
- * 1) queue list lock
- * 2) individual queue lock
- * 3) interface list lock
- * This order has sort of "evolved" over the lifetime of this
- * application, but it is now in place this way, so please adhere
- * to this order!
- */
-
-
-enum {
- QUEUE_STRATEGY_RINGALL = 0,
- QUEUE_STRATEGY_ROUNDROBIN,
- QUEUE_STRATEGY_LEASTRECENT,
- QUEUE_STRATEGY_FEWESTCALLS,
- QUEUE_STRATEGY_RANDOM,
- QUEUE_STRATEGY_RRMEMORY
-};
-
-static struct strategy {
- int strategy;
- char *name;
-} strategies[] = {
- { QUEUE_STRATEGY_RINGALL, "ringall" },
- { QUEUE_STRATEGY_ROUNDROBIN, "roundrobin" },
- { QUEUE_STRATEGY_LEASTRECENT, "leastrecent" },
- { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
- { QUEUE_STRATEGY_RANDOM, "random" },
- { QUEUE_STRATEGY_RRMEMORY, "rrmemory" },
-};
-
-#define DEFAULT_RETRY 5
-#define DEFAULT_TIMEOUT 15
-#define RECHECK 1 /* Recheck every second to see we we're at the top yet */
-#define MAX_PERIODIC_ANNOUNCEMENTS 10 /* The maximum periodic announcements we can have */
-
-#define RES_OKAY 0 /* Action completed */
-#define RES_EXISTS (-1) /* Entry already exists */
-#define RES_OUTOFMEMORY (-2) /* Out of memory */
-#define RES_NOSUCHQUEUE (-3) /* No such queue */
-#define RES_NOT_DYNAMIC (-4) /* Member is not dynamic */
-
-static char *app = "Queue";
-
-static char *synopsis = "Queue a call for a call queue";
-
-static char *descrip =
-" Queue(queuename[|options[|URL][|announceoverride][|timeout][|AGI]):\n"
-"Queues an incoming call in a particular call queue as defined in queues.conf.\n"
-"This application will return to the dialplan if the queue does not exist, or\n"
-"any of the join options cause the caller to not enter the queue.\n"
-"The option string may contain zero or more of the following characters:\n"
-" 'd' -- data-quality (modem) call (minimum delay).\n"
-" 'h' -- allow callee to hang up by hitting '*', or whatver disconnect sequence\n"
-" defined in the featuremap section in features.conf.\n"
-" 'H' -- allow caller to hang up by hitting '*', or whatever disconnect sequence\n"
-" defined in the featuremap section in features.conf.\n"
-" 'n' -- no retries on the timeout; will exit this application and \n"
-" go to the next step.\n"
-" 'i' -- ignore call forward requests from queue members and do nothing\n"
-" when they are requested.\n"
-" 'r' -- ring instead of playing MOH\n"
-" 't' -- allow the called user transfer the calling user by pressing '#' or\n"
-" whatever blindxfer sequence defined in the featuremap section in\n"
-" features.conf\n"
-" 'T' -- to allow the calling user to transfer the call by pressing '#' or\n"
-" whatever blindxfer sequence defined in the featuremap section in\n"
-" features.conf\n"
-" 'w' -- allow the called user to write the conversation to disk via Monitor\n"
-" by pressing the automon sequence defined in the featuremap section in\n"
-" features.conf\n"
-" 'W' -- allow the calling user to write the conversation to disk via Monitor\n"
-" by pressing the automon sequence defined in the featuremap section in\n"
-" features.conf\n"
-" In addition to transferring the call, a call may be parked and then picked\n"
-"up by another user, by transferring to the parking lot extension. See features.conf.\n"
-" The optional URL will be sent to the called party if the channel supports\n"
-"it.\n"
-" The optional AGI parameter will setup an AGI script to be executed on the \n"
-"calling party's channel once they are connected to a queue member.\n"
-" The timeout will cause the queue to fail out after a specified number of\n"
-"seconds, checked between each queues.conf 'timeout' and 'retry' cycle.\n"
-" This application sets the following channel variable upon completion:\n"
-" QUEUESTATUS The status of the call as a text string, one of\n"
-" TIMEOUT | FULL | JOINEMPTY | LEAVEEMPTY | JOINUNAVAIL | LEAVEUNAVAIL\n";
-
-static char *app_aqm = "AddQueueMember" ;
-static char *app_aqm_synopsis = "Dynamically adds queue members" ;
-static char *app_aqm_descrip =
-" AddQueueMember(queuename[|interface[|penalty[|options[|membername]]]]):\n"
-"Dynamically adds interface to an existing queue.\n"
-"If the interface is already in the queue and there exists an n+101 priority\n"
-"then it will then jump to this priority. Otherwise it will return an error\n"
-"The option string may contain zero or more of the following characters:\n"
-" 'j' -- jump to +101 priority when appropriate.\n"
-" This application sets the following channel variable upon completion:\n"
-" AQMSTATUS The status of the attempt to add a queue member as a \n"
-" text string, one of\n"
-" ADDED | MEMBERALREADY | NOSUCHQUEUE \n"
-"Example: AddQueueMember(techsupport|SIP/3000)\n"
-"";
-
-static char *app_rqm = "RemoveQueueMember" ;
-static char *app_rqm_synopsis = "Dynamically removes queue members" ;
-static char *app_rqm_descrip =
-" RemoveQueueMember(queuename[|interface[|options]]):\n"
-"Dynamically removes interface to an existing queue\n"
-"If the interface is NOT in the queue and there exists an n+101 priority\n"
-"then it will then jump to this priority. Otherwise it will return an error\n"
-"The option string may contain zero or more of the following characters:\n"
-" 'j' -- jump to +101 priority when appropriate.\n"
-" This application sets the following channel variable upon completion:\n"
-" RQMSTATUS The status of the attempt to remove a queue member as a\n"
-" text string, one of\n"
-" REMOVED | NOTINQUEUE | NOSUCHQUEUE \n"
-"Example: RemoveQueueMember(techsupport|SIP/3000)\n"
-"";
-
-static char *app_pqm = "PauseQueueMember" ;
-static char *app_pqm_synopsis = "Pauses a queue member" ;
-static char *app_pqm_descrip =
-" PauseQueueMember([queuename]|interface[|options]):\n"
-"Pauses (blocks calls for) a queue member.\n"
-"The given interface will be paused in the given queue. This prevents\n"
-"any calls from being sent from the queue to the interface until it is\n"
-"unpaused with UnpauseQueueMember or the manager interface. If no\n"
-"queuename is given, the interface is paused in every queue it is a\n"
-"member of. If the interface is not in the named queue, or if no queue\n"
-"is given and the interface is not in any queue, it will jump to\n"
-"priority n+101, if it exists and the appropriate options are set.\n"
-"The application will fail if the interface is not found and no extension\n"
-"to jump to exists.\n"
-"The option string may contain zero or more of the following characters:\n"
-" 'j' -- jump to +101 priority when appropriate.\n"
-" This application sets the following channel variable upon completion:\n"
-" PQMSTATUS The status of the attempt to pause a queue member as a\n"
-" text string, one of\n"
-" PAUSED | NOTFOUND\n"
-"Example: PauseQueueMember(|SIP/3000)\n";
-
-static char *app_upqm = "UnpauseQueueMember" ;
-static char *app_upqm_synopsis = "Unpauses a queue member" ;
-static char *app_upqm_descrip =
-" UnpauseQueueMember([queuename]|interface[|options]):\n"
-"Unpauses (resumes calls to) a queue member.\n"
-"This is the counterpart to PauseQueueMember and operates exactly the\n"
-"same way, except it unpauses instead of pausing the given interface.\n"
-"The option string may contain zero or more of the following characters:\n"
-" 'j' -- jump to +101 priority when appropriate.\n"
-" This application sets the following channel variable upon completion:\n"
-" UPQMSTATUS The status of the attempt to unpause a queue \n"
-" member as a text string, one of\n"
-" UNPAUSED | NOTFOUND\n"
-"Example: UnpauseQueueMember(|SIP/3000)\n";
-
-static char *app_ql = "QueueLog" ;
-static char *app_ql_synopsis = "Writes to the queue_log" ;
-static char *app_ql_descrip =
-" QueueLog(queuename|uniqueid|agent|event[|additionalinfo]):\n"
-"Allows you to write your own events into the queue log\n"
-"Example: QueueLog(101|${UNIQUEID}|${AGENT}|WENTONBREAK|600)\n";
-
-/*! \brief Persistent Members astdb family */
-static const char *pm_family = "Queue/PersistentMembers";
-/* The maximum length of each persistent member queue database entry */
-#define PM_MAX_LEN 8192
-
-/*! \brief queues.conf [general] option */
-static int queue_persistent_members = 0;
-
-/*! \brief queues.conf per-queue weight option */
-static int use_weight = 0;
-
-/*! \brief queues.conf [general] option */
-static int autofill_default = 0;
-
-/*! \brief queues.conf [general] option */
-static int montype_default = 0;
-
-enum queue_result {
- QUEUE_UNKNOWN = 0,
- QUEUE_TIMEOUT = 1,
- QUEUE_JOINEMPTY = 2,
- QUEUE_LEAVEEMPTY = 3,
- QUEUE_JOINUNAVAIL = 4,
- QUEUE_LEAVEUNAVAIL = 5,
- QUEUE_FULL = 6,
-};
-
-const struct {
- enum queue_result id;
- char *text;
-} queue_results[] = {
- { QUEUE_UNKNOWN, "UNKNOWN" },
- { QUEUE_TIMEOUT, "TIMEOUT" },
- { QUEUE_JOINEMPTY,"JOINEMPTY" },
- { QUEUE_LEAVEEMPTY, "LEAVEEMPTY" },
- { QUEUE_JOINUNAVAIL, "JOINUNAVAIL" },
- { QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },
- { QUEUE_FULL, "FULL" },
-};
-
-/*! \brief We define a custom "local user" structure because we
- use it not only for keeping track of what is in use but
- also for keeping track of who we're dialing.
-
- There are two "links" defined in this structure, q_next and call_next.
- q_next links ALL defined callattempt structures into a linked list. call_next is
- a link which allows for a subset of the callattempts to be traversed. This subset
- is used in wait_for_answer so that irrelevant callattempts are not traversed. This
- also is helpful so that queue logs are always accurate in the case where a call to
- a member times out, especially if using the ringall strategy. */
-
-struct callattempt {
- struct callattempt *q_next;
- struct callattempt *call_next;
- struct ast_channel *chan;
- char interface[256];
- int stillgoing;
- int metric;
- int oldstatus;
- time_t lastcall;
- struct member *member;
-};
-
-
-struct queue_ent {
- struct call_queue *parent; /*!< What queue is our parent */
- char moh[80]; /*!< Name of musiconhold to be used */
- char announce[80]; /*!< 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 valid_digits; /*!< Digits entered correspond to valid extension. Exited */
- int pos; /*!< Where we are in the queue */
- int prio; /*!< Our priority */
- int last_pos_said; /*!< Last position we told the user */
- time_t last_periodic_announce_time; /*!< The last time we played a periodic announcement */
- int last_periodic_announce_sound; /*!< The last periodic announcement we made */
- time_t last_pos; /*!< Last time we told the user their position */
- int opos; /*!< Where we started in the queue */
- int handled; /*!< Whether our call was handled */
- int pending; /*!< Non-zero if we are attempting to call a member */
- int max_penalty; /*!< Limit the members that can take this call to this penalty or lower */
- time_t start; /*!< When we started holding */
- time_t expire; /*!< When this entry should expire (time out of queue) */
- struct ast_channel *chan; /*!< Our channel */
- struct queue_ent *next; /*!< The next queue entry */
-};
-
-struct member {
- char interface[80]; /*!< Technology/Location */
- char membername[80]; /*!< Member name to use in queue logs */
- int penalty; /*!< Are we a last resort? */
- int calls; /*!< Number of calls serviced by this member */
- int dynamic; /*!< Are we dynamically added? */
- int realtime; /*!< Is this member realtime? */
- int status; /*!< Status of queue member */
- int paused; /*!< Are we paused (not accepting calls)? */
- time_t lastcall; /*!< When last successful call was hungup */
- unsigned int dead:1; /*!< Used to detect members deleted in realtime */
- unsigned int delme:1; /*!< Flag to delete entry on reload */
-};
-
-struct member_interface {
- char interface[80];
- AST_LIST_ENTRY(member_interface) list; /*!< Next call queue */
-};
-
-static AST_LIST_HEAD_STATIC(interfaces, member_interface);
-
-/* values used in multi-bit flags in call_queue */
-#define QUEUE_EMPTY_NORMAL 1
-#define QUEUE_EMPTY_STRICT 2
-#define ANNOUNCEHOLDTIME_ALWAYS 1
-#define ANNOUNCEHOLDTIME_ONCE 2
-#define QUEUE_EVENT_VARIABLES 3
-
-struct call_queue {
- ast_mutex_t lock;
- char name[80]; /*!< Name */
- char moh[80]; /*!< Music On Hold class to be used */
- char announce[80]; /*!< Announcement to play when call is answered */
- char context[AST_MAX_CONTEXT]; /*!< Exit context */
- unsigned int monjoin:1;
- unsigned int dead:1;
- unsigned int joinempty:2;
- unsigned int eventwhencalled:2;
- unsigned int leavewhenempty:2;
- unsigned int ringinuse:1;
- unsigned int setinterfacevar:1;
- unsigned int reportholdtime:1;
- unsigned int wrapped:1;
- unsigned int timeoutrestart:1;
- unsigned int announceholdtime:2;
- int strategy:4;
- unsigned int maskmemberstatus:1;
- unsigned int realtime:1;
- unsigned int found:1;
- int announcefrequency; /*!< How often to announce their position */
- int periodicannouncefrequency; /*!< How often to play periodic announcement */
- int roundingseconds; /*!< How many seconds do we round to? */
- int holdtime; /*!< Current avg holdtime, based on an exponential average */
- int callscompleted; /*!< Number of queue calls completed */
- int callsabandoned; /*!< Number of queue calls abandoned */
- int servicelevel; /*!< seconds setting for servicelevel*/
- int callscompletedinsl; /*!< Number of calls answered with servicelevel*/
- char monfmt[8]; /*!< Format to use when recording calls */
- int montype; /*!< Monitor type Monitor vs. MixMonitor */
- char sound_next[80]; /*!< Sound file: "Your call is now first in line" (def. queue-youarenext) */
- char sound_thereare[80]; /*!< Sound file: "There are currently" (def. queue-thereare) */
- char sound_calls[80]; /*!< Sound file: "calls waiting to speak to a representative." (def. queue-callswaiting)*/
- char sound_holdtime[80]; /*!< Sound file: "The current estimated total holdtime is" (def. queue-holdtime) */
- char sound_minutes[80]; /*!< Sound file: "minutes." (def. queue-minutes) */
- char sound_lessthan[80]; /*!< Sound file: "less-than" (def. queue-lessthan) */
- char sound_seconds[80]; /*!< Sound file: "seconds." (def. queue-seconds) */
- char sound_thanks[80]; /*!< Sound file: "Thank you for your patience." (def. queue-thankyou) */
- char sound_reporthold[80]; /*!< Sound file: "Hold time" (def. queue-reporthold) */
- char sound_periodicannounce[MAX_PERIODIC_ANNOUNCEMENTS][80];/*!< Sound files: Custom announce, no default */
-
- int count; /*!< How many entries */
- int maxlen; /*!< Max number of entries */
- int wrapuptime; /*!< Wrapup Time */
-
- int retry; /*!< Retry calling everyone after this amount of time */
- int timeout; /*!< How long to wait for an answer */
- int weight; /*!< Respective weight */
- int autopause; /*!< Auto pause queue members if they fail to answer */
-
- /* Queue strategy things */
- int rrpos; /*!< Round Robin - position */
- int memberdelay; /*!< Seconds to delay connecting member to caller */
- int autofill; /*!< Ignore the head call status and ring an available agent */
-
- struct ao2_container *members; /*!< Head of the list of members */
- /*!
- * \brief Number of members _logged in_
- * \note There will be members in the members container that are not logged
- * in, so this can not simply be replaced with ao2_container_count().
- */
- int membercount;
- struct queue_ent *head; /*!< Head of the list of callers */
- AST_LIST_ENTRY(call_queue) list; /*!< Next call queue */
-};
-
-static AST_LIST_HEAD_STATIC(queues, call_queue);
-
-static int set_member_paused(const char *queuename, const char *interface, int paused);
-
-static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
-
-static void rr_dep_warning(void)
-{
- static unsigned int warned = 0;
-
- if (!warned) {
- ast_log(LOG_NOTICE, "The 'roundrobin' queue strategy is deprecated. Please use the 'rrmemory' strategy instead.\n");
- warned = 1;
- }
-}
-
-static void monjoin_dep_warning(void)
-{
- static unsigned int warned = 0;
- if (!warned) {
- ast_log(LOG_NOTICE, "The 'monitor-join' queue option is deprecated. Please use monitor-type=mixmonitor instead.\n");
- warned = 1;
- }
-}
-/*! \brief sets the QUEUESTATUS channel variable */
-static void set_queue_result(struct ast_channel *chan, enum queue_result res)
-{
- int i;
-
- for (i = 0; i < sizeof(queue_results) / sizeof(queue_results[0]); i++) {
- if (queue_results[i].id == res) {
- pbx_builtin_setvar_helper(chan, "QUEUESTATUS", queue_results[i].text);
- return;
- }
- }
-}
-
-static char *int2strat(int strategy)
-{
- int x;
-
- for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) {
- if (strategy == strategies[x].strategy)
- return strategies[x].name;
- }
-
- return "<unknown>";
-}
-
-static int strat2int(const char *strategy)
-{
- int x;
-
- for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) {
- if (!strcasecmp(strategy, strategies[x].name))
- return strategies[x].strategy;
- }
-
- return -1;
-}
-
-/*! \brief Insert the 'new' entry after the 'prev' entry of queue 'q' */
-static inline void insert_entry(struct call_queue *q, struct queue_ent *prev, struct queue_ent *new, int *pos)
-{
- struct queue_ent *cur;
-
- if (!q || !new)
- return;
- if (prev) {
- cur = prev->next;
- prev->next = new;
- } else {
- cur = q->head;
- q->head = new;
- }
- new->next = cur;
- new->parent = q;
- new->pos = ++(*pos);
- new->opos = *pos;
-}
-
-enum queue_member_status {
- QUEUE_NO_MEMBERS,
- QUEUE_NO_REACHABLE_MEMBERS,
- QUEUE_NORMAL
-};
-
-/*! \brief Check if members are available
- *
- * This function checks to see if members are available to be called. If any member
- * is available, the function immediately returns QUEUE_NORMAL. If no members are available,
- * the appropriate reason why is returned
- */
-static enum queue_member_status get_member_status(struct call_queue *q, int max_penalty)
-{
- struct member *member;
- struct ao2_iterator mem_iter;
- enum queue_member_status result = QUEUE_NO_MEMBERS;
-
- ast_mutex_lock(&q->lock);
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((member = ao2_iterator_next(&mem_iter))) {
- if (max_penalty && (member->penalty > max_penalty)) {
- ao2_ref(member, -1);
- continue;
- }
-
- if (member->paused) {
- ao2_ref(member, -1);
- continue;
- }
-
- switch (member->status) {
- case AST_DEVICE_INVALID:
- /* nothing to do */
- ao2_ref(member, -1);
- break;
- case AST_DEVICE_UNAVAILABLE:
- result = QUEUE_NO_REACHABLE_MEMBERS;
- ao2_ref(member, -1);
- break;
- default:
- ast_mutex_unlock(&q->lock);
- ao2_ref(member, -1);
- return QUEUE_NORMAL;
- }
- }
-
- ast_mutex_unlock(&q->lock);
- return result;
-}
-
-struct statechange {
- AST_LIST_ENTRY(statechange) entry;
- int state;
- char dev[0];
-};
-
-static int update_status(const char *interface, const int status)
-{
- struct member *cur;
- struct ao2_iterator mem_iter;
- struct call_queue *q;
-
- AST_LIST_LOCK(&queues);
- AST_LIST_TRAVERSE(&queues, q, list) {
- ast_mutex_lock(&q->lock);
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((cur = ao2_iterator_next(&mem_iter))) {
- char *tmp_interface;
- char *slash_pos;
- tmp_interface = ast_strdupa(cur->interface);
- if ((slash_pos = strchr(tmp_interface, '/')))
- if ((slash_pos = strchr(slash_pos + 1, '/')))
- *slash_pos = '\0';
-
- if (strcasecmp(interface, tmp_interface)) {
- ao2_ref(cur, -1);
- continue;
- }
-
- if (cur->status != status) {
- cur->status = status;
- if (q->maskmemberstatus) {
- ao2_ref(cur, -1);
- continue;
- }
-
- manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
- "Queue: %s\r\n"
- "Location: %s\r\n"
- "MemberName: %s\r\n"
- "Membership: %s\r\n"
- "Penalty: %d\r\n"
- "CallsTaken: %d\r\n"
- "LastCall: %d\r\n"
- "Status: %d\r\n"
- "Paused: %d\r\n",
- q->name, cur->interface, cur->membername, cur->dynamic ? "dynamic" : cur->realtime ? "realtime" : "static",
- cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused);
- }
- ao2_ref(cur, -1);
- }
- ast_mutex_unlock(&q->lock);
- }
- AST_LIST_UNLOCK(&queues);
-
- return 0;
-}
-
-/*! \brief set a member's status based on device state of that member's interface*/
-static void *handle_statechange(struct statechange *sc)
-{
- struct member_interface *curint;
- char *loc;
- char *technology;
-
- technology = ast_strdupa(sc->dev);
- loc = strchr(technology, '/');
- if (loc) {
- *loc++ = '\0';
- } else {
- return NULL;
- }
-
- AST_LIST_LOCK(&interfaces);
- AST_LIST_TRAVERSE(&interfaces, curint, list) {
- char *interface;
- char *slash_pos;
- interface = ast_strdupa(curint->interface);
- if ((slash_pos = strchr(interface, '/')))
- if ((slash_pos = strchr(slash_pos + 1, '/')))
- *slash_pos = '\0';
-
- if (!strcasecmp(interface, sc->dev))
- break;
- }
- AST_LIST_UNLOCK(&interfaces);
-
- if (!curint) {
- if (option_debug > 2)
- ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n", technology, loc, sc->state, devstate2str(sc->state));
- return NULL;
- }
-
- if (option_debug)
- ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s)\n", technology, loc, sc->state, devstate2str(sc->state));
-
- update_status(sc->dev, sc->state);
-
- return NULL;
-}
-
-/*!
- * \brief Data used by the device state thread
- */
-static struct {
- /*! Set to 1 to stop the thread */
- unsigned int stop:1;
- /*! The device state monitoring thread */
- pthread_t thread;
- /*! Lock for the state change queue */
- ast_mutex_t lock;
- /*! Condition for the state change queue */
- ast_cond_t cond;
- /*! Queue of state changes */
- AST_LIST_HEAD_NOLOCK(, statechange) state_change_q;
-} device_state = {
- .thread = AST_PTHREADT_NULL,
-};
-
-/*! \brief Consumer of the statechange queue */
-static void *device_state_thread(void *data)
-{
- struct statechange *sc = NULL;
-
- while (!device_state.stop) {
- ast_mutex_lock(&device_state.lock);
- if (!(sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry))) {
- ast_cond_wait(&device_state.cond, &device_state.lock);
- sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry);
- }
- ast_mutex_unlock(&device_state.lock);
-
- /* Check to see if we were woken up to see the request to stop */
- if (device_state.stop)
- break;
-
- if (!sc)
- continue;
-
- handle_statechange(sc);
-
- free(sc);
- sc = NULL;
- }
-
- if (sc)
- free(sc);
-
- while ((sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry)))
- free(sc);
-
- return NULL;
-}
-/*! \brief Producer of the statechange queue */
-static int statechange_queue(const char *dev, int state, void *ign)
-{
- struct statechange *sc;
-
- if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(dev) + 1)))
- return 0;
-
- sc->state = state;
- strcpy(sc->dev, dev);
-
- ast_mutex_lock(&device_state.lock);
- AST_LIST_INSERT_TAIL(&device_state.state_change_q, sc, entry);
- ast_cond_signal(&device_state.cond);
- ast_mutex_unlock(&device_state.lock);
-
- return 0;
-}
-/*! \brief allocate space for new queue member and set fields based on parameters passed */
-static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused)
-{
- struct member *cur;
-
- if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
- cur->penalty = penalty;
- cur->paused = paused;
- ast_copy_string(cur->interface, interface, sizeof(cur->interface));
- if (!ast_strlen_zero(membername))
- ast_copy_string(cur->membername, membername, sizeof(cur->membername));
- else
- ast_copy_string(cur->membername, interface, sizeof(cur->membername));
- if (!strchr(cur->interface, '/'))
- ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
- cur->status = ast_device_state(interface);
- }
-
- return cur;
-}
-
-static struct call_queue *alloc_queue(const char *queuename)
-{
- struct call_queue *q;
-
- if ((q = ast_calloc(1, sizeof(*q)))) {
- ast_mutex_init(&q->lock);
- ast_copy_string(q->name, queuename, sizeof(q->name));
- }
- return q;
-}
-
-static int compress_char(const char c)
-{
- if (c < 32)
- return 0;
- else if (c > 96)
- return c - 64;
- else
- return c - 32;
-}
-
-static int member_hash_fn(const void *obj, const int flags)
-{
- const struct member *mem = obj;
- const char *chname = strchr(mem->interface, '/');
- int ret = 0, i;
- if (!chname)
- chname = mem->interface;
- for (i = 0; i < 5 && chname[i]; i++)
- ret += compress_char(chname[i]) << (i * 6);
- return ret;
-}
-
-static int member_cmp_fn(void *obj1, void *obj2, int flags)
-{
- struct member *mem1 = obj1, *mem2 = obj2;
- return strcmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH | CMP_STOP;
-}
-
-static void init_queue(struct call_queue *q)
-{
- int i;
-
- q->dead = 0;
- q->retry = DEFAULT_RETRY;
- q->timeout = -1;
- q->maxlen = 0;
- q->announcefrequency = 0;
- q->announceholdtime = 0;
- q->roundingseconds = 0; /* Default - don't announce seconds */
- q->servicelevel = 0;
- q->ringinuse = 1;
- q->setinterfacevar = 0;
- q->autofill = autofill_default;
- q->montype = montype_default;
- q->moh[0] = '\0';
- q->announce[0] = '\0';
- q->context[0] = '\0';
- q->monfmt[0] = '\0';
- q->periodicannouncefrequency = 0;
- q->reportholdtime = 0;
- q->monjoin = 0;
- q->wrapuptime = 0;
- q->joinempty = 0;
- q->leavewhenempty = 0;
- q->memberdelay = 0;
- q->maskmemberstatus = 0;
- q->eventwhencalled = 0;
- q->weight = 0;
- q->timeoutrestart = 0;
- if (!q->members)
- q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
- q->membercount = 0;
- q->found = 1;
- ast_copy_string(q->sound_next, "queue-youarenext", sizeof(q->sound_next));
- ast_copy_string(q->sound_thereare, "queue-thereare", sizeof(q->sound_thereare));
- ast_copy_string(q->sound_calls, "queue-callswaiting", sizeof(q->sound_calls));
- ast_copy_string(q->sound_holdtime, "queue-holdtime", sizeof(q->sound_holdtime));
- ast_copy_string(q->sound_minutes, "queue-minutes", sizeof(q->sound_minutes));
- ast_copy_string(q->sound_seconds, "queue-seconds", sizeof(q->sound_seconds));
- ast_copy_string(q->sound_thanks, "queue-thankyou", sizeof(q->sound_thanks));
- ast_copy_string(q->sound_lessthan, "queue-less-than", sizeof(q->sound_lessthan));
- ast_copy_string(q->sound_reporthold, "queue-reporthold", sizeof(q->sound_reporthold));
- ast_copy_string(q->sound_periodicannounce[0], "queue-periodic-announce", sizeof(q->sound_periodicannounce[0]));
- for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
- q->sound_periodicannounce[i][0]='\0';
- }
-}
-
-static void clear_queue(struct call_queue *q)
-{
- q->holdtime = 0;
- q->callscompleted = 0;
- q->callsabandoned = 0;
- q->callscompletedinsl = 0;
- q->wrapuptime = 0;
-}
-
-static int add_to_interfaces(const char *interface)
-{
- struct member_interface *curint;
-
- AST_LIST_LOCK(&interfaces);
- AST_LIST_TRAVERSE(&interfaces, curint, list) {
- if (!strcasecmp(curint->interface, interface))
- break;
- }
-
- if (curint) {
- AST_LIST_UNLOCK(&interfaces);
- return 0;
- }
-
- if (option_debug)
- ast_log(LOG_DEBUG, "Adding %s to the list of interfaces that make up all of our queue members.\n", interface);
-
- if ((curint = ast_calloc(1, sizeof(*curint)))) {
- ast_copy_string(curint->interface, interface, sizeof(curint->interface));
- AST_LIST_INSERT_HEAD(&interfaces, curint, list);
- }
- AST_LIST_UNLOCK(&interfaces);
-
- return 0;
-}
-
-static int interface_exists_global(const char *interface)
-{
- struct call_queue *q;
- struct member *mem, tmpmem;
- int ret = 0;
-
- ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
-
- AST_LIST_LOCK(&queues);
- AST_LIST_TRAVERSE(&queues, q, list) {
- ast_mutex_lock(&q->lock);
- if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) {
- ao2_ref(mem, -1);
- ret = 1;
- }
- ast_mutex_unlock(&q->lock);
- if (ret)
- break;
- }
- AST_LIST_UNLOCK(&queues);
-
- return ret;
-}
-
-static int remove_from_interfaces(const char *interface)
-{
- struct member_interface *curint;
-
- if (interface_exists_global(interface))
- return 0;
-
- AST_LIST_LOCK(&interfaces);
- AST_LIST_TRAVERSE_SAFE_BEGIN(&interfaces, curint, list) {
- if (!strcasecmp(curint->interface, interface)) {
- if (option_debug)
- ast_log(LOG_DEBUG, "Removing %s from the list of interfaces that make up all of our queue members.\n", interface);
- AST_LIST_REMOVE_CURRENT(&interfaces, list);
- free(curint);
- break;
- }
- }
- AST_LIST_TRAVERSE_SAFE_END;
- AST_LIST_UNLOCK(&interfaces);
-
- return 0;
-}
-
-static void clear_and_free_interfaces(void)
-{
- struct member_interface *curint;
-
- AST_LIST_LOCK(&interfaces);
- while ((curint = AST_LIST_REMOVE_HEAD(&interfaces, list)))
- free(curint);
- AST_LIST_UNLOCK(&interfaces);
-}
-
-/*! \brief Configure a queue parameter.
-\par
- For error reporting, line number is passed for .conf static configuration.
- For Realtime queues, linenum is -1.
- The failunknown flag is set for config files (and static realtime) to show
- errors for unknown parameters. It is cleared for dynamic realtime to allow
- extra fields in the tables. */
-static void queue_set_param(struct call_queue *q, const char *param, const char *val, int linenum, int failunknown)
-{
- if (!strcasecmp(param, "musicclass") ||
- !strcasecmp(param, "music") || !strcasecmp(param, "musiconhold")) {
- ast_copy_string(q->moh, val, sizeof(q->moh));
- } else if (!strcasecmp(param, "announce")) {
- ast_copy_string(q->announce, val, sizeof(q->announce));
- } else if (!strcasecmp(param, "context")) {
- ast_copy_string(q->context, val, sizeof(q->context));
- } else if (!strcasecmp(param, "timeout")) {
- q->timeout = atoi(val);
- if (q->timeout < 0)
- q->timeout = DEFAULT_TIMEOUT;
- } else if (!strcasecmp(param, "ringinuse")) {
- q->ringinuse = ast_true(val);
- } else if (!strcasecmp(param, "setinterfacevar")) {
- q->setinterfacevar = ast_true(val);
- } else if (!strcasecmp(param, "monitor-join")) {
- monjoin_dep_warning();
- q->monjoin = ast_true(val);
- } else if (!strcasecmp(param, "monitor-format")) {
- ast_copy_string(q->monfmt, val, sizeof(q->monfmt));
- } else if (!strcasecmp(param, "queue-youarenext")) {
- ast_copy_string(q->sound_next, val, sizeof(q->sound_next));
- } else if (!strcasecmp(param, "queue-thereare")) {
- ast_copy_string(q->sound_thereare, val, sizeof(q->sound_thereare));
- } else if (!strcasecmp(param, "queue-callswaiting")) {
- ast_copy_string(q->sound_calls, val, sizeof(q->sound_calls));
- } else if (!strcasecmp(param, "queue-holdtime")) {
- ast_copy_string(q->sound_holdtime, val, sizeof(q->sound_holdtime));
- } else if (!strcasecmp(param, "queue-minutes")) {
- ast_copy_string(q->sound_minutes, val, sizeof(q->sound_minutes));
- } else if (!strcasecmp(param, "queue-seconds")) {
- ast_copy_string(q->sound_seconds, val, sizeof(q->sound_seconds));
- } else if (!strcasecmp(param, "queue-lessthan")) {
- ast_copy_string(q->sound_lessthan, val, sizeof(q->sound_lessthan));
- } else if (!strcasecmp(param, "queue-thankyou")) {
- ast_copy_string(q->sound_thanks, val, sizeof(q->sound_thanks));
- } else if (!strcasecmp(param, "queue-reporthold")) {
- ast_copy_string(q->sound_reporthold, val, sizeof(q->sound_reporthold));
- } else if (!strcasecmp(param, "announce-frequency")) {
- q->announcefrequency = atoi(val);
- } else if (!strcasecmp(param, "announce-round-seconds")) {
- q->roundingseconds = atoi(val);
- if (q->roundingseconds>60 || q->roundingseconds<0) {
- if (linenum >= 0) {
- ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
- "using 0 instead for queue '%s' at line %d of queues.conf\n",
- val, param, q->name, linenum);
- } else {
- ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
- "using 0 instead for queue '%s'\n", val, param, q->name);
- }
- q->roundingseconds=0;
- }
- } else if (!strcasecmp(param, "announce-holdtime")) {
- if (!strcasecmp(val, "once"))
- q->announceholdtime = ANNOUNCEHOLDTIME_ONCE;
- else if (ast_true(val))
- q->announceholdtime = ANNOUNCEHOLDTIME_ALWAYS;
- else
- q->announceholdtime = 0;
- } else if (!strcasecmp(param, "periodic-announce")) {
- if (strchr(val, '|')) {
- char *s, *buf = ast_strdupa(val);
- unsigned int i = 0;
-
- while ((s = strsep(&buf, "|"))) {
- ast_copy_string(q->sound_periodicannounce[i], s, sizeof(q->sound_periodicannounce[i]));
- i++;
- if (i == MAX_PERIODIC_ANNOUNCEMENTS)
- break;
- }
- } else {
- ast_copy_string(q->sound_periodicannounce[0], val, sizeof(q->sound_periodicannounce[0]));
- }
- } else if (!strcasecmp(param, "periodic-announce-frequency")) {
- q->periodicannouncefrequency = atoi(val);
- } else if (!strcasecmp(param, "retry")) {
- q->retry = atoi(val);
- if (q->retry <= 0)
- q->retry = DEFAULT_RETRY;
- } else if (!strcasecmp(param, "wrapuptime")) {
- q->wrapuptime = atoi(val);
- } else if (!strcasecmp(param, "autofill")) {
- q->autofill = ast_true(val);
- } else if (!strcasecmp(param, "monitor-type")) {
- if (!strcasecmp(val, "mixmonitor"))
- q->montype = 1;
- } else if (!strcasecmp(param, "autopause")) {
- q->autopause = ast_true(val);
- } else if (!strcasecmp(param, "maxlen")) {
- q->maxlen = atoi(val);
- if (q->maxlen < 0)
- q->maxlen = 0;
- } else if (!strcasecmp(param, "servicelevel")) {
- q->servicelevel= atoi(val);
- } else if (!strcasecmp(param, "strategy")) {
- q->strategy = strat2int(val);
- if (q->strategy < 0) {
- ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
- val, q->name);
- q->strategy = QUEUE_STRATEGY_RINGALL;
- }
- } else if (!strcasecmp(param, "joinempty")) {
- if (!strcasecmp(val, "strict"))
- q->joinempty = QUEUE_EMPTY_STRICT;
- else if (ast_true(val))
- q->joinempty = QUEUE_EMPTY_NORMAL;
- else
- q->joinempty = 0;
- } else if (!strcasecmp(param, "leavewhenempty")) {
- if (!strcasecmp(val, "strict"))
- q->leavewhenempty = QUEUE_EMPTY_STRICT;
- else if (ast_true(val))
- q->leavewhenempty = QUEUE_EMPTY_NORMAL;
- else
- q->leavewhenempty = 0;
- } else if (!strcasecmp(param, "eventmemberstatus")) {
- q->maskmemberstatus = !ast_true(val);
- } else if (!strcasecmp(param, "eventwhencalled")) {
- if (!strcasecmp(val, "vars")) {
- q->eventwhencalled = QUEUE_EVENT_VARIABLES;
- } else {
- q->eventwhencalled = ast_true(val) ? 1 : 0;
- }
- } else if (!strcasecmp(param, "reportholdtime")) {
- q->reportholdtime = ast_true(val);
- } else if (!strcasecmp(param, "memberdelay")) {
- q->memberdelay = atoi(val);
- } else if (!strcasecmp(param, "weight")) {
- q->weight = atoi(val);
- if (q->weight)
- use_weight++;
- /* With Realtime queues, if the last queue using weights is deleted in realtime,
- we will not see any effect on use_weight until next reload. */
- } else if (!strcasecmp(param, "timeoutrestart")) {
- q->timeoutrestart = ast_true(val);
- } else if (failunknown) {
- if (linenum >= 0) {
- ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n",
- q->name, param, linenum);
- } else {
- ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s\n", q->name, param);
- }
- }
-}
-
-static void rt_handle_member_record(struct call_queue *q, char *interface, const char *membername, const char *penalty_str, const char *paused_str)
-{
- struct member *m, tmpmem;
- int penalty = 0;
- int paused = 0;
-
- if (penalty_str) {
- penalty = atoi(penalty_str);
- if (penalty < 0)
- penalty = 0;
- }
-
- if (paused_str) {
- paused = atoi(paused_str);
- if (paused < 0)
- paused = 0;
- }
-
- /* Find the member, or the place to put a new one. */
- ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
- m = ao2_find(q->members, &tmpmem, OBJ_POINTER);
-
- /* Create a new one if not found, else update penalty */
- if (!m) {
- if ((m = create_queue_member(interface, membername, penalty, paused))) {
- m->dead = 0;
- m->realtime = 1;
- add_to_interfaces(interface);
- ao2_link(q->members, m);
- ao2_ref(m, -1);
- m = NULL;
- q->membercount++;
- }
- } else {
- m->dead = 0; /* Do not delete this one. */
- if (paused_str)
- m->paused = paused;
- m->penalty = penalty;
- ao2_ref(m, -1);
- }
-}
-
-static void free_members(struct call_queue *q, int all)
-{
- /* Free non-dynamic members */
- struct member *cur;
- struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
-
- while ((cur = ao2_iterator_next(&mem_iter))) {
- if (all || !cur->dynamic) {
- ao2_unlink(q->members, cur);
- remove_from_interfaces(cur->interface);
- q->membercount--;
- }
- ao2_ref(cur, -1);
- }
-}
-
-static void destroy_queue(struct call_queue *q)
-{
- free_members(q, 1);
- ast_mutex_destroy(&q->lock);
- ao2_ref(q->members, -1);
- free(q);
-}
-
-/*!\brief Reload a single queue via realtime.
- \return Return the queue, or NULL if it doesn't exist.
- \note Should be called with the global qlock locked. */
-static struct call_queue *find_queue_by_name_rt(const char *queuename, struct ast_variable *queue_vars, struct ast_config *member_config)
-{
- struct ast_variable *v;
- struct call_queue *q;
- struct member *m;
- struct ao2_iterator mem_iter;
- char *interface = NULL;
- char *tmp, *tmp_name;
- char tmpbuf[64]; /* Must be longer than the longest queue param name. */
-
- /* Find the queue in the in-core list (we will create a new one if not found). */
- AST_LIST_TRAVERSE(&queues, q, list) {
- if (!strcasecmp(q->name, queuename))
- break;
- }
-
- /* Static queues override realtime. */
- if (q) {
- ast_mutex_lock(&q->lock);
- if (!q->realtime) {
- if (q->dead) {
- ast_mutex_unlock(&q->lock);
- return NULL;
- } else {
- ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name);
- ast_mutex_unlock(&q->lock);
- return q;
- }
- }
- } else if (!member_config)
- /* Not found in the list, and it's not realtime ... */
- return NULL;
-
- /* Check if queue is defined in realtime. */
- if (!queue_vars) {
- /* Delete queue from in-core list if it has been deleted in realtime. */
- if (q) {
- /*! \note Hmm, can't seem to distinguish a DB failure from a not
- found condition... So we might delete an in-core queue
- in case of DB failure. */
- ast_log(LOG_DEBUG, "Queue %s not found in realtime.\n", queuename);
-
- q->dead = 1;
- /* Delete if unused (else will be deleted when last caller leaves). */
- if (!q->count) {
- /* Delete. */
- AST_LIST_REMOVE(&queues, q, list);
- ast_mutex_unlock(&q->lock);
- destroy_queue(q);
- } else
- ast_mutex_unlock(&q->lock);
- }
- return NULL;
- }
-
- /* Create a new queue if an in-core entry does not exist yet. */
- if (!q) {
- if (!(q = alloc_queue(queuename)))
- return NULL;
- ast_mutex_lock(&q->lock);
- clear_queue(q);
- q->realtime = 1;
- AST_LIST_INSERT_HEAD(&queues, q, list);
- }
- init_queue(q); /* Ensure defaults for all parameters not set explicitly. */
-
- memset(tmpbuf, 0, sizeof(tmpbuf));
- for (v = queue_vars; v; v = v->next) {
- /* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */
- if ((tmp = strchr(v->name, '_'))) {
- ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
- tmp_name = tmpbuf;
- tmp = tmp_name;
- while ((tmp = strchr(tmp, '_')))
- *tmp++ = '-';
- } else
- tmp_name = v->name;
-
- if (!ast_strlen_zero(v->value)) {
- /* Don't want to try to set the option if the value is empty */
- queue_set_param(q, tmp_name, v->value, -1, 0);
- }
- }
-
- if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN)
- rr_dep_warning();
-
- /* Temporarily set realtime members dead so we can detect deleted ones.
- * Also set the membercount correctly for realtime*/
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((m = ao2_iterator_next(&mem_iter))) {
- q->membercount++;
- if (m->realtime)
- m->dead = 1;
- ao2_ref(m, -1);
- }
-
- while ((interface = ast_category_browse(member_config, interface))) {
- rt_handle_member_record(q, interface,
- ast_variable_retrieve(member_config, interface, "membername"),
- ast_variable_retrieve(member_config, interface, "penalty"),
- ast_variable_retrieve(member_config, interface, "paused"));
- }
-
- /* Delete all realtime members that have been deleted in DB. */
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((m = ao2_iterator_next(&mem_iter))) {
- if (m->dead) {
- ao2_unlink(q->members, m);
- ast_mutex_unlock(&q->lock);
- remove_from_interfaces(m->interface);
- ast_mutex_lock(&q->lock);
- q->membercount--;
- }
- ao2_ref(m, -1);
- }
-
- ast_mutex_unlock(&q->lock);
-
- return q;
-}
-
-static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value)
-{
- struct ast_variable *var, *save;
- int ret = -1;
-
- if (!(var = ast_load_realtime("queue_members", "interface", mem->interface, "queue_name", queue_name, NULL)))
- return ret;
- save = var;
- while (var) {
- if (!strcmp(var->name, "uniqueid"))
- break;
- var = var->next;
- }
- if (var && !ast_strlen_zero(var->value)) {
- if ((ast_update_realtime("queue_members", "uniqueid", var->value, field, value, NULL)) > -1)
- ret = 0;
- }
- ast_variables_destroy(save);
- return ret;
-}
-
-static void update_realtime_members(struct call_queue *q)
-{
- struct ast_config *member_config = NULL;
- struct member *m;
- char *interface = NULL;
- struct ao2_iterator mem_iter;
-
- if (!(member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , NULL))) {
- /*This queue doesn't have realtime members*/
- if (option_debug > 2)
- ast_log(LOG_DEBUG, "Queue %s has no realtime members defined. No need for update\n", q->name);
- return;
- }
-
- ast_mutex_lock(&q->lock);
-
- /* Temporarily set realtime members dead so we can detect deleted ones.*/
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((m = ao2_iterator_next(&mem_iter))) {
- if (m->realtime)
- m->dead = 1;
- ao2_ref(m, -1);
- }
-
- while ((interface = ast_category_browse(member_config, interface))) {
- rt_handle_member_record(q, interface,
- S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface),
- ast_variable_retrieve(member_config, interface, "penalty"),
- ast_variable_retrieve(member_config, interface, "paused"));
- }
-
- /* Delete all realtime members that have been deleted in DB. */
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((m = ao2_iterator_next(&mem_iter))) {
- if (m->dead) {
- ao2_unlink(q->members, m);
- ast_mutex_unlock(&q->lock);
- remove_from_interfaces(m->interface);
- ast_mutex_lock(&q->lock);
- q->membercount--;
- }
- ao2_ref(m, -1);
- }
- ast_mutex_unlock(&q->lock);
- ast_config_destroy(member_config);
-}
-
-static struct call_queue *load_realtime_queue(const char *queuename)
-{
- struct ast_variable *queue_vars;
- struct ast_config *member_config = NULL;
- struct call_queue *q;
-
- /* Find the queue in the in-core list first. */
- AST_LIST_LOCK(&queues);
- AST_LIST_TRAVERSE(&queues, q, list) {
- if (!strcasecmp(q->name, queuename)) {
- break;
- }
- }
- AST_LIST_UNLOCK(&queues);
-
- if (!q || q->realtime) {
- /*! \note Load from realtime before taking the global qlock, to avoid blocking all
- queue operations while waiting for the DB.
-
- This will be two separate database transactions, so we might
- see queue parameters as they were before another process
- changed the queue and member list as it was after the change.
- Thus we might see an empty member list when a queue is
- deleted. In practise, this is unlikely to cause a problem. */
-
- queue_vars = ast_load_realtime("queues", "name", queuename, NULL);
- if (queue_vars) {
- member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, NULL);
- if (!member_config) {
- ast_log(LOG_ERROR, "no queue_members defined in your config (extconfig.conf).\n");
- ast_variables_destroy(queue_vars);
- return NULL;
- }
- }
-
- AST_LIST_LOCK(&queues);
-
- q = find_queue_by_name_rt(queuename, queue_vars, member_config);
- if (member_config)
- ast_config_destroy(member_config);
- if (queue_vars)
- ast_variables_destroy(queue_vars);
-
- AST_LIST_UNLOCK(&queues);
- } else {
- update_realtime_members(q);
- }
- return q;
-}
-
-static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason)
-{
- struct call_queue *q;
- struct queue_ent *cur, *prev = NULL;
- int res = -1;
- int pos = 0;
- int inserted = 0;
- enum queue_member_status stat;
-
- if (!(q = load_realtime_queue(queuename)))
- return res;
-
- AST_LIST_LOCK(&queues);
- ast_mutex_lock(&q->lock);
-
- /* This is our one */
- stat = get_member_status(q, qe->max_penalty);
- if (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
- *reason = QUEUE_JOINEMPTY;
- else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_MEMBERS))
- *reason = QUEUE_JOINUNAVAIL;
- else if (q->maxlen && (q->count >= q->maxlen))
- *reason = QUEUE_FULL;
- else {
- /* There's space for us, put us at the right position inside
- * the queue.
- * Take into account the priority of the calling user */
- inserted = 0;
- prev = NULL;
- cur = q->head;
- while (cur) {
- /* We have higher priority than the current user, enter
- * before him, after all the other users with priority
- * higher or equal to our priority. */
- if ((!inserted) && (qe->prio > cur->prio)) {
- insert_entry(q, prev, qe, &pos);
- inserted = 1;
- }
- cur->pos = ++pos;
- prev = cur;
- cur = cur->next;
- }
- /* No luck, join at the end of the queue */
- if (!inserted)
- insert_entry(q, prev, qe, &pos);
- 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));
- q->count++;
- res = 0;
- manager_event(EVENT_FLAG_CALL, "Join",
- "Channel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\nUniqueid: %s\r\n",
- qe->chan->name,
- S_OR(qe->chan->cid.cid_num, "unknown"), /* XXX somewhere else it is <unknown> */
- S_OR(qe->chan->cid.cid_name, "unknown"),
- q->name, qe->pos, q->count, qe->chan->uniqueid );
- if (option_debug)
- ast_log(LOG_DEBUG, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
- }
- ast_mutex_unlock(&q->lock);
- AST_LIST_UNLOCK(&queues);
-
- return res;
-}
-
-static int play_file(struct ast_channel *chan, char *filename)
-{
- int res;
-
- ast_stopstream(chan);
-
- res = ast_streamfile(chan, filename, chan->language);
- if (!res)
- res = ast_waitstream(chan, AST_DIGIT_ANY);
-
- ast_stopstream(chan);
-
- return res;
-}
-
-static int valid_exit(struct queue_ent *qe, char digit)
-{
- int digitlen = strlen(qe->digits);
-
- /* Prevent possible buffer overflow */
- if (digitlen < sizeof(qe->digits) - 2) {
- qe->digits[digitlen] = digit;
- qe->digits[digitlen + 1] = '\0';
- } else {
- qe->digits[0] = '\0';
- return 0;
- }
-
- /* If there's no context to goto, short-circuit */
- if (ast_strlen_zero(qe->context))
- return 0;
-
- /* If the extension is bad, then reset the digits to blank */
- if (!ast_canmatch_extension(qe->chan, qe->context, qe->digits, 1, qe->chan->cid.cid_num)) {
- qe->digits[0] = '\0';
- return 0;
- }
-
- /* We have an exact match */
- if (!ast_goto_if_exists(qe->chan, qe->context, qe->digits, 1)) {
- qe->valid_digits = 1;
- /* Return 1 on a successful goto */
- return 1;
- }
-
- return 0;
-}
-
-static int say_position(struct queue_ent *qe)
-{
- int res = 0, avgholdmins, avgholdsecs;
- time_t now;
-
- /* Check to see if this is ludicrous -- if we just announced position, don't do it again*/
- time(&now);
- if ((now - qe->last_pos) < 15)
- return 0;
-
- /* If either our position has changed, or we are over the freq timer, say position */
- if ((qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency))
- return 0;
-
- ast_moh_stop(qe->chan);
- /* Say we're next, if we are */
- if (qe->pos == 1) {
- res = play_file(qe->chan, qe->parent->sound_next);
- if (res)
- goto playout;
- else
- goto posout;
- } else {
- res = play_file(qe->chan, qe->parent->sound_thereare);
- if (res)
- goto playout;
- res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */
- if (res)
- goto playout;
- res = play_file(qe->chan, qe->parent->sound_calls);
- if (res)
- goto playout;
- }
- /* Round hold time to nearest minute */
- avgholdmins = abs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60);
-
- /* If they have specified a rounding then round the seconds as well */
- if (qe->parent->roundingseconds) {
- avgholdsecs = (abs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
- avgholdsecs *= qe->parent->roundingseconds;
- } else {
- avgholdsecs = 0;
- }
-
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes %d seconds\n", qe->parent->name, avgholdmins, avgholdsecs);
-
- /* If the hold time is >1 min, if it's enabled, and if it's not
- supposed to be only once and we have already said it, say it */
- if ((avgholdmins+avgholdsecs) > 0 && qe->parent->announceholdtime &&
- ((qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE && !qe->last_pos) ||
- !(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE))) {
- res = play_file(qe->chan, qe->parent->sound_holdtime);
- if (res)
- goto playout;
-
- if (avgholdmins > 0) {
- if (avgholdmins < 2) {
- res = play_file(qe->chan, qe->parent->sound_lessthan);
- if (res)
- goto playout;
-
- res = ast_say_number(qe->chan, 2, AST_DIGIT_ANY, qe->chan->language, NULL);
- if (res)
- goto playout;
- } else {
- res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, NULL);
- if (res)
- goto playout;
- }
-
- res = play_file(qe->chan, qe->parent->sound_minutes);
- if (res)
- goto playout;
- }
- if (avgholdsecs>0) {
- res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, NULL);
- if (res)
- goto playout;
-
- res = play_file(qe->chan, qe->parent->sound_seconds);
- if (res)
- goto playout;
- }
-
- }
-
-posout:
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n",
- qe->chan->name, qe->parent->name, qe->pos);
- res = play_file(qe->chan, qe->parent->sound_thanks);
-
-playout:
- if ((res > 0 && !valid_exit(qe, res)) || res < 0)
- res = 0;
-
- /* Set our last_pos indicators */
- qe->last_pos = now;
- qe->last_pos_said = qe->pos;
-
- /* Don't restart music on hold if we're about to exit the caller from the queue */
- if (!res)
- ast_moh_start(qe->chan, qe->moh, NULL);
-
- return res;
-}
-
-static void recalc_holdtime(struct queue_ent *qe, int newholdtime)
-{
- int oldvalue;
-
- /* Calculate holdtime using an exponential average */
- /* Thanks to SRT for this contribution */
- /* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
-
- ast_mutex_lock(&qe->parent->lock);
- oldvalue = qe->parent->holdtime;
- qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
- ast_mutex_unlock(&qe->parent->lock);
-}
-
-
-static void leave_queue(struct queue_ent *qe)
-{
- struct call_queue *q;
- struct queue_ent *cur, *prev = NULL;
- int pos = 0;
-
- if (!(q = qe->parent))
- return;
- ast_mutex_lock(&q->lock);
-
- prev = NULL;
- for (cur = q->head; cur; cur = cur->next) {
- if (cur == qe) {
- q->count--;
-
- /* Take us out of the queue */
- manager_event(EVENT_FLAG_CALL, "Leave",
- "Channel: %s\r\nQueue: %s\r\nCount: %d\r\nUniqueid: %s\r\n",
- qe->chan->name, q->name, q->count, qe->chan->uniqueid);
- if (option_debug)
- ast_log(LOG_DEBUG, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
- /* Take us out of the queue */
- if (prev)
- prev->next = cur->next;
- else
- q->head = cur->next;
- } else {
- /* Renumber the people after us in the queue based on a new count */
- cur->pos = ++pos;
- prev = cur;
- }
- }
- ast_mutex_unlock(&q->lock);
-
- if (q->dead && !q->count) {
- /* It's dead and nobody is in it, so kill it */
- AST_LIST_LOCK(&queues);
- AST_LIST_REMOVE(&queues, q, list);
- AST_LIST_UNLOCK(&queues);
- destroy_queue(q);
- }
-}
-
-/* Hang up a list of outgoing calls */
-static void hangupcalls(struct callattempt *outgoing, struct ast_channel *exception)
-{
- struct callattempt *oo;
-
- while (outgoing) {
- /* Hangup any existing lines we have open */
- if (outgoing->chan && (outgoing->chan != exception))
- ast_hangup(outgoing->chan);
- oo = outgoing;
- outgoing = outgoing->q_next;
- if (oo->member)
- ao2_ref(oo->member, -1);
- free(oo);
- }
-}
-
-
-/* traverse all defined queues which have calls waiting and contain this member
- return 0 if no other queue has precedence (higher weight) or 1 if found */
-static int compare_weight(struct call_queue *rq, struct member *member)
-{
- struct call_queue *q;
- struct member *mem;
- int found = 0;
-
- /* &qlock and &rq->lock already set by try_calling()
- * to solve deadlock */
- AST_LIST_TRAVERSE(&queues, q, list) {
- if (q == rq) /* don't check myself, could deadlock */
- continue;
- ast_mutex_lock(&q->lock);
- if (q->count && q->members) {
- if ((mem = ao2_find(q->members, member, OBJ_POINTER))) {
- ast_log(LOG_DEBUG, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
- if (q->weight > rq->weight) {
- ast_log(LOG_DEBUG, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count);
- found = 1;
- }
- ao2_ref(mem, -1);
- }
- }
- ast_mutex_unlock(&q->lock);
- if (found)
- break;
- }
- return found;
-}
-
-/*! \brief common hangup actions */
-static void do_hang(struct callattempt *o)
-{
- o->stillgoing = 0;
- ast_hangup(o->chan);
- o->chan = NULL;
-}
-
-static char *vars2manager(struct ast_channel *chan, char *vars, size_t len)
-{
- char *tmp = alloca(len);
-
- if (pbx_builtin_serialize_variables(chan, tmp, len)) {
- int i, j;
-
- /* convert "\n" to "\nVariable: " */
- strcpy(vars, "Variable: ");
-
- for (i = 0, j = 10; (i < len - 1) && (j < len - 1); i++, j++) {
- vars[j] = tmp[i];
-
- if (tmp[i + 1] == '\0')
- break;
- if (tmp[i] == '\n') {
- vars[j++] = '\r';
- vars[j++] = '\n';
-
- ast_copy_string(&(vars[j]), "Variable: ", len - j);
- j += 9;
- }
- }
- if (j > len - 3)
- j = len - 3;
- vars[j++] = '\r';
- vars[j++] = '\n';
- vars[j] = '\0';
- } else {
- /* there are no channel variables; leave it blank */
- *vars = '\0';
- }
- return vars;
-}
-
-/*! \brief Part 2 of ring_one
- *
- * Does error checking before attempting to request a channel and call a member. This
- * function is only called from ring_one
- */
-static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies)
-{
- int res;
- int status;
- char tech[256];
- char *location;
- const char *macrocontext, *macroexten;
-
- /* on entry here, we know that tmp->chan == NULL */
- if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) {
- if (option_debug)
- ast_log(LOG_DEBUG, "Wrapuptime not yet expired for %s\n", tmp->interface);
- if (qe->chan->cdr)
- ast_cdr_busy(qe->chan->cdr);
- tmp->stillgoing = 0;
- (*busies)++;
- return 0;
- }
-
- if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) {
- if (option_debug)
- ast_log(LOG_DEBUG, "%s in use, can't receive call\n", tmp->interface);
- if (qe->chan->cdr)
- ast_cdr_busy(qe->chan->cdr);
- tmp->stillgoing = 0;
- return 0;
- }
-
- if (tmp->member->paused) {
- if (option_debug)
- ast_log(LOG_DEBUG, "%s paused, can't receive call\n", tmp->interface);
- if (qe->chan->cdr)
- ast_cdr_busy(qe->chan->cdr);
- tmp->stillgoing = 0;
- return 0;
- }
- if (use_weight && compare_weight(qe->parent,tmp->member)) {
- ast_log(LOG_DEBUG, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface);
- if (qe->chan->cdr)
- ast_cdr_busy(qe->chan->cdr);
- tmp->stillgoing = 0;
- (*busies)++;
- return 0;
- }
-
- ast_copy_string(tech, tmp->interface, sizeof(tech));
- if ((location = strchr(tech, '/')))
- *location++ = '\0';
- else
- location = "";
-
- /* Request the peer */
- tmp->chan = ast_request(tech, qe->chan->nativeformats, location, &status);
- if (!tmp->chan) { /* If we can't, just go on to the next call */
- if (qe->chan->cdr)
- ast_cdr_busy(qe->chan->cdr);
- tmp->stillgoing = 0;
-
- update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
-
- ast_mutex_lock(&qe->parent->lock);
- qe->parent->rrpos++;
- ast_mutex_unlock(&qe->parent->lock);
-
- (*busies)++;
- return 0;
- }
-
- tmp->chan->appl = "AppQueue";
- tmp->chan->data = "(Outgoing Line)";
- tmp->chan->whentohangup = 0;
- if (tmp->chan->cid.cid_num)
- free(tmp->chan->cid.cid_num);
- tmp->chan->cid.cid_num = ast_strdup(qe->chan->cid.cid_num);
- if (tmp->chan->cid.cid_name)
- free(tmp->chan->cid.cid_name);
- tmp->chan->cid.cid_name = ast_strdup(qe->chan->cid.cid_name);
- if (tmp->chan->cid.cid_ani)
- free(tmp->chan->cid.cid_ani);
- tmp->chan->cid.cid_ani = ast_strdup(qe->chan->cid.cid_ani);
-
- /* Inherit specially named variables from parent channel */
- ast_channel_inherit_variables(qe->chan, tmp->chan);
-
- /* Presense of ADSI CPE on outgoing channel follows ours */
- tmp->chan->adsicpe = qe->chan->adsicpe;
-
- /* Inherit context and extension */
- ast_channel_lock(qe->chan);
- macrocontext = pbx_builtin_getvar_helper(qe->chan, "MACRO_CONTEXT");
- if (!ast_strlen_zero(macrocontext))
- ast_copy_string(tmp->chan->dialcontext, macrocontext, sizeof(tmp->chan->dialcontext));
- else
- ast_copy_string(tmp->chan->dialcontext, qe->chan->context, sizeof(tmp->chan->dialcontext));
- macroexten = pbx_builtin_getvar_helper(qe->chan, "MACRO_EXTEN");
- if (!ast_strlen_zero(macroexten))
- ast_copy_string(tmp->chan->exten, macroexten, sizeof(tmp->chan->exten));
- else
- ast_copy_string(tmp->chan->exten, qe->chan->exten, sizeof(tmp->chan->exten));
- ast_channel_unlock(qe->chan);
-
- /* Place the call, but don't wait on the answer */
- if ((res = ast_call(tmp->chan, location, 0))) {
- /* Again, keep going even if there's an error */
- if (option_debug)
- ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res);
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->interface);
- do_hang(tmp);
- (*busies)++;
- update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
- return 0;
- } else if (qe->parent->eventwhencalled) {
- char vars[2048];
-
- manager_event(EVENT_FLAG_AGENT, "AgentCalled",
- "AgentCalled: %s\r\n"
- "AgentName: %s\r\n"
- "ChannelCalling: %s\r\n"
- "CallerID: %s\r\n"
- "CallerIDName: %s\r\n"
- "Context: %s\r\n"
- "Extension: %s\r\n"
- "Priority: %d\r\n"
- "%s",
- tmp->interface, tmp->member->membername, qe->chan->name,
- tmp->chan->cid.cid_num ? tmp->chan->cid.cid_num : "unknown",
- tmp->chan->cid.cid_name ? tmp->chan->cid.cid_name : "unknown",
- qe->chan->context, qe->chan->exten, qe->chan->priority,
- qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->interface);
- }
-
- update_status(tmp->member->interface, ast_device_state(tmp->member->interface));
- return 1;
-}
-
-/*! \brief find the entry with the best metric, or NULL */
-static struct callattempt *find_best(struct callattempt *outgoing)
-{
- struct callattempt *best = NULL, *cur;
-
- for (cur = outgoing; cur; cur = cur->q_next) {
- if (cur->stillgoing && /* Not already done */
- !cur->chan && /* Isn't already going */
- (!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */
- best = cur;
- }
- }
-
- return best;
-}
-
-/*! \brief Place a call to a queue member
- *
- * Once metrics have been calculated for each member, this function is used
- * to place a call to the appropriate member (or members). The low-level
- * channel-handling and error detection is handled in ring_entry
- *
- * Returns 1 if a member was called successfully, 0 otherwise
- */
-static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies)
-{
- int ret = 0;
-
- while (ret == 0) {
- struct callattempt *best = find_best(outgoing);
- if (!best) {
- if (option_debug)
- ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n");
- break;
- }
- if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
- struct callattempt *cur;
- /* Ring everyone who shares this best metric (for ringall) */
- for (cur = outgoing; cur; cur = cur->q_next) {
- if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) {
- if (option_debug)
- ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric);
- ret |= ring_entry(qe, cur, busies);
- }
- }
- } else {
- /* Ring just the best channel */
- if (option_debug)
- ast_log(LOG_DEBUG, "Trying '%s' with metric %d\n", best->interface, best->metric);
- ret = ring_entry(qe, best, busies);
- }
- }
-
- return ret;
-}
-
-static int store_next(struct queue_ent *qe, struct callattempt *outgoing)
-{
- struct callattempt *best = find_best(outgoing);
-
- if (best) {
- /* Ring just the best channel */
- if (option_debug)
- ast_log(LOG_DEBUG, "Next is '%s' with metric %d\n", best->interface, best->metric);
- qe->parent->rrpos = best->metric % 1000;
- } else {
- /* Just increment rrpos */
- if (qe->parent->wrapped) {
- /* No more channels, start over */
- qe->parent->rrpos = 0;
- } else {
- /* Prioritize next entry */
- qe->parent->rrpos++;
- }
- }
- qe->parent->wrapped = 0;
-
- return 0;
-}
-
-static int say_periodic_announcement(struct queue_ent *qe)
-{
- int res = 0;
- time_t now;
-
- /* Get the current time */
- time(&now);
-
- /* Check to see if it is time to announce */
- if ((now - qe->last_periodic_announce_time) < qe->parent->periodicannouncefrequency)
- return 0;
-
- /* Stop the music on hold so we can play our own file */
- ast_moh_stop(qe->chan);
-
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 "Playing periodic announcement\n");
-
- /* Check to make sure we have a sound file. If not, reset to the first sound file */
- if (qe->last_periodic_announce_sound >= MAX_PERIODIC_ANNOUNCEMENTS || !strlen(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound])) {
- qe->last_periodic_announce_sound = 0;
- }
-
- /* play the announcement */
- res = play_file(qe->chan, qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]);
-
- if ((res > 0 && !valid_exit(qe, res)) || res < 0)
- res = 0;
-
- /* Resume Music on Hold if the caller is going to stay in the queue */
- if (!res)
- ast_moh_start(qe->chan, qe->moh, NULL);
-
- /* update last_periodic_announce_time */
- qe->last_periodic_announce_time = now;
-
- /* Update the current periodic announcement to the next announcement */
- qe->last_periodic_announce_sound++;
-
- return res;
-}
-
-static void record_abandoned(struct queue_ent *qe)
-{
- ast_mutex_lock(&qe->parent->lock);
- manager_event(EVENT_FLAG_AGENT, "QueueCallerAbandon",
- "Queue: %s\r\n"
- "Uniqueid: %s\r\n"
- "Position: %d\r\n"
- "OriginalPosition: %d\r\n"
- "HoldTime: %d\r\n",
- qe->parent->name, qe->chan->uniqueid, qe->pos, qe->opos, (int)(time(NULL) - qe->start));
-
- qe->parent->callsabandoned++;
- ast_mutex_unlock(&qe->parent->lock);
-}
-
-/*! \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)
-{
- if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", rnatime);
- ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime);
- if (qe->parent->autopause) {
- if (!set_member_paused(qe->parent->name, interface, 1)) {
- if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", interface, qe->parent->name);
- } else {
- if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "Failed to pause Queue Member %s in queue %s!\n", interface, qe->parent->name);
- }
- }
- return;
-}
-
-#define AST_MAX_WATCHERS 256
-/*! \brief Wait for a member to answer the call
- *
- * \param[in] qe the queue_ent corresponding to the caller in the queue
- * \param[in] outgoing the list of callattempts. Relevant ones will have their chan and stillgoing parameters non-zero
- * \param[in] to the amount of time (in milliseconds) to wait for a response
- * \param[out] digit if a user presses a digit to exit the queue, this is the digit the caller pressed
- * \param[in] prebusies number of busy members calculated prior to calling wait_for_answer
- * \param[in] caller_disconnect if the 'H' option is used when calling Queue(), this is used to detect if the caller pressed * to disconnect the call
- * \param[in] forwardsallowed used to detect if we should allow call forwarding, based on the 'i' option to Queue()
- */
-static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed)
-{
- char *queue = qe->parent->name;
- struct callattempt *o, *start = NULL, *prev = NULL;
- int status;
- int numbusies = prebusies;
- int numnochan = 0;
- int stillgoing = 0;
- int orig = *to;
- struct ast_frame *f;
- struct callattempt *peer = NULL;
- struct ast_channel *winner;
- struct ast_channel *in = qe->chan;
- char on[80] = "";
- char membername[80] = "";
- long starttime = 0;
- long endtime = 0;
-
- starttime = (long) time(NULL);
-
- while (*to && !peer) {
- int numlines, retry, pos = 1;
- struct ast_channel *watchers[AST_MAX_WATCHERS];
- watchers[0] = in;
- start = NULL;
-
- for (retry = 0; retry < 2; retry++) {
- numlines = 0;
- for (o = outgoing; o; o = o->q_next) { /* Keep track of important channels */
- if (o->stillgoing) { /* Keep track of important channels */
- stillgoing = 1;
- if (o->chan) {
- watchers[pos++] = o->chan;
- if (!start)
- start = o;
- else
- prev->call_next = o;
- prev = o;
- }
- }
- numlines++;
- }
- if (pos > 1 /* found */ || !stillgoing /* nobody listening */ ||
- (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) /* ring would not be delivered */)
- break;
- /* On "ringall" strategy we only move to the next penalty level
- when *all* ringing phones are done in the current penalty level */
- ring_one(qe, outgoing, &numbusies);
- /* and retry... */
- }
- if (pos == 1 /* not found */) {
- if (numlines == (numbusies + numnochan)) {
- ast_log(LOG_DEBUG, "Everyone is busy at this time\n");
- } else {
- ast_log(LOG_NOTICE, "No one is answering queue '%s' (%d/%d/%d)\n", queue, numlines, numbusies, numnochan);
- }
- *to = 0;
- return NULL;
- }
- winner = ast_waitfor_n(watchers, pos, to);
- for (o = start; o; o = o->call_next) {
- if (o->stillgoing && (o->chan) && (o->chan->_state == AST_STATE_UP)) {
- if (!peer) {
- if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
- peer = o;
- }
- } else if (o->chan && (o->chan == winner)) {
-
- ast_copy_string(on, o->member->interface, sizeof(on));
- ast_copy_string(membername, o->member->membername, sizeof(membername));
-
- if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) {
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 "Forwarding %s to '%s' prevented.\n", in->name, o->chan->call_forward);
- numnochan++;
- do_hang(o);
- winner = NULL;
- continue;
- } else if (!ast_strlen_zero(o->chan->call_forward)) {
- char tmpchan[256];
- char *stuff;
- char *tech;
-
- ast_copy_string(tmpchan, o->chan->call_forward, sizeof(tmpchan));
- if ((stuff = strchr(tmpchan, '/'))) {
- *stuff++ = '\0';
- tech = tmpchan;
- } else {
- snprintf(tmpchan, sizeof(tmpchan), "%s@%s", o->chan->call_forward, o->chan->context);
- stuff = tmpchan;
- tech = "Local";
- }
- /* Before processing channel, go ahead and check for forwarding */
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, o->chan->name);
- /* Setup parameters */
- o->chan = ast_request(tech, in->nativeformats, stuff, &status);
- if (!o->chan) {
- ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s'\n", tech, stuff);
- o->stillgoing = 0;
- numnochan++;
- } else {
- ast_channel_inherit_variables(in, o->chan);
- ast_channel_datastore_inherit(in, o->chan);
- if (o->chan->cid.cid_num)
- free(o->chan->cid.cid_num);
- o->chan->cid.cid_num = ast_strdup(in->cid.cid_num);
-
- if (o->chan->cid.cid_name)
- free(o->chan->cid.cid_name);
- o->chan->cid.cid_name = ast_strdup(in->cid.cid_name);
-
- ast_string_field_set(o->chan, accountcode, in->accountcode);
- o->chan->cdrflags = in->cdrflags;
-
- if (in->cid.cid_ani) {
- if (o->chan->cid.cid_ani)
- free(o->chan->cid.cid_ani);
- o->chan->cid.cid_ani = ast_strdup(in->cid.cid_ani);
- }
- if (o->chan->cid.cid_rdnis)
- free(o->chan->cid.cid_rdnis);
- o->chan->cid.cid_rdnis = ast_strdup(S_OR(in->macroexten, in->exten));
- if (ast_call(o->chan, tmpchan, 0)) {
- ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan);
- do_hang(o);
- numnochan++;
- }
- }
- /* Hangup the original channel now, in case we needed it */
- ast_hangup(winner);
- continue;
- }
- f = ast_read(winner);
- if (f) {
- if (f->frametype == AST_FRAME_CONTROL) {
- switch (f->subclass) {
- case AST_CONTROL_ANSWER:
- /* This is our guy if someone answered. */
- if (!peer) {
- if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
- peer = o;
- }
- break;
- case AST_CONTROL_BUSY:
- if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
- if (in->cdr)
- ast_cdr_busy(in->cdr);
- do_hang(o);
- endtime = (long)time(NULL);
- endtime -= starttime;
- rna(endtime*1000, qe, on, membername);
- if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
- if (qe->parent->timeoutrestart)
- *to = orig;
- ring_one(qe, outgoing, &numbusies);
- }
- numbusies++;
- break;
- case AST_CONTROL_CONGESTION:
- if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
- if (in->cdr)
- ast_cdr_busy(in->cdr);
- endtime = (long)time(NULL);
- endtime -= starttime;
- rna(endtime*1000, qe, on, membername);
- do_hang(o);
- if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
- if (qe->parent->timeoutrestart)
- *to = orig;
- ring_one(qe, outgoing, &numbusies);
- }
- numbusies++;
- break;
- case AST_CONTROL_RINGING:
- if (option_verbose > 2)
- ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
- break;
- case AST_CONTROL_OFFHOOK:
- /* Ignore going off hook */
- break;
- default:
- ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
- }
- }
- ast_frfree(f);
- } else {
- endtime = (long) time(NULL) - starttime;
- rna(endtime * 1000, qe, on, membername);
- do_hang(o);
- if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
- if (qe->parent->timeoutrestart)
- *to = orig;
- ring_one(qe, outgoing, &numbusies);
- }
- }
- }
- }
- if (winner == in) {
- f = ast_read(in);
- if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
- /* Got hung up */
- *to = -1;
- if (f)
- ast_frfree(f);
- return NULL;
- }
- if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass == '*')) {
- if (option_verbose > 3)
- ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
- *to = 0;
- ast_frfree(f);
- return NULL;
- }
- if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass)) {
- if (option_verbose > 3)
- ast_verbose(VERBOSE_PREFIX_3 "User pressed digit: %c\n", f->subclass);
- *to = 0;
- *digit = f->subclass;
- ast_frfree(f);
- return NULL;
- }
- ast_frfree(f);
- }
- if (!*to) {
- for (o = start; o; o = o->call_next)
- rna(orig, qe, o->interface, o->member->membername);
- }
- }
-
- return peer;
-}
-/*! \brief Check if we should start attempting to call queue members
- *
- * The behavior of this function is dependent first on whether autofill is enabled
- * and second on whether the ring strategy is ringall. If autofill is not enabled,
- * then return true if we're the head of the queue. If autofill is enabled, then
- * we count the available members and see if the number of available members is enough
- * that given our position in the queue, we would theoretically be able to connect to
- * one of those available members
- */
-static int is_our_turn(struct queue_ent *qe)
-{
- struct queue_ent *ch;
- struct member *cur;
- int avl = 0;
- int idx = 0;
- int res;
-
- if (!qe->parent->autofill) {
- /* Atomically read the parent head -- does not need a lock */
- ch = qe->parent->head;
- /* If we are now at the top of the head, break out */
- if (ch == qe) {
- if (option_debug)
- ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name);
- res = 1;
- } else {
- if (option_debug)
- ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name);
- res = 0;
- }
-
- } else {
- /* This needs a lock. How many members are available to be served? */
- ast_mutex_lock(&qe->parent->lock);
-
- ch = qe->parent->head;
-
- if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
- if (option_debug)
- ast_log(LOG_DEBUG, "Even though there may be multiple members available, the strategy is ringall so only the head call is allowed in\n");
- avl = 1;
- } else {
- struct ao2_iterator mem_iter = ao2_iterator_init(qe->parent->members, 0);
- while ((cur = ao2_iterator_next(&mem_iter))) {
- switch (cur->status) {
- case AST_DEVICE_INUSE:
- if (!qe->parent->ringinuse)
- break;
- /* else fall through */
- case AST_DEVICE_NOT_INUSE:
- case AST_DEVICE_UNKNOWN:
- if (!cur->paused)
- avl++;
- break;
- }
- ao2_ref(cur, -1);
- }
- }
-
- if (option_debug)
- ast_log(LOG_DEBUG, "There are %d available members.\n", avl);
-
- while ((idx < avl) && (ch) && (ch != qe)) {
- if (!ch->pending)
- idx++;
- ch = ch->next;
- }
-
- /* If the queue entry is within avl [the number of available members] calls from the top ... */
- if (ch && idx < avl) {
- if (option_debug)
- ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name);
- res = 1;
- } else {
- if (option_debug)
- ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name);
- res = 0;
- }
-
- ast_mutex_unlock(&qe->parent->lock);
- }
-
- return res;
-}
-/*! \brief The waiting areas for callers who are not actively calling members
- *
- * This function is one large loop. This function will return if a caller
- * either exits the queue or it becomes that caller's turn to attempt calling
- * queue members. Inside the loop, we service the caller with periodic announcements,
- * holdtime announcements, etc. as configured in queues.conf
- *
- * \retval 0 if the caller's turn has arrived
- * \retval -1 if the caller should exit the queue.
- */
-static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *reason)
-{
- int res = 0;
-
- /* This is the holding pen for callers 2 through maxlen */
- for (;;) {
- enum queue_member_status stat;
-
- if (is_our_turn(qe))
- break;
-
- /* If we have timed out, break out */
- if (qe->expire && (time(NULL) >= qe->expire)) {
- *reason = QUEUE_TIMEOUT;
- break;
- }
-
- stat = get_member_status(qe->parent, qe->max_penalty);
-
- /* leave the queue if no agents, if enabled */
- if (qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
- *reason = QUEUE_LEAVEEMPTY;
- ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
- leave_queue(qe);
- break;
- }
-
- /* leave the queue if no reachable agents, if enabled */
- if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
- *reason = QUEUE_LEAVEUNAVAIL;
- ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
- leave_queue(qe);
- break;
- }
-
- /* Make a position announcement, if enabled */
- if (qe->parent->announcefrequency && !ringing &&
- (res = say_position(qe)))
- break;
-
- /* If we have timed out, break out */
- if (qe->expire && (time(NULL) >= qe->expire)) {
- *reason = QUEUE_TIMEOUT;
- break;
- }
-
- /* Make a periodic announcement, if enabled */
- if (qe->parent->periodicannouncefrequency && !ringing &&
- (res = say_periodic_announcement(qe)))
- break;
-
- /* If we have timed out, break out */
- if (qe->expire && (time(NULL) >= qe->expire)) {
- *reason = QUEUE_TIMEOUT;
- break;
- }
-
- /* Wait a second before checking again */
- if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) {
- if (res > 0 && !valid_exit(qe, res))
- res = 0;
- else
- break;
- }
-
- /* If we have timed out, break out */
- if (qe->expire && (time(NULL) >= qe->expire)) {
- *reason = QUEUE_TIMEOUT;
- break;
- }
- }
-
- return res;
-}
-
-static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl)
-{
- ast_mutex_lock(&q->lock);
- time(&member->lastcall);
- member->calls++;
- q->callscompleted++;
- if (callcompletedinsl)
- q->callscompletedinsl++;
- ast_mutex_unlock(&q->lock);
- return 0;
-}
-
-/*! \brief Calculate the metric of each member in the outgoing callattempts
- *
- * A numeric metric is given to each member depending on the ring strategy used
- * by the queue. Members with lower metrics will be called before members with
- * higher metrics
- */
-static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct callattempt *tmp)
-{
- if (qe->max_penalty && (mem->penalty > qe->max_penalty))
- return -1;
-
- switch (q->strategy) {
- case QUEUE_STRATEGY_RINGALL:
- /* Everyone equal, except for penalty */
- tmp->metric = mem->penalty * 1000000;
- break;
- case QUEUE_STRATEGY_ROUNDROBIN:
- if (!pos) {
- if (!q->wrapped) {
- /* No more channels, start over */
- q->rrpos = 0;
- } else {
- /* Prioritize next entry */
- q->rrpos++;
- }
- q->wrapped = 0;
- }
- /* Fall through */
- case QUEUE_STRATEGY_RRMEMORY:
- if (pos < q->rrpos) {
- tmp->metric = 1000 + pos;
- } else {
- if (pos > q->rrpos)
- /* Indicate there is another priority */
- q->wrapped = 1;
- tmp->metric = pos;
- }
- tmp->metric += mem->penalty * 1000000;
- break;
- case QUEUE_STRATEGY_RANDOM:
- tmp->metric = ast_random() % 1000;
- tmp->metric += mem->penalty * 1000000;
- break;
- case QUEUE_STRATEGY_FEWESTCALLS:
- tmp->metric = mem->calls;
- tmp->metric += mem->penalty * 1000000;
- break;
- case QUEUE_STRATEGY_LEASTRECENT:
- if (!mem->lastcall)
- tmp->metric = 0;
- else
- tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
- tmp->metric += mem->penalty * 1000000;
- break;
- default:
- ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
- break;
- }
- return 0;
-}
-
-struct queue_transfer_ds {
- struct queue_ent *qe;
- struct member *member;
- time_t starttime;
- int callcompletedinsl;
-};
-
-static void queue_transfer_destroy(void *data)
-{
- struct queue_transfer_ds *qtds = data;
- ast_free(qtds);
-}
-
-/*! \brief a datastore used to help correctly log attended transfers of queue callers
- */
-static const struct ast_datastore_info queue_transfer_info = {
- .type = "queue_transfer",
- .chan_fixup = queue_transfer_fixup,
- .destroy = queue_transfer_destroy,
-};
-
-/*! \brief Log an attended transfer when a queue caller channel is masqueraded
- *
- * When a caller is masqueraded, we want to log a transfer. Fixup time is the closest we can come to when
- * the actual transfer occurs. This happens during the masquerade after datastores are moved from old_chan
- * to new_chan. This is why new_chan is referenced for exten, context, and datastore information.
- *
- * At the end of this, we want to remove the datastore so that this fixup function is not called on any
- * future masquerades of the caller during the current call.
- */
-static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan)
-{
- struct queue_transfer_ds *qtds = data;
- struct queue_ent *qe = qtds->qe;
- struct member *member = qtds->member;
- time_t callstart = qtds->starttime;
- int callcompletedinsl = qtds->callcompletedinsl;
- struct ast_datastore *datastore;
-
- ast_queue_log(qe->parent->name, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld",
- new_chan->exten, new_chan->context, (long) (callstart - qe->start),
- (long) (time(NULL) - callstart));
-
- update_queue(qe->parent, member, callcompletedinsl);
-
- /* No need to lock the channels because they are already locked in ast_do_masquerade */
- if ((datastore = ast_channel_datastore_find(old_chan, &queue_transfer_info, NULL))) {
- ast_channel_datastore_remove(old_chan, datastore);
- } else {
- ast_log(LOG_WARNING, "Can't find the queue_transfer datastore.\n");
- }
-}
-
-/*! \brief mechanism to tell if a queue caller was atxferred by a queue member.
- *
- * When a caller is atxferred, then the queue_transfer_info datastore
- * is removed from the channel. If it's still there after the bridge is
- * broken, then the caller was not atxferred.
- *
- * \note Only call this with chan locked
- */
-static int attended_transfer_occurred(struct ast_channel *chan)
-{
- return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1;
-}
-
-/*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log
- */
-static struct ast_datastore *setup_transfer_datastore(struct queue_ent *qe, struct member *member, time_t starttime, int callcompletedinsl)
-{
- struct ast_datastore *ds;
- struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds));
-
- if (!qtds) {
- ast_log(LOG_WARNING, "Memory allocation error!\n");
- return NULL;
- }
-
- ast_channel_lock(qe->chan);
- if (!(ds = ast_channel_datastore_alloc(&queue_transfer_info, NULL))) {
- ast_channel_unlock(qe->chan);
- ast_log(LOG_WARNING, "Unable to create transfer datastore. queue_log will not show attended transfer\n");
- return NULL;
- }
-
- qtds->qe = qe;
- /* This member is refcounted in try_calling, so no need to add it here, too */
- qtds->member = member;
- qtds->starttime = starttime;
- qtds->callcompletedinsl = callcompletedinsl;
- ds->data = qtds;
- ast_channel_datastore_add(qe->chan, ds);
- ast_channel_unlock(qe->chan);
- return ds;
-}
-
-
-/*! \brief A large function which calls members, updates statistics, and bridges the caller and a member
- *
- * Here is the process of this function
- * 1. Process any options passed to the Queue() application. Options here mean the third argument to Queue()
- * 2. Iterate trough the members of the queue, creating a callattempt corresponding to each member. During this
- * iteration, we also check the dialed_interfaces datastore to see if we have already attempted calling this
- * member. If we have, we do not create a callattempt. This is in place to prevent call forwarding loops. Also
- * during each iteration, we call calc_metric to determine which members should be rung when.
- * 3. Call ring_one to place a call to the appropriate member(s)
- * 4. Call wait_for_answer to wait for an answer. If no one answers, return.
- * 5. Take care of any holdtime announcements, member delays, or other options which occur after a call has been answered.
- * 6. Start the monitor or mixmonitor if the option is set
- * 7. Remove the caller from the queue to allow other callers to advance
- * 8. Bridge the call.
- * 9. Do any post processing after the call has disconnected.
- *
- * \param[in] qe the queue_ent structure which corresponds to the caller attempting to reach members
- * \param[in] options the options passed as the third parameter to the Queue() application
- * \param[in] url the url passed as the fourth parameter to the Queue() application
- * \param[in,out] tries the number of times we have tried calling queue members
- * \param[out] noption set if the call to Queue() has the 'n' option set.
- * \param[in] agi the agi passed as the fifth parameter to the Queue() application
- */
-
-static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *tries, int *noption, const char *agi)
-{
- struct member *cur;
- struct callattempt *outgoing = NULL; /* the list of calls we are building */
- int to;
- char oldexten[AST_MAX_EXTENSION]="";
- char oldcontext[AST_MAX_CONTEXT]="";
- char queuename[256]="";
- struct ast_channel *peer;
- struct ast_channel *which;
- struct callattempt *lpeer;
- struct member *member;
- struct ast_app *app;
- int res = 0, bridge = 0;
- int numbusies = 0;
- int x=0;
- char *announce = NULL;
- char digit = 0;
- time_t callstart;
- time_t now = time(NULL);
- struct ast_bridge_config bridge_config;
- char nondataquality = 1;
- char *agiexec = NULL;
- int ret = 0;
- const char *monitorfilename;
- const char *monitor_exec;
- const char *monitor_options;
- char tmpid[256], tmpid2[256];
- char meid[1024], meid2[1024];
- char mixmonargs[1512];
- struct ast_app *mixmonapp = NULL;
- char *p;
- char vars[2048];
- int forwardsallowed = 1;
- int callcompletedinsl;
- struct ao2_iterator memi;
- struct ast_datastore *datastore, *transfer_ds;
-
- ast_channel_lock(qe->chan);
- datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL);
- ast_channel_unlock(qe->chan);
-
- memset(&bridge_config, 0, sizeof(bridge_config));
- time(&now);
-
- /* If we've already exceeded our timeout, then just stop
- * This should be extremely rare. queue_exec will take care
- * of removing the caller and reporting the timeout as the reason.
- */
- if (qe->expire && now >= qe->expire) {
- res = 0;
- goto out;
- }
-
- for (; options && *options; options++)
- switch (*options) {
- case 't':
- ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_REDIRECT);
- break;
- case 'T':
- ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_REDIRECT);
- break;
- case 'w':
- ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMON);
- break;
- case 'W':
- ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON);
- break;
- case 'd':
- nondataquality = 0;
- break;
- case 'h':
- ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_DISCONNECT);
- break;
- case 'H':
- ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT);
- break;
- case 'n':
- if (qe->parent->strategy == QUEUE_STRATEGY_ROUNDROBIN || qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY)
- (*tries)++;
- else
- *tries = qe->parent->membercount;
- *noption = 1;
- break;
- case 'i':
- forwardsallowed = 0;
- break;
- }
-
- /* Hold the lock while we setup the outgoing calls */
- if (use_weight)
- AST_LIST_LOCK(&queues);
- ast_mutex_lock(&qe->parent->lock);
- if (option_debug)
- ast_log(LOG_DEBUG, "%s is trying to call a queue member.\n",
- qe->chan->name);
- ast_copy_string(queuename, qe->parent->name, sizeof(queuename));
- if (!ast_strlen_zero(qe->announce))
- announce = qe->announce;
- if (!ast_strlen_zero(announceoverride))
- announce = announceoverride;
-
- memi = ao2_iterator_init(qe->parent->members, 0);
- while ((cur = ao2_iterator_next(&memi))) {
- struct callattempt *tmp = ast_calloc(1, sizeof(*tmp));
- struct ast_dialed_interface *di;
- AST_LIST_HEAD(, ast_dialed_interface) *dialed_interfaces;
- if (!tmp) {
- ao2_ref(cur, -1);
- ast_mutex_unlock(&qe->parent->lock);
- if (use_weight)
- AST_LIST_UNLOCK(&queues);
- goto out;
- }
- if (!datastore) {
- if (!(datastore = ast_channel_datastore_alloc(&dialed_interface_info, NULL))) {
- ao2_ref(cur, -1);
- ast_mutex_unlock(&qe->parent->lock);
- if (use_weight)
- AST_LIST_UNLOCK(&queues);
- free(tmp);
- goto out;
- }
- datastore->inheritance = DATASTORE_INHERIT_FOREVER;
- if (!(dialed_interfaces = ast_calloc(1, sizeof(*dialed_interfaces)))) {
- ao2_ref(cur, -1);
- ast_mutex_unlock(&qe->parent->lock);
- if (use_weight)
- AST_LIST_UNLOCK(&queues);
- free(tmp);
- goto out;
- }
- datastore->data = dialed_interfaces;
- AST_LIST_HEAD_INIT(dialed_interfaces);
-
- ast_channel_lock(qe->chan);
- ast_channel_datastore_add(qe->chan, datastore);
- ast_channel_unlock(qe->chan);
- } else
- dialed_interfaces = datastore->data;
-
- AST_LIST_LOCK(dialed_interfaces);
- AST_LIST_TRAVERSE(dialed_interfaces, di, list) {
- if (!strcasecmp(cur->interface, di->interface)) {
- ast_log(LOG_DEBUG, "Skipping dialing interface '%s' since it has already been dialed\n",
- di->interface);
- break;
- }
- }
- AST_LIST_UNLOCK(dialed_interfaces);
-
- if (di) {
- free(tmp);
- continue;
- }
-
- /* It is always ok to dial a Local interface. We only keep track of
- * which "real" interfaces have been dialed. The Local channel will
- * inherit this list so that if it ends up dialing a real interface,
- * it won't call one that has already been called. */
- if (strncasecmp(cur->interface, "Local/", 6)) {
- if (!(di = ast_calloc(1, sizeof(*di) + strlen(cur->interface)))) {
- ao2_ref(cur, -1);
- ast_mutex_unlock(&qe->parent->lock);
- if (use_weight)
- AST_LIST_UNLOCK(&queues);
- free(tmp);
- goto out;
- }
- strcpy(di->interface, cur->interface);
-
- AST_LIST_LOCK(dialed_interfaces);
- AST_LIST_INSERT_TAIL(dialed_interfaces, di, list);
- AST_LIST_UNLOCK(dialed_interfaces);
- }
-
- tmp->stillgoing = -1;
- tmp->member = cur;
- tmp->oldstatus = cur->status;
- tmp->lastcall = cur->lastcall;
- ast_copy_string(tmp->interface, cur->interface, sizeof(tmp->interface));
- /* Special case: If we ring everyone, go ahead and ring them, otherwise
- just calculate their metric for the appropriate strategy */
- if (!calc_metric(qe->parent, cur, x++, qe, tmp)) {
- /* Put them in the list of outgoing thingies... We're ready now.
- XXX If we're forcibly removed, these outgoing calls won't get
- hung up XXX */
- tmp->q_next = outgoing;
- outgoing = tmp;
- /* If this line is up, don't try anybody else */
- if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
- break;
- } else {
- ao2_ref(cur, -1);
- free(tmp);
- }
- }
- if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout))
- to = (qe->expire - now) * 1000;
- else
- to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1;
- ++qe->pending;
- ast_mutex_unlock(&qe->parent->lock);
- ring_one(qe, outgoing, &numbusies);
- if (use_weight)
- AST_LIST_UNLOCK(&queues);
- lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
- /* The ast_channel_datastore_remove() function could fail here if the
- * datastore was moved to another channel during a masquerade. If this is
- * the case, don't free the datastore here because later, when the channel
- * to which the datastore was moved hangs up, it will attempt to free this
- * datastore again, causing a crash
- */
- ast_channel_lock(qe->chan);
- if (datastore && !ast_channel_datastore_remove(qe->chan, datastore)) {
- ast_channel_datastore_free(datastore);
- }
- ast_channel_unlock(qe->chan);
- ast_mutex_lock(&qe->parent->lock);
- if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) {
- store_next(qe, outgoing);
- }
- ast_mutex_unlock(&qe->parent->lock);
- peer = lpeer ? lpeer->chan : NULL;
- if (!peer) {
- qe->pending = 0;
- if (to) {
- /* Must gotten hung up */
- res = -1;
- } else {
- /* User exited by pressing a digit */
- res = digit;
- }
- if (option_debug && res == -1)
- ast_log(LOG_DEBUG, "%s: Nobody answered.\n", qe->chan->name);
- } else { /* peer is valid */
- /* Ah ha! Someone answered within the desired timeframe. Of course after this
- we will always return with -1 so that it is hung up properly after the
- conversation. */
- if (!strcmp(qe->chan->tech->type, "Zap"))
- ast_channel_setoption(qe->chan, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
- if (!strcmp(peer->tech->type, "Zap"))
- ast_channel_setoption(peer, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
- /* Update parameters for the queue */
- time(&now);
- recalc_holdtime(qe, (now - qe->start));
- ast_mutex_lock(&qe->parent->lock);
- callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel);
- ast_mutex_unlock(&qe->parent->lock);
- member = lpeer->member;
- /* Increment the refcount for this member, since we're going to be using it for awhile in here. */
- ao2_ref(member, 1);
- hangupcalls(outgoing, peer);
- outgoing = NULL;
- if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
- int res2;
-
- res2 = ast_autoservice_start(qe->chan);
- if (!res2) {
- if (qe->parent->memberdelay) {
- ast_log(LOG_NOTICE, "Delaying member connect for %d seconds\n", qe->parent->memberdelay);
- res2 |= ast_safe_sleep(peer, qe->parent->memberdelay * 1000);
- }
- if (!res2 && announce) {
- play_file(peer, announce);
- }
- if (!res2 && qe->parent->reportholdtime) {
- if (!play_file(peer, qe->parent->sound_reporthold)) {
- int holdtime;
-
- time(&now);
- holdtime = abs((now - qe->start) / 60);
- if (holdtime < 2) {
- play_file(peer, qe->parent->sound_lessthan);
- ast_say_number(peer, 2, AST_DIGIT_ANY, peer->language, NULL);
- } else
- ast_say_number(peer, holdtime, AST_DIGIT_ANY, peer->language, NULL);
- play_file(peer, qe->parent->sound_minutes);
- }
- }
- }
- res2 |= ast_autoservice_stop(qe->chan);
- if (peer->_softhangup) {
- /* Agent must have hung up */
- ast_log(LOG_WARNING, "Agent on %s hungup on the customer.\n", peer->name);
- ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "AGENTDUMP", "%s", "");
- if (qe->parent->eventwhencalled)
- manager_event(EVENT_FLAG_AGENT, "AgentDump",
- "Queue: %s\r\n"
- "Uniqueid: %s\r\n"
- "Channel: %s\r\n"
- "Member: %s\r\n"
- "MemberName: %s\r\n"
- "%s",
- queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
- qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
- ast_hangup(peer);
- ao2_ref(member, -1);
- goto out;
- } else if (res2) {
- /* Caller must have hung up just before being connected*/
- ast_log(LOG_NOTICE, "Caller was about to talk to agent on %s but the caller hungup.\n", peer->name);
- ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long)time(NULL) - qe->start);
- record_abandoned(qe);
- ast_hangup(peer);
- ao2_ref(member, -1);
- return -1;
- }
- }
- /* Stop music on hold */
- ast_moh_stop(qe->chan);
- /* If appropriate, log that we have a destination channel */
- if (qe->chan->cdr)
- ast_cdr_setdestchan(qe->chan->cdr, peer->name);
- /* Make sure channels are compatible */
- res = ast_channel_make_compatible(qe->chan, peer);
- if (res < 0) {
- ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "SYSCOMPAT", "%s", "");
- ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name);
- record_abandoned(qe);
- ast_hangup(peer);
- ao2_ref(member, -1);
- return -1;
- }
-
- if (qe->parent->setinterfacevar)
- pbx_builtin_setvar_helper(qe->chan, "MEMBERINTERFACE", member->interface);
-
- /* Begin Monitoring */
- if (qe->parent->monfmt && *qe->parent->monfmt) {
- if (!qe->parent->montype) {
- if (option_debug)
- ast_log(LOG_DEBUG, "Starting Monitor as requested.\n");
- monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME");
- if (pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC") || pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC_ARGS"))
- which = qe->chan;
- else
- which = peer;
- if (monitorfilename)
- ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1 );
- else if (qe->chan->cdr)
- ast_monitor_start(which, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 );
- else {
- /* Last ditch effort -- no CDR, make up something */
- snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
- ast_monitor_start(which, qe->parent->monfmt, tmpid, 1 );
- }
- if (qe->parent->monjoin)
- ast_monitor_setjoinfiles(which, 1);
- } else {
- if (option_debug)
- ast_log(LOG_DEBUG, "Starting MixMonitor as requested.\n");
- monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME");
- if (!monitorfilename) {
- if (qe->chan->cdr)
- ast_copy_string(tmpid, qe->chan->cdr->uniqueid, sizeof(tmpid)-1);
- else
- snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
- } else {
- ast_copy_string(tmpid2, monitorfilename, sizeof(tmpid2)-1);
- for (p = tmpid2; *p ; p++) {
- if (*p == '^' && *(p+1) == '{') {
- *p = '$';
- }
- }
-
- memset(tmpid, 0, sizeof(tmpid));
- pbx_substitute_variables_helper(qe->chan, tmpid2, tmpid, sizeof(tmpid) - 1);
- }
-
- monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC");
- monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS");
-
- if (monitor_exec) {
- ast_copy_string(meid2, monitor_exec, sizeof(meid2)-1);
- for (p = meid2; *p ; p++) {
- if (*p == '^' && *(p+1) == '{') {
- *p = '$';
- }
- }
-
- memset(meid, 0, sizeof(meid));
- pbx_substitute_variables_helper(qe->chan, meid2, meid, sizeof(meid) - 1);
- }
-
- snprintf(tmpid2, sizeof(tmpid2)-1, "%s.%s", tmpid, qe->parent->monfmt);
-
- mixmonapp = pbx_findapp("MixMonitor");
-
- if (strchr(tmpid2, '|')) {
- ast_log(LOG_WARNING, "monitor-format (in queues.conf) and MONITOR_FILENAME cannot contain a '|'! Not recording.\n");
- mixmonapp = NULL;
- }
-
- if (!monitor_options)
- monitor_options = "";
-
- if (strchr(monitor_options, '|')) {
- ast_log(LOG_WARNING, "MONITOR_OPTIONS cannot contain a '|'! Not recording.\n");
- mixmonapp = NULL;
- }
-
- if (mixmonapp) {
- if (!ast_strlen_zero(monitor_exec))
- snprintf(mixmonargs, sizeof(mixmonargs)-1, "%s|b%s|%s", tmpid2, monitor_options, monitor_exec);
- else
- snprintf(mixmonargs, sizeof(mixmonargs)-1, "%s|b%s", tmpid2, monitor_options);
-
- if (option_debug)
- ast_log(LOG_DEBUG, "Arguments being passed to MixMonitor: %s\n", mixmonargs);
- /* We purposely lock the CDR so that pbx_exec does not update the application data */
- if (qe->chan->cdr)
- ast_set_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED);
- ret = pbx_exec(qe->chan, mixmonapp, mixmonargs);
- if (qe->chan->cdr)
- ast_clear_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED);
-
- } else
- ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n");
-
- }
- }
- /* Drop out of the queue at this point, to prepare for next caller */
- leave_queue(qe);
- if (!ast_strlen_zero(url) && ast_channel_supports_html(peer)) {
- if (option_debug)
- ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url);
- ast_channel_sendurl(peer, url);
- }
- if (!ast_strlen_zero(agi)) {
- if (option_debug)
- ast_log(LOG_DEBUG, "app_queue: agi=%s.\n", agi);
- app = pbx_findapp("agi");
- if (app) {
- agiexec = ast_strdupa(agi);
- ret = pbx_exec(qe->chan, app, agiexec);
- } else
- ast_log(LOG_WARNING, "Asked to execute an AGI on this channel, but could not find application (agi)!\n");
- }
- qe->handled++;
- ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "CONNECT", "%ld|%s", (long)time(NULL) - qe->start, peer->uniqueid);
- if (qe->parent->eventwhencalled)
- manager_event(EVENT_FLAG_AGENT, "AgentConnect",
- "Queue: %s\r\n"
- "Uniqueid: %s\r\n"
- "Channel: %s\r\n"
- "Member: %s\r\n"
- "MemberName: %s\r\n"
- "Holdtime: %ld\r\n"
- "BridgedChannel: %s\r\n"
- "%s",
- queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
- (long)time(NULL) - qe->start, peer->uniqueid,
- qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
- ast_copy_string(oldcontext, qe->chan->context, sizeof(oldcontext));
- ast_copy_string(oldexten, qe->chan->exten, sizeof(oldexten));
- time(&callstart);
-
- if (member->status == AST_DEVICE_NOT_INUSE)
- ast_log(LOG_WARNING, "The device state of this queue member, %s, is still 'Not in Use' when it probably should not be! Please check UPGRADE.txt for correct configuration settings.\n", member->membername);
-
- transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl);
- bridge = ast_bridge_call(qe->chan,peer, &bridge_config);
-
- ast_channel_lock(qe->chan);
- if (!attended_transfer_occurred(qe->chan)) {
- struct ast_datastore *tds;
- if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
- ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld",
- qe->chan->exten, qe->chan->context, (long) (callstart - qe->start),
- (long) (time(NULL) - callstart));
- } else if (qe->chan->_softhangup) {
- ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d",
- (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
- if (qe->parent->eventwhencalled)
- manager_event(EVENT_FLAG_AGENT, "AgentComplete",
- "Queue: %s\r\n"
- "Uniqueid: %s\r\n"
- "Channel: %s\r\n"
- "Member: %s\r\n"
- "MemberName: %s\r\n"
- "HoldTime: %ld\r\n"
- "TalkTime: %ld\r\n"
- "Reason: caller\r\n"
- "%s",
- queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
- (long)(callstart - qe->start), (long)(time(NULL) - callstart),
- qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
- } else {
- ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d",
- (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos);
- if (qe->parent->eventwhencalled)
- manager_event(EVENT_FLAG_AGENT, "AgentComplete",
- "Queue: %s\r\n"
- "Uniqueid: %s\r\n"
- "Channel: %s\r\n"
- "MemberName: %s\r\n"
- "HoldTime: %ld\r\n"
- "TalkTime: %ld\r\n"
- "Reason: agent\r\n"
- "%s",
- queuename, qe->chan->uniqueid, peer->name, member->membername, (long)(callstart - qe->start),
- (long)(time(NULL) - callstart),
- qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
- }
- if ((tds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL))) {
- ast_channel_datastore_remove(qe->chan, tds);
- }
- update_queue(qe->parent, member, callcompletedinsl);
- }
-
- if (transfer_ds) {
- ast_channel_datastore_free(transfer_ds);
- }
- ast_channel_unlock(qe->chan);
- ast_hangup(peer);
- res = bridge ? bridge : 1;
- ao2_ref(member, -1);
- }
-out:
- hangupcalls(outgoing, NULL);
-
- return res;
-}
-
-static int wait_a_bit(struct queue_ent *qe)
-{
- /* Don't need to hold the lock while we setup the outgoing calls */
- int retrywait = qe->parent->retry * 1000;
-
- int res = ast_waitfordigit(qe->chan, retrywait);
- if (res > 0 && !valid_exit(qe, res))
- res = 0;
-
- return res;
-}
-
-static struct member *interface_exists(struct call_queue *q, const char *interface)
-{
- struct member *mem;
- struct ao2_iterator mem_iter;
-
- if (!q)
- return NULL;
-
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((mem = ao2_iterator_next(&mem_iter))) {
- if (!strcasecmp(interface, mem->interface))
- return mem;
- ao2_ref(mem, -1);
- }
-
- return NULL;
-}
-
-
-/* Dump all members in a specific queue to the database
- *
- * <pm_family>/<queuename> = <interface>;<penalty>;<paused>[|...]
- *
- */
-static void dump_queue_members(struct call_queue *pm_queue)
-{
- struct member *cur_member;
- char value[PM_MAX_LEN];
- int value_len = 0;
- int res;
- struct ao2_iterator mem_iter;
-
- memset(value, 0, sizeof(value));
-
- if (!pm_queue)
- return;
-
- mem_iter = ao2_iterator_init(pm_queue->members, 0);
- while ((cur_member = ao2_iterator_next(&mem_iter))) {
- if (!cur_member->dynamic) {
- ao2_ref(cur_member, -1);
- continue;
- }
-
- res = snprintf(value + value_len, sizeof(value) - value_len, "%s%s;%d;%d;%s",
- value_len ? "|" : "", cur_member->interface, cur_member->penalty, cur_member->paused, cur_member->membername);
-
- ao2_ref(cur_member, -1);
-
- if (res != strlen(value + value_len)) {
- ast_log(LOG_WARNING, "Could not create persistent member string, out of space\n");
- break;
- }
- value_len += res;
- }
-
- if (value_len && !cur_member) {
- if (ast_db_put(pm_family, pm_queue->name, value))
- ast_log(LOG_WARNING, "failed to create persistent dynamic entry!\n");
- } else
- /* Delete the entry if the queue is empty or there is an error */
- ast_db_del(pm_family, pm_queue->name);
-}
-
-static int remove_from_queue(const char *queuename, const char *interface)
-{
- struct call_queue *q;
- struct member *mem, tmpmem;
- int res = RES_NOSUCHQUEUE;
-
- ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
-
- AST_LIST_LOCK(&queues);
- AST_LIST_TRAVERSE(&queues, q, list) {
- ast_mutex_lock(&q->lock);
- if (strcmp(q->name, queuename)) {
- ast_mutex_unlock(&q->lock);
- continue;
- }
-
- if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) {
- /* XXX future changes should beware of this assumption!! */
- if (!mem->dynamic) {
- res = RES_NOT_DYNAMIC;
- ao2_ref(mem, -1);
- ast_mutex_unlock(&q->lock);
- break;
- }
- q->membercount--;
- manager_event(EVENT_FLAG_AGENT, "QueueMemberRemoved",
- "Queue: %s\r\n"
- "Location: %s\r\n"
- "MemberName: %s\r\n",
- q->name, mem->interface, mem->membername);
- ao2_unlink(q->members, mem);
- ao2_ref(mem, -1);
-
- if (queue_persistent_members)
- dump_queue_members(q);
-
- res = RES_OKAY;
- } else {
- res = RES_EXISTS;
- }
- ast_mutex_unlock(&q->lock);
- break;
- }
-
- if (res == RES_OKAY)
- remove_from_interfaces(interface);
-
- AST_LIST_UNLOCK(&queues);
-
- return res;
-}
-
-
-static int add_to_queue(const char *queuename, const char *interface, const char *membername, int penalty, int paused, int dump)
-{
- struct call_queue *q;
- struct member *new_member, *old_member;
- int res = RES_NOSUCHQUEUE;
-
- /* \note Ensure the appropriate realtime queue is loaded. Note that this
- * short-circuits if the queue is already in memory. */
- if (!(q = load_realtime_queue(queuename)))
- return res;
-
- AST_LIST_LOCK(&queues);
-
- ast_mutex_lock(&q->lock);
- if ((old_member = interface_exists(q, interface)) == NULL) {
- add_to_interfaces(interface);
- if ((new_member = create_queue_member(interface, membername, penalty, paused))) {
- new_member->dynamic = 1;
- ao2_link(q->members, new_member);
- q->membercount++;
- manager_event(EVENT_FLAG_AGENT, "QueueMemberAdded",
- "Queue: %s\r\n"
- "Location: %s\r\n"
- "MemberName: %s\r\n"
- "Membership: %s\r\n"
- "Penalty: %d\r\n"
- "CallsTaken: %d\r\n"
- "LastCall: %d\r\n"
- "Status: %d\r\n"
- "Paused: %d\r\n",
- q->name, new_member->interface, new_member->membername,
- "dynamic",
- new_member->penalty, new_member->calls, (int) new_member->lastcall,
- new_member->status, new_member->paused);
-
- ao2_ref(new_member, -1);
- new_member = NULL;
-
- if (dump)
- dump_queue_members(q);
-
- res = RES_OKAY;
- } else {
- res = RES_OUTOFMEMORY;
- }
- } else {
- ao2_ref(old_member, -1);
- res = RES_EXISTS;
- }
- ast_mutex_unlock(&q->lock);
- AST_LIST_UNLOCK(&queues);
-
- return res;
-}
-
-static int set_member_paused(const char *queuename, const char *interface, int paused)
-{
- int found = 0;
- struct call_queue *q;
- struct member *mem;
-
- /* Special event for when all queues are paused - individual events still generated */
- /* XXX In all other cases, we use the membername, but since this affects all queues, we cannot */
- if (ast_strlen_zero(queuename))
- ast_queue_log("NONE", "NONE", interface, (paused ? "PAUSEALL" : "UNPAUSEALL"), "%s", "");
-
- AST_LIST_LOCK(&queues);
- AST_LIST_TRAVERSE(&queues, q, list) {
- ast_mutex_lock(&q->lock);
- if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) {
- if ((mem = interface_exists(q, interface))) {
- found++;
- if (mem->paused == paused)
- ast_log(LOG_DEBUG, "%spausing already-%spaused queue member %s:%s\n", (paused ? "" : "un"), (paused ? "" : "un"), q->name, interface);
- mem->paused = paused;
-
- if (queue_persistent_members)
- dump_queue_members(q);
-
- if (mem->realtime)
- update_realtime_member_field(mem, q->name, "paused", paused ? "1" : "0");
-
- ast_queue_log(q->name, "NONE", mem->membername, (paused ? "PAUSE" : "UNPAUSE"), "%s", "");
-
- manager_event(EVENT_FLAG_AGENT, "QueueMemberPaused",
- "Queue: %s\r\n"
- "Location: %s\r\n"
- "MemberName: %s\r\n"
- "Paused: %d\r\n",
- q->name, mem->interface, mem->membername, paused);
- ao2_ref(mem, -1);
- }
- }
- ast_mutex_unlock(&q->lock);
- }
- AST_LIST_UNLOCK(&queues);
-
- return found ? RESULT_SUCCESS : RESULT_FAILURE;
-}
-
-/* Reload dynamic queue members persisted into the astdb */
-static void reload_queue_members(void)
-{
- char *cur_ptr;
- char *queue_name;
- char *member;
- char *interface;
- char *membername = NULL;
- char *penalty_tok;
- int penalty = 0;
- char *paused_tok;
- int paused = 0;
- struct ast_db_entry *db_tree;
- struct ast_db_entry *entry;
- struct call_queue *cur_queue;
- char queue_data[PM_MAX_LEN];
-
- AST_LIST_LOCK(&queues);
-
- /* Each key in 'pm_family' is the name of a queue */
- db_tree = ast_db_gettree(pm_family, NULL);
- for (entry = db_tree; entry; entry = entry->next) {
-
- queue_name = entry->key + strlen(pm_family) + 2;
-
- AST_LIST_TRAVERSE(&queues, cur_queue, list) {
- ast_mutex_lock(&cur_queue->lock);
- if (!strcmp(queue_name, cur_queue->name))
- break;
- ast_mutex_unlock(&cur_queue->lock);
- }
-
- if (!cur_queue)
- cur_queue = load_realtime_queue(queue_name);
-
- if (!cur_queue) {
- /* If the queue no longer exists, remove it from the
- * database */
- ast_log(LOG_WARNING, "Error loading persistent queue: '%s': it does not exist\n", queue_name);
- ast_db_del(pm_family, queue_name);
- continue;
- } else
- ast_mutex_unlock(&cur_queue->lock);
-
- if (ast_db_get(pm_family, queue_name, queue_data, PM_MAX_LEN))
- continue;
-
- cur_ptr = queue_data;
- while ((member = strsep(&cur_ptr, "|"))) {
- if (ast_strlen_zero(member))
- continue;
-
- interface = strsep(&member, ";");
- penalty_tok = strsep(&member, ";");
- paused_tok = strsep(&member, ";");
- membername = strsep(&member, ";");
-
- if (!penalty_tok) {
- ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (penalty)\n", queue_name);
- break;
- }
- penalty = strtol(penalty_tok, NULL, 10);
- if (errno == ERANGE) {
- ast_log(LOG_WARNING, "Error converting penalty: %s: Out of range.\n", penalty_tok);
- break;
- }
-
- if (!paused_tok) {
- ast_log(LOG_WARNING, "Error parsing persistent member string for '%s' (paused)\n", queue_name);
- break;
- }
- paused = strtol(paused_tok, NULL, 10);
- if ((errno == ERANGE) || paused < 0 || paused > 1) {
- ast_log(LOG_WARNING, "Error converting paused: %s: Expected 0 or 1.\n", paused_tok);
- break;
- }
- if (ast_strlen_zero(membername))
- membername = interface;
-
- if (option_debug)
- ast_log(LOG_DEBUG, "Reload Members: Queue: %s Member: %s Name: %s Penalty: %d Paused: %d\n", queue_name, interface, membername, penalty, paused);
-
- if (add_to_queue(queue_name, interface, membername, penalty, paused, 0) == RES_OUTOFMEMORY) {
- ast_log(LOG_ERROR, "Out of Memory when reloading persistent queue member\n");
- break;
- }
- }
- }
-
- AST_LIST_UNLOCK(&queues);
- if (db_tree) {
- ast_log(LOG_NOTICE, "Queue members successfully reloaded from database.\n");
- ast_db_freetree(db_tree);
- }
-}
-
-static int pqm_exec(struct ast_channel *chan, void *data)
-{
- struct ast_module_user *lu;
- char *parse;
- int priority_jump = 0;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(queuename);
- AST_APP_ARG(interface);
- AST_APP_ARG(options);
- );
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_WARNING, "PauseQueueMember requires an argument ([queuename]|interface[|options])\n");
- return -1;
- }
-
- parse = ast_strdupa(data);
-
- AST_STANDARD_APP_ARGS(args, parse);
-
- lu = ast_module_user_add(chan);
-
- if (args.options) {
- if (strchr(args.options, 'j'))
- priority_jump = 1;
- }
-
- if (ast_strlen_zero(args.interface)) {
- ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n");
- ast_module_user_remove(lu);
- return -1;
- }
-
- if (set_member_paused(args.queuename, args.interface, 1)) {
- ast_log(LOG_WARNING, "Attempt to pause interface %s, not found\n", args.interface);
- if (priority_jump || ast_opt_priority_jumping) {
- if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
- pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND");
- ast_module_user_remove(lu);
- return 0;
- }
- }
- ast_module_user_remove(lu);
- pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND");
- return 0;
- }
-
- ast_module_user_remove(lu);
- pbx_builtin_setvar_helper(chan, "PQMSTATUS", "PAUSED");
-
- return 0;
-}
-
-static int upqm_exec(struct ast_channel *chan, void *data)
-{
- struct ast_module_user *lu;
- char *parse;
- int priority_jump = 0;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(queuename);
- AST_APP_ARG(interface);
- AST_APP_ARG(options);
- );
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_WARNING, "UnpauseQueueMember requires an argument ([queuename]|interface[|options])\n");
- return -1;
- }
-
- parse = ast_strdupa(data);
-
- AST_STANDARD_APP_ARGS(args, parse);
-
- lu = ast_module_user_add(chan);
-
- if (args.options) {
- if (strchr(args.options, 'j'))
- priority_jump = 1;
- }
-
- if (ast_strlen_zero(args.interface)) {
- ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n");
- ast_module_user_remove(lu);
- return -1;
- }
-
- if (set_member_paused(args.queuename, args.interface, 0)) {
- ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", args.interface);
- if (priority_jump || ast_opt_priority_jumping) {
- if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
- pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND");
- ast_module_user_remove(lu);
- return 0;
- }
- }
- ast_module_user_remove(lu);
- pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND");
- return 0;
- }
-
- ast_module_user_remove(lu);
- pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "UNPAUSED");
-
- return 0;
-}
-
-static int rqm_exec(struct ast_channel *chan, void *data)
-{
- int res=-1;
- struct ast_module_user *lu;
- char *parse, *temppos = NULL;
- int priority_jump = 0;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(queuename);
- AST_APP_ARG(interface);
- AST_APP_ARG(options);
- );
-
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename[|interface[|options]])\n");
- return -1;
- }
-
- parse = ast_strdupa(data);
-
- AST_STANDARD_APP_ARGS(args, parse);
-
- lu = ast_module_user_add(chan);
-
- if (ast_strlen_zero(args.interface)) {
- args.interface = ast_strdupa(chan->name);
- temppos = strrchr(args.interface, '-');
- if (temppos)
- *temppos = '\0';
- }
-
- if (args.options) {
- if (strchr(args.options, 'j'))
- priority_jump = 1;
- }
-
- switch (remove_from_queue(args.queuename, args.interface)) {
- case RES_OKAY:
- ast_queue_log(args.queuename, chan->uniqueid, args.interface, "REMOVEMEMBER", "%s", "");
- ast_log(LOG_NOTICE, "Removed interface '%s' from queue '%s'\n", args.interface, args.queuename);
- pbx_builtin_setvar_helper(chan, "RQMSTATUS", "REMOVED");
- res = 0;
- break;
- case RES_EXISTS:
- ast_log(LOG_DEBUG, "Unable to remove interface '%s' from queue '%s': Not there\n", args.interface, args.queuename);
- if (priority_jump || ast_opt_priority_jumping)
- ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
- pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTINQUEUE");
- res = 0;
- break;
- case RES_NOSUCHQUEUE:
- ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", args.queuename);
- pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOSUCHQUEUE");
- res = 0;
- break;
- case RES_NOT_DYNAMIC:
- ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': '%s' is not a dynamic member\n", args.queuename, args.interface);
- pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTDYNAMIC");
- res = 0;
- break;
- }
-
- ast_module_user_remove(lu);
-
- return res;
-}
-
-static int aqm_exec(struct ast_channel *chan, void *data)
-{
- int res=-1;
- struct ast_module_user *lu;
- char *parse, *temppos = NULL;
- int priority_jump = 0;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(queuename);
- AST_APP_ARG(interface);
- AST_APP_ARG(penalty);
- AST_APP_ARG(options);
- AST_APP_ARG(membername);
- );
- int penalty = 0;
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[|[interface]|[penalty][|options][|membername]])\n");
- return -1;
- }
-
- parse = ast_strdupa(data);
-
- AST_STANDARD_APP_ARGS(args, parse);
-
- lu = ast_module_user_add(chan);
-
- if (ast_strlen_zero(args.interface)) {
- args.interface = ast_strdupa(chan->name);
- temppos = strrchr(args.interface, '-');
- if (temppos)
- *temppos = '\0';
- }
-
- if (!ast_strlen_zero(args.penalty)) {
- if ((sscanf(args.penalty, "%d", &penalty) != 1) || penalty < 0) {
- ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", args.penalty);
- penalty = 0;
- }
- }
-
- if (args.options) {
- if (strchr(args.options, 'j'))
- priority_jump = 1;
- }
-
- switch (add_to_queue(args.queuename, args.interface, args.membername, penalty, 0, queue_persistent_members)) {
- case RES_OKAY:
- ast_queue_log(args.queuename, chan->uniqueid, args.interface, "ADDMEMBER", "%s", "");
- ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", args.interface, args.queuename);
- pbx_builtin_setvar_helper(chan, "AQMSTATUS", "ADDED");
- res = 0;
- break;
- case RES_EXISTS:
- ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': Already there\n", args.interface, args.queuename);
- if (priority_jump || ast_opt_priority_jumping)
- ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
- pbx_builtin_setvar_helper(chan, "AQMSTATUS", "MEMBERALREADY");
- res = 0;
- break;
- case RES_NOSUCHQUEUE:
- ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", args.queuename);
- pbx_builtin_setvar_helper(chan, "AQMSTATUS", "NOSUCHQUEUE");
- res = 0;
- break;
- case RES_OUTOFMEMORY:
- ast_log(LOG_ERROR, "Out of memory adding member %s to queue %s\n", args.interface, args.queuename);
- break;
- }
-
- ast_module_user_remove(lu);
-
- return res;
-}
-
-static int ql_exec(struct ast_channel *chan, void *data)
-{
- struct ast_module_user *u;
- char *parse;
-
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(queuename);
- AST_APP_ARG(uniqueid);
- AST_APP_ARG(membername);
- AST_APP_ARG(event);
- AST_APP_ARG(params);
- );
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo]\n");
- return -1;
- }
-
- u = ast_module_user_add(chan);
-
- parse = ast_strdupa(data);
-
- AST_STANDARD_APP_ARGS(args, parse);
-
- if (ast_strlen_zero(args.queuename) || ast_strlen_zero(args.uniqueid)
- || ast_strlen_zero(args.membername) || ast_strlen_zero(args.event)) {
- ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|membername|event[|additionalinfo])\n");
- ast_module_user_remove(u);
- return -1;
- }
-
- ast_queue_log(args.queuename, args.uniqueid, args.membername, args.event,
- "%s", args.params ? args.params : "");
-
- ast_module_user_remove(u);
-
- return 0;
-}
-
-/*!\brief The starting point for all queue calls
- *
- * The process involved here is to
- * 1. Parse the options specified in the call to Queue()
- * 2. Join the queue
- * 3. Wait in a loop until it is our turn to try calling a queue member
- * 4. Attempt to call a queue member
- * 5. If 4. did not result in a bridged call, then check for between
- * call options such as periodic announcements etc.
- * 6. Try 4 again uless some condition (such as an expiration time) causes us to
- * exit the queue.
- */
-static int queue_exec(struct ast_channel *chan, void *data)
-{
- int res=-1;
- int ringing=0;
- struct ast_module_user *lu;
- const char *user_priority;
- const char *max_penalty_str;
- int prio;
- int max_penalty;
- enum queue_result reason = QUEUE_UNKNOWN;
- /* whether to exit Queue application after the timeout hits */
- int tries = 0;
- int noption = 0;
- char *parse;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(queuename);
- AST_APP_ARG(options);
- AST_APP_ARG(url);
- AST_APP_ARG(announceoverride);
- AST_APP_ARG(queuetimeoutstr);
- AST_APP_ARG(agi);
- );
- /* Our queue entry */
- struct queue_ent qe;
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_WARNING, "Queue requires an argument: queuename[|options[|URL[|announceoverride[|timeout[|agi]]]]]\n");
- return -1;
- }
-
- parse = ast_strdupa(data);
- AST_STANDARD_APP_ARGS(args, parse);
-
- lu = ast_module_user_add(chan);
-
- /* Setup our queue entry */
- memset(&qe, 0, sizeof(qe));
- qe.start = time(NULL);
-
- /* set the expire time based on the supplied timeout; */
- if (!ast_strlen_zero(args.queuetimeoutstr))
- qe.expire = qe.start + atoi(args.queuetimeoutstr);
- else
- qe.expire = 0;
-
- /* Get the priority from the variable ${QUEUE_PRIO} */
- user_priority = pbx_builtin_getvar_helper(chan, "QUEUE_PRIO");
- if (user_priority) {
- if (sscanf(user_priority, "%d", &prio) == 1) {
- if (option_debug)
- ast_log(LOG_DEBUG, "%s: Got priority %d from ${QUEUE_PRIO}.\n",
- chan->name, prio);
- } else {
- ast_log(LOG_WARNING, "${QUEUE_PRIO}: Invalid value (%s), channel %s.\n",
- user_priority, chan->name);
- prio = 0;
- }
- } else {
- if (option_debug > 2)
- ast_log(LOG_DEBUG, "NO QUEUE_PRIO variable found. Using default.\n");
- prio = 0;
- }
-
- /* Get the maximum penalty from the variable ${QUEUE_MAX_PENALTY} */
- if ((max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY"))) {
- if (sscanf(max_penalty_str, "%d", &max_penalty) == 1) {
- if (option_debug)
- ast_log(LOG_DEBUG, "%s: Got max penalty %d from ${QUEUE_MAX_PENALTY}.\n",
- chan->name, max_penalty);
- } else {
- ast_log(LOG_WARNING, "${QUEUE_MAX_PENALTY}: Invalid value (%s), channel %s.\n",
- max_penalty_str, chan->name);
- max_penalty = 0;
- }
- } else {
- max_penalty = 0;
- }
-
- if (args.options && (strchr(args.options, 'r')))
- ringing = 1;
-
- if (option_debug)
- ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s, expires: %ld, priority: %d\n",
- args.queuename, args.options, args.url, args.announceoverride, (long)qe.expire, prio);
-
- qe.chan = chan;
- qe.prio = prio;
- qe.max_penalty = max_penalty;
- qe.last_pos_said = 0;
- qe.last_pos = 0;
- qe.last_periodic_announce_time = time(NULL);
- qe.last_periodic_announce_sound = 0;
- qe.valid_digits = 0;
- if (!join_queue(args.queuename, &qe, &reason)) {
- int makeannouncement = 0;
-
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""),
- S_OR(chan->cid.cid_num, ""));
-check_turns:
- if (ringing) {
- ast_indicate(chan, AST_CONTROL_RINGING);
- } else {
- ast_moh_start(chan, qe.moh, NULL);
- }
-
- /* This is the wait loop for callers 2 through maxlen */
- res = wait_our_turn(&qe, ringing, &reason);
- if (res)
- goto stop;
-
- for (;;) {
- /* This is the wait loop for the head caller*/
- /* To exit, they may get their call answered; */
- /* they may dial a digit from the queue context; */
- /* or, they may timeout. */
-
- enum queue_member_status stat;
-
- /* Leave if we have exceeded our queuetimeout */
- if (qe.expire && (time(NULL) >= qe.expire)) {
- record_abandoned(&qe);
- reason = QUEUE_TIMEOUT;
- res = 0;
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
- break;
- }
-
- if (makeannouncement) {
- /* Make a position announcement, if enabled */
- if (qe.parent->announcefrequency && !ringing)
- if ((res = say_position(&qe)))
- goto stop;
-
- }
- makeannouncement = 1;
-
- /* Leave if we have exceeded our queuetimeout */
- if (qe.expire && (time(NULL) >= qe.expire)) {
- record_abandoned(&qe);
- reason = QUEUE_TIMEOUT;
- res = 0;
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
- break;
- }
- /* Make a periodic announcement, if enabled */
- if (qe.parent->periodicannouncefrequency && !ringing)
- if ((res = say_periodic_announcement(&qe)))
- goto stop;
-
- /* Leave if we have exceeded our queuetimeout */
- if (qe.expire && (time(NULL) >= qe.expire)) {
- record_abandoned(&qe);
- reason = QUEUE_TIMEOUT;
- res = 0;
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
- break;
- }
- /* Try calling all queue members for 'timeout' seconds */
- res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi);
- if (res)
- goto stop;
-
- stat = get_member_status(qe.parent, qe.max_penalty);
-
- /* exit after 'timeout' cycle if 'n' option enabled */
- if (noption && tries >= qe.parent->membercount) {
- if (option_verbose > 2)
- ast_verbose(VERBOSE_PREFIX_3 "Exiting on time-out cycle\n");
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
- record_abandoned(&qe);
- reason = QUEUE_TIMEOUT;
- res = 0;
- break;
- }
-
- /* leave the queue if no agents, if enabled */
- if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
- record_abandoned(&qe);
- reason = QUEUE_LEAVEEMPTY;
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
- res = 0;
- break;
- }
-
- /* leave the queue if no reachable agents, if enabled */
- if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
- record_abandoned(&qe);
- reason = QUEUE_LEAVEUNAVAIL;
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
- res = 0;
- break;
- }
-
- /* Leave if we have exceeded our queuetimeout */
- if (qe.expire && (time(NULL) >= qe.expire)) {
- record_abandoned(&qe);
- reason = QUEUE_TIMEOUT;
- res = 0;
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
- break;
- }
-
- /* If using dynamic realtime members, we should regenerate the member list for this queue */
- update_realtime_members(qe.parent);
-
- /* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
- res = wait_a_bit(&qe);
- if (res)
- goto stop;
-
- /* Since this is a priority queue and
- * it is not sure that we are still at the head
- * of the queue, go and check for our turn again.
- */
- if (!is_our_turn(&qe)) {
- if (option_debug)
- ast_log(LOG_DEBUG, "Darn priorities, going back in queue (%s)!\n",
- qe.chan->name);
- goto check_turns;
- }
- }
-
-stop:
- if (res) {
- if (res < 0) {
- if (!qe.handled) {
- record_abandoned(&qe);
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON",
- "%d|%d|%ld", qe.pos, qe.opos,
- (long) time(NULL) - qe.start);
- }
- res = -1;
- } else if (qe.valid_digits) {
- ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY",
- "%s|%d", qe.digits, qe.pos);
- }
- }
-
- /* Don't allow return code > 0 */
- if (res >= 0) {
- res = 0;
- if (ringing) {
- ast_indicate(chan, -1);
- } else {
- ast_moh_stop(chan);
- }
- ast_stopstream(chan);
- }
- leave_queue(&qe);
- if (reason != QUEUE_UNKNOWN)
- set_queue_result(chan, reason);
- } else {
- ast_log(LOG_WARNING, "Unable to join queue '%s'\n", args.queuename);
- set_queue_result(chan, reason);
- res = 0;
- }
- ast_module_user_remove(lu);
-
- return res;
-}
-
-static int queue_function_qac(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
-{
- int count = 0;
- struct call_queue *q;
- struct ast_module_user *lu;
- struct member *m;
- struct ao2_iterator mem_iter;
-
- buf[0] = '\0';
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
- return -1;
- }
-
- lu = ast_module_user_add(chan);
-
- if ((q = load_realtime_queue(data))) {
- ast_mutex_lock(&q->lock);
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((m = ao2_iterator_next(&mem_iter))) {
- /* Count the agents who are logged in and presently answering calls */
- if ((m->status != AST_DEVICE_UNAVAILABLE) && (m->status != AST_DEVICE_INVALID)) {
- count++;
- }
- ao2_ref(m, -1);
- }
- ast_mutex_unlock(&q->lock);
- } else
- ast_log(LOG_WARNING, "queue %s was not found\n", data);
-
- snprintf(buf, len, "%d", count);
- ast_module_user_remove(lu);
-
- return 0;
-}
-
-static int queue_function_queuewaitingcount(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
-{
- int count = 0;
- struct call_queue *q;
- struct ast_module_user *lu;
- struct ast_variable *var = NULL;
-
- buf[0] = '\0';
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
- return -1;
- }
-
- lu = ast_module_user_add(chan);
-
- AST_LIST_LOCK(&queues);
- AST_LIST_TRAVERSE(&queues, q, list) {
- if (!strcasecmp(q->name, data)) {
- ast_mutex_lock(&q->lock);
- break;
- }
- }
- AST_LIST_UNLOCK(&queues);
-
- if (q) {
- count = q->count;
- ast_mutex_unlock(&q->lock);
- } else if ((var = ast_load_realtime("queues", "name", data, NULL))) {
- /* if the queue is realtime but was not found in memory, this
- * means that the queue had been deleted from memory since it was
- * "dead." This means it has a 0 waiting count
- */
- count = 0;
- ast_variables_destroy(var);
- } else
- ast_log(LOG_WARNING, "queue %s was not found\n", data);
-
- snprintf(buf, len, "%d", count);
- ast_module_user_remove(lu);
- return 0;
-}
-
-static int queue_function_queuememberlist(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
-{
- struct ast_module_user *u;
- struct call_queue *q;
- struct member *m;
-
- /* Ensure an otherwise empty list doesn't return garbage */
- buf[0] = '\0';
-
- if (ast_strlen_zero(data)) {
- ast_log(LOG_ERROR, "QUEUE_MEMBER_LIST requires an argument: queuename\n");
- return -1;
- }
-
- u = ast_module_user_add(chan);
-
- AST_LIST_LOCK(&queues);
- AST_LIST_TRAVERSE(&queues, q, list) {
- if (!strcasecmp(q->name, data)) {
- ast_mutex_lock(&q->lock);
- break;
- }
- }
- AST_LIST_UNLOCK(&queues);
-
- if (q) {
- int buflen = 0, count = 0;
- struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
-
- while ((m = ao2_iterator_next(&mem_iter))) {
- /* strcat() is always faster than printf() */
- if (count++) {
- strncat(buf + buflen, ",", len - buflen - 1);
- buflen++;
- }
- strncat(buf + buflen, m->membername, len - buflen - 1);
- buflen += strlen(m->membername);
- /* Safeguard against overflow (negative length) */
- if (buflen >= len - 2) {
- ao2_ref(m, -1);
- ast_log(LOG_WARNING, "Truncating list\n");
- break;
- }
- ao2_ref(m, -1);
- }
- ast_mutex_unlock(&q->lock);
- } else
- ast_log(LOG_WARNING, "queue %s was not found\n", data);
-
- /* We should already be terminated, but let's make sure. */
- buf[len - 1] = '\0';
- ast_module_user_remove(u);
-
- return 0;
-}
-
-static struct ast_custom_function queueagentcount_function = {
- .name = "QUEUEAGENTCOUNT",
- .synopsis = "Count number of agents answering a queue",
- .syntax = "QUEUEAGENTCOUNT(<queuename>)",
- .desc =
-"Returns the number of members currently associated with the specified queue.\n"
-"This function is deprecated. You should use QUEUE_MEMBER_COUNT() instead.\n",
- .read = queue_function_qac,
-};
-
-static struct ast_custom_function queuemembercount_function = {
- .name = "QUEUE_MEMBER_COUNT",
- .synopsis = "Count number of members answering a queue",
- .syntax = "QUEUE_MEMBER_COUNT(<queuename>)",
- .desc =
-"Returns the number of members currently associated with the specified queue.\n",
- .read = queue_function_qac,
-};
-
-static struct ast_custom_function queuewaitingcount_function = {
- .name = "QUEUE_WAITING_COUNT",
- .synopsis = "Count number of calls currently waiting in a queue",
- .syntax = "QUEUE_WAITING_COUNT(<queuename>)",
- .desc =
-"Returns the number of callers currently waiting in the specified queue.\n",
- .read = queue_function_queuewaitingcount,
-};
-
-static struct ast_custom_function queuememberlist_function = {
- .name = "QUEUE_MEMBER_LIST",
- .synopsis = "Returns a list of interfaces on a queue",
- .syntax = "QUEUE_MEMBER_LIST(<queuename>)",
- .desc =
-"Returns a comma-separated list of members associated with the specified queue.\n",
- .read = queue_function_queuememberlist,
-};
-
-static int reload_queues(void)
-{
- struct call_queue *q;
- struct ast_config *cfg;
- char *cat, *tmp;
- struct ast_variable *var;
- struct member *cur, *newm;
- struct ao2_iterator mem_iter;
- int new;
- const char *general_val = NULL;
- char parse[80];
- char *interface;
- char *membername = NULL;
- int penalty;
- AST_DECLARE_APP_ARGS(args,
- AST_APP_ARG(interface);
- AST_APP_ARG(penalty);
- AST_APP_ARG(membername);
- );
-
- if (!(cfg = ast_config_load("queues.conf"))) {
- ast_log(LOG_NOTICE, "No call queueing config file (queues.conf), so no call queues\n");
- return 0;
- }
- AST_LIST_LOCK(&queues);
- use_weight=0;
- /* Mark all non-realtime queues as dead for the moment */
- AST_LIST_TRAVERSE(&queues, q, list) {
- if (!q->realtime) {
- q->dead = 1;
- q->found = 0;
- }
- }
-
- /* Chug through config file */
- cat = NULL;
- while ((cat = ast_category_browse(cfg, cat)) ) {
- if (!strcasecmp(cat, "general")) {
- /* Initialize global settings */
- queue_persistent_members = 0;
- if ((general_val = ast_variable_retrieve(cfg, "general", "persistentmembers")))
- queue_persistent_members = ast_true(general_val);
- autofill_default = 0;
- if ((general_val = ast_variable_retrieve(cfg, "general", "autofill")))
- autofill_default = ast_true(general_val);
- montype_default = 0;
- if ((general_val = ast_variable_retrieve(cfg, "general", "monitor-type")))
- if (!strcasecmp(general_val, "mixmonitor"))
- montype_default = 1;
- } else { /* Define queue */
- /* Look for an existing one */
- AST_LIST_TRAVERSE(&queues, q, list) {
- if (!strcmp(q->name, cat))
- break;
- }
- if (!q) {
- /* Make one then */
- if (!(q = alloc_queue(cat))) {
- /* TODO: Handle memory allocation failure */
- }
- new = 1;
- } else
- new = 0;
- if (q) {
- if (!new)
- ast_mutex_lock(&q->lock);
- /* Check if a queue with this name already exists */
- if (q->found) {
- ast_log(LOG_WARNING, "Queue '%s' already defined! Skipping!\n", cat);
- if (!new)
- ast_mutex_unlock(&q->lock);
- continue;
- }
- /* Re-initialize the queue, and clear statistics */
- init_queue(q);
- clear_queue(q);
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((cur = ao2_iterator_next(&mem_iter))) {
- if (!cur->dynamic) {
- cur->delme = 1;
- }
- ao2_ref(cur, -1);
- }
- for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
- if (!strcasecmp(var->name, "member")) {
- struct member tmpmem;
- membername = NULL;
-
- /* Add a new member */
- ast_copy_string(parse, var->value, sizeof(parse));
-
- AST_NONSTANDARD_APP_ARGS(args, parse, ',');
-
- interface = args.interface;
- if (!ast_strlen_zero(args.penalty)) {
- tmp = args.penalty;
- while (*tmp && *tmp < 33) tmp++;
- penalty = atoi(tmp);
- if (penalty < 0) {
- penalty = 0;
- }
- } else
- penalty = 0;
-
- if (!ast_strlen_zero(args.membername)) {
- membername = args.membername;
- while (*membername && *membername < 33) membername++;
- }
-
- /* Find the old position in the list */
- ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
- cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK);
-
- newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0);
- ao2_link(q->members, newm);
- ao2_ref(newm, -1);
- newm = NULL;
-
- if (cur)
- ao2_ref(cur, -1);
- else {
- /* Add them to the master int list if necessary */
- add_to_interfaces(interface);
- q->membercount++;
- }
- } else {
- queue_set_param(q, var->name, var->value, var->lineno, 1);
- }
- }
-
- /* Free remaining members marked as delme */
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((cur = ao2_iterator_next(&mem_iter))) {
- if (! cur->delme) {
- ao2_ref(cur, -1);
- continue;
- }
-
- q->membercount--;
- ao2_unlink(q->members, cur);
- remove_from_interfaces(cur->interface);
- ao2_ref(cur, -1);
- }
-
- if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN)
- rr_dep_warning();
-
- if (new) {
- AST_LIST_INSERT_HEAD(&queues, q, list);
- } else
- ast_mutex_unlock(&q->lock);
- }
- }
- }
- ast_config_destroy(cfg);
- AST_LIST_TRAVERSE_SAFE_BEGIN(&queues, q, list) {
- if (q->dead) {
- AST_LIST_REMOVE_CURRENT(&queues, list);
- if (!q->count)
- destroy_queue(q);
- else
- ast_log(LOG_DEBUG, "XXX Leaking a little memory :( XXX\n");
- } else {
- ast_mutex_lock(&q->lock);
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((cur = ao2_iterator_next(&mem_iter))) {
- if (cur->dynamic)
- q->membercount++;
- cur->status = ast_device_state(cur->interface);
- ao2_ref(cur, -1);
- }
- ast_mutex_unlock(&q->lock);
- }
- }
- AST_LIST_TRAVERSE_SAFE_END;
- AST_LIST_UNLOCK(&queues);
- return 1;
-}
-
-static int __queues_show(struct mansession *s, int manager, int fd, int argc, char **argv)
-{
- struct call_queue *q;
- struct queue_ent *qe;
- struct member *mem;
- int pos, queue_show;
- time_t now;
- char max_buf[150];
- char *max;
- size_t max_left;
- float sl = 0;
- char *term = manager ? "\r\n" : "\n";
- struct ao2_iterator mem_iter;
-
- time(&now);
- if (argc == 2)
- queue_show = 0;
- else if (argc == 3)
- queue_show = 1;
- else
- return RESULT_SHOWUSAGE;
-
- /* We only want to load realtime queues when a specific queue is asked for. */
- if (queue_show) {
- load_realtime_queue(argv[2]);
- } else if (ast_check_realtime("queues")) {
- struct ast_config *cfg = ast_load_realtime_multientry("queues", "name LIKE", "%", (char *) NULL);
- char *queuename;
- if (cfg) {
- for (queuename = ast_category_browse(cfg, NULL); !ast_strlen_zero(queuename); queuename = ast_category_browse(cfg, queuename)) {
- load_realtime_queue(queuename);
- }
- ast_config_destroy(cfg);
- }
- }
-
- AST_LIST_LOCK(&queues);
- if (AST_LIST_EMPTY(&queues)) {
- AST_LIST_UNLOCK(&queues);
- if (queue_show) {
- if (s)
- astman_append(s, "No such queue: %s.%s",argv[2], term);
- else
- ast_cli(fd, "No such queue: %s.%s",argv[2], term);
- } else {
- if (s)
- astman_append(s, "No queues.%s", term);
- else
- ast_cli(fd, "No queues.%s", term);
- }
- return RESULT_SUCCESS;
- }
- AST_LIST_TRAVERSE(&queues, q, list) {
- ast_mutex_lock(&q->lock);
- if (queue_show) {
- if (strcasecmp(q->name, argv[2]) != 0) {
- ast_mutex_unlock(&q->lock);
- if (!AST_LIST_NEXT(q, list)) {
- ast_cli(fd, "No such queue: %s.%s",argv[2], term);
- break;
- }
- continue;
- }
- }
- max_buf[0] = '\0';
- max = max_buf;
- max_left = sizeof(max_buf);
- if (q->maxlen)
- ast_build_string(&max, &max_left, "%d", q->maxlen);
- else
- ast_build_string(&max, &max_left, "unlimited");
- sl = 0;
- if (q->callscompleted > 0)
- sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted);
- if (s)
- astman_append(s, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s",
- q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight,
- q->callscompleted, q->callsabandoned,sl,q->servicelevel, term);
- else
- ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s",
- q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight, q->callscompleted, q->callsabandoned,sl,q->servicelevel, term);
- if (ao2_container_count(q->members)) {
- if (s)
- astman_append(s, " Members: %s", term);
- else
- ast_cli(fd, " Members: %s", term);
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((mem = ao2_iterator_next(&mem_iter))) {
- max_buf[0] = '\0';
- max = max_buf;
- max_left = sizeof(max_buf);
- if (strcasecmp(mem->membername, mem->interface)) {
- ast_build_string(&max, &max_left, " (%s)", mem->interface);
- }
- if (mem->penalty)
- ast_build_string(&max, &max_left, " with penalty %d", mem->penalty);
- if (mem->dynamic)
- ast_build_string(&max, &max_left, " (dynamic)");
- if (mem->realtime)
- ast_build_string(&max, &max_left, " (realtime)");
- if (mem->paused)
- ast_build_string(&max, &max_left, " (paused)");
- ast_build_string(&max, &max_left, " (%s)", devstate2str(mem->status));
- if (mem->calls) {
- ast_build_string(&max, &max_left, " has taken %d calls (last was %ld secs ago)",
- mem->calls, (long) (time(NULL) - mem->lastcall));
- } else
- ast_build_string(&max, &max_left, " has taken no calls yet");
- if (s)
- astman_append(s, " %s%s%s", mem->membername, max_buf, term);
- else
- ast_cli(fd, " %s%s%s", mem->membername, max_buf, term);
- ao2_ref(mem, -1);
- }
- } else if (s)
- astman_append(s, " No Members%s", term);
- else
- ast_cli(fd, " No Members%s", term);
- if (q->head) {
- pos = 1;
- if (s)
- astman_append(s, " Callers: %s", term);
- else
- ast_cli(fd, " Callers: %s", term);
- for (qe = q->head; qe; qe = qe->next) {
- if (s)
- astman_append(s, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s",
- pos++, qe->chan->name, (long) (now - qe->start) / 60,
- (long) (now - qe->start) % 60, qe->prio, term);
- else
- ast_cli(fd, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s", pos++,
- qe->chan->name, (long) (now - qe->start) / 60,
- (long) (now - qe->start) % 60, qe->prio, term);
- }
- } else if (s)
- astman_append(s, " No Callers%s", term);
- else
- ast_cli(fd, " No Callers%s", term);
- if (s)
- astman_append(s, "%s", term);
- else
- ast_cli(fd, "%s", term);
- ast_mutex_unlock(&q->lock);
- if (queue_show)
- break;
- }
- AST_LIST_UNLOCK(&queues);
- return RESULT_SUCCESS;
-}
-
-static int queue_show(int fd, int argc, char **argv)
-{
- return __queues_show(NULL, 0, fd, argc, argv);
-}
-
-static char *complete_queue(const char *line, const char *word, int pos, int state)
-{
- struct call_queue *q;
- char *ret = NULL;
- int which = 0;
- int wordlen = strlen(word);
-
- AST_LIST_LOCK(&queues);
- AST_LIST_TRAVERSE(&queues, q, list) {
- if (!strncasecmp(word, q->name, wordlen) && ++which > state) {
- ret = ast_strdup(q->name);
- break;
- }
- }
- AST_LIST_UNLOCK(&queues);
-
- return ret;
-}
-
-static char *complete_queue_show(const char *line, const char *word, int pos, int state)
-{
- if (pos == 2)
- return complete_queue(line, word, pos, state);
- return NULL;
-}
-
-/*!\brief callback to display queues status in manager
- \addtogroup Group_AMI
- */
-static int manager_queues_show(struct mansession *s, const struct message *m)
-{
- char *a[] = { "queue", "show" };
-
- __queues_show(s, 1, -1, 2, a);
- astman_append(s, "\r\n\r\n"); /* Properly terminate Manager output */
-
- return RESULT_SUCCESS;
-}
-
-/* Dump queue status */
-static int manager_queues_status(struct mansession *s, const struct message *m)
-{
- time_t now;
- int pos;
- const char *id = astman_get_header(m,"ActionID");
- const char *queuefilter = astman_get_header(m,"Queue");
- const char *memberfilter = astman_get_header(m,"Member");
- char idText[256] = "";
- struct call_queue *q;
- struct queue_ent *qe;
- float sl = 0;
- struct member *mem;
- struct ao2_iterator mem_iter;
-
- astman_send_ack(s, m, "Queue status will follow");
- time(&now);
- AST_LIST_LOCK(&queues);
- if (!ast_strlen_zero(id))
- snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
-
- AST_LIST_TRAVERSE(&queues, q, list) {
- ast_mutex_lock(&q->lock);
-
- /* List queue properties */
- if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) {
- sl = ((q->callscompleted > 0) ? 100 * ((float)q->callscompletedinsl / (float)q->callscompleted) : 0);
- astman_append(s, "Event: QueueParams\r\n"
- "Queue: %s\r\n"
- "Max: %d\r\n"
- "Calls: %d\r\n"
- "Holdtime: %d\r\n"
- "Completed: %d\r\n"
- "Abandoned: %d\r\n"
- "ServiceLevel: %d\r\n"
- "ServicelevelPerf: %2.1f\r\n"
- "Weight: %d\r\n"
- "%s"
- "\r\n",
- q->name, q->maxlen, q->count, q->holdtime, q->callscompleted,
- q->callsabandoned, q->servicelevel, sl, q->weight, idText);
- /* List Queue Members */
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((mem = ao2_iterator_next(&mem_iter))) {
- if (ast_strlen_zero(memberfilter) || !strcmp(mem->interface, memberfilter)) {
- astman_append(s, "Event: QueueMember\r\n"
- "Queue: %s\r\n"
- "Name: %s\r\n"
- "Location: %s\r\n"
- "Membership: %s\r\n"
- "Penalty: %d\r\n"
- "CallsTaken: %d\r\n"
- "LastCall: %d\r\n"
- "Status: %d\r\n"
- "Paused: %d\r\n"
- "%s"
- "\r\n",
- q->name, mem->membername, mem->interface, mem->dynamic ? "dynamic" : "static",
- mem->penalty, mem->calls, (int)mem->lastcall, mem->status, mem->paused, idText);
- }
- ao2_ref(mem, -1);
- }
- /* List Queue Entries */
- pos = 1;
- for (qe = q->head; qe; qe = qe->next) {
- astman_append(s, "Event: QueueEntry\r\n"
- "Queue: %s\r\n"
- "Position: %d\r\n"
- "Channel: %s\r\n"
- "CallerID: %s\r\n"
- "CallerIDName: %s\r\n"
- "Wait: %ld\r\n"
- "%s"
- "\r\n",
- q->name, pos++, qe->chan->name,
- S_OR(qe->chan->cid.cid_num, "unknown"),
- S_OR(qe->chan->cid.cid_name, "unknown"),
- (long) (now - qe->start), idText);
- }
- }
- ast_mutex_unlock(&q->lock);
- }
-
- astman_append(s,
- "Event: QueueStatusComplete\r\n"
- "%s"
- "\r\n",idText);
-
- AST_LIST_UNLOCK(&queues);
-
-
- return RESULT_SUCCESS;
-}
-
-static int manager_add_queue_member(struct mansession *s, const struct message *m)
-{
- const char *queuename, *interface, *penalty_s, *paused_s, *membername;
- int paused, penalty = 0;
-
- queuename = astman_get_header(m, "Queue");
- interface = astman_get_header(m, "Interface");
- penalty_s = astman_get_header(m, "Penalty");
- paused_s = astman_get_header(m, "Paused");
- membername = astman_get_header(m, "MemberName");
-
- if (ast_strlen_zero(queuename)) {
- astman_send_error(s, m, "'Queue' not specified.");
- return 0;
- }
-
- if (ast_strlen_zero(interface)) {
- astman_send_error(s, m, "'Interface' not specified.");
- return 0;
- }
-
- if (ast_strlen_zero(penalty_s))
- penalty = 0;
- else if (sscanf(penalty_s, "%d", &penalty) != 1 || penalty < 0)
- penalty = 0;
-
- if (ast_strlen_zero(paused_s))
- paused = 0;
- else
- paused = abs(ast_true(paused_s));
-
- switch (add_to_queue(queuename, interface, membername, penalty, paused, queue_persistent_members)) {
- case RES_OKAY:
- ast_queue_log(queuename, "MANAGER", interface, "ADDMEMBER", "%s", "");
- astman_send_ack(s, m, "Added interface to queue");
- break;
- case RES_EXISTS:
- astman_send_error(s, m, "Unable to add interface: Already there");
- break;
- case RES_NOSUCHQUEUE:
- astman_send_error(s, m, "Unable to add interface to queue: No such queue");
- break;
- case RES_OUTOFMEMORY:
- astman_send_error(s, m, "Out of memory");
- break;
- }
-
- return 0;
-}
-
-static int manager_remove_queue_member(struct mansession *s, const struct message *m)
-{
- const char *queuename, *interface;
-
- queuename = astman_get_header(m, "Queue");
- interface = astman_get_header(m, "Interface");
-
- if (ast_strlen_zero(queuename) || ast_strlen_zero(interface)) {
- astman_send_error(s, m, "Need 'Queue' and 'Interface' parameters.");
- return 0;
- }
-
- switch (remove_from_queue(queuename, interface)) {
- case RES_OKAY:
- ast_queue_log(queuename, "MANAGER", interface, "REMOVEMEMBER", "%s", "");
- astman_send_ack(s, m, "Removed interface from queue");
- break;
- case RES_EXISTS:
- astman_send_error(s, m, "Unable to remove interface: Not there");
- break;
- case RES_NOSUCHQUEUE:
- astman_send_error(s, m, "Unable to remove interface from queue: No such queue");
- break;
- case RES_OUTOFMEMORY:
- astman_send_error(s, m, "Out of memory");
- break;
- case RES_NOT_DYNAMIC:
- astman_send_error(s, m, "Member not dynamic");
- break;
- }
-
- return 0;
-}
-
-static int manager_pause_queue_member(struct mansession *s, const struct message *m)
-{
- const char *queuename, *interface, *paused_s;
- int paused;
-
- interface = astman_get_header(m, "Interface");
- paused_s = astman_get_header(m, "Paused");
- queuename = astman_get_header(m, "Queue"); /* Optional - if not supplied, pause the given Interface in all queues */
-
- if (ast_strlen_zero(interface) || ast_strlen_zero(paused_s)) {
- astman_send_error(s, m, "Need 'Interface' and 'Paused' parameters.");
- return 0;
- }
-
- paused = abs(ast_true(paused_s));
-
- if (set_member_paused(queuename, interface, paused))
- astman_send_error(s, m, "Interface not found");
- else
- astman_send_ack(s, m, paused ? "Interface paused successfully" : "Interface unpaused successfully");
- return 0;
-}
-
-static int handle_queue_add_member(int fd, int argc, char *argv[])
-{
- char *queuename, *interface, *membername = NULL;
- int penalty;
-
- if ((argc != 6) && (argc != 8) && (argc != 10)) {
- return RESULT_SHOWUSAGE;
- } else if (strcmp(argv[4], "to")) {
- return RESULT_SHOWUSAGE;
- } else if ((argc == 8) && strcmp(argv[6], "penalty")) {
- return RESULT_SHOWUSAGE;
- } else if ((argc == 10) && strcmp(argv[8], "as")) {
- return RESULT_SHOWUSAGE;
- }
-
- queuename = argv[5];
- interface = argv[3];
- if (argc >= 8) {
- if (sscanf(argv[7], "%d", &penalty) == 1) {
- if (penalty < 0) {
- ast_cli(fd, "Penalty must be >= 0\n");
- penalty = 0;
- }
- } else {
- ast_cli(fd, "Penalty must be an integer >= 0\n");
- penalty = 0;
- }
- } else {
- penalty = 0;
- }
-
- if (argc >= 10) {
- membername = argv[9];
- }
-
- switch (add_to_queue(queuename, interface, membername, penalty, 0, queue_persistent_members)) {
- case RES_OKAY:
- ast_queue_log(queuename, "CLI", interface, "ADDMEMBER", "%s", "");
- ast_cli(fd, "Added interface '%s' to queue '%s'\n", interface, queuename);
- return RESULT_SUCCESS;
- case RES_EXISTS:
- ast_cli(fd, "Unable to add interface '%s' to queue '%s': Already there\n", interface, queuename);
- return RESULT_FAILURE;
- case RES_NOSUCHQUEUE:
- ast_cli(fd, "Unable to add interface to queue '%s': No such queue\n", queuename);
- return RESULT_FAILURE;
- case RES_OUTOFMEMORY:
- ast_cli(fd, "Out of memory\n");
- return RESULT_FAILURE;
- default:
- return RESULT_FAILURE;
- }
-}
-
-static char *complete_queue_add_member(const char *line, const char *word, int pos, int state)
-{
- /* 0 - queue; 1 - add; 2 - member; 3 - <interface>; 4 - to; 5 - <queue>; 6 - penalty; 7 - <penalty>; 8 - as; 9 - <membername> */
- switch (pos) {
- case 3: /* Don't attempt to complete name of interface (infinite possibilities) */
- return NULL;
- case 4: /* only one possible match, "to" */
- return state == 0 ? ast_strdup("to") : NULL;
- case 5: /* <queue> */
- return complete_queue(line, word, pos, state);
- case 6: /* only one possible match, "penalty" */
- return state == 0 ? ast_strdup("penalty") : NULL;
- case 7:
- if (state < 100) { /* 0-99 */
- char *num;
- if ((num = ast_malloc(3))) {
- sprintf(num, "%d", state);
- }
- return num;
- } else {
- return NULL;
- }
- case 8: /* only one possible match, "as" */
- return state == 0 ? ast_strdup("as") : NULL;
- case 9: /* Don't attempt to complete name of member (infinite possibilities) */
- return NULL;
- default:
- return NULL;
- }
-}
-
-static int handle_queue_remove_member(int fd, int argc, char *argv[])
-{
- char *queuename, *interface;
-
- if (argc != 6) {
- return RESULT_SHOWUSAGE;
- } else if (strcmp(argv[4], "from")) {
- return RESULT_SHOWUSAGE;
- }
-
- queuename = argv[5];
- interface = argv[3];
-
- switch (remove_from_queue(queuename, interface)) {
- case RES_OKAY:
- ast_queue_log(queuename, "CLI", interface, "REMOVEMEMBER", "%s", "");
- ast_cli(fd, "Removed interface '%s' from queue '%s'\n", interface, queuename);
- return RESULT_SUCCESS;
- case RES_EXISTS:
- ast_cli(fd, "Unable to remove interface '%s' from queue '%s': Not there\n", interface, queuename);
- return RESULT_FAILURE;
- case RES_NOSUCHQUEUE:
- ast_cli(fd, "Unable to remove interface from queue '%s': No such queue\n", queuename);
- return RESULT_FAILURE;
- case RES_OUTOFMEMORY:
- ast_cli(fd, "Out of memory\n");
- return RESULT_FAILURE;
- case RES_NOT_DYNAMIC:
- ast_cli(fd, "Member not dynamic\n");
- return RESULT_FAILURE;
- default:
- return RESULT_FAILURE;
- }
-}
-
-static char *complete_queue_remove_member(const char *line, const char *word, int pos, int state)
-{
- int which = 0;
- struct call_queue *q;
- struct member *m;
- struct ao2_iterator mem_iter;
-
- /* 0 - queue; 1 - remove; 2 - member; 3 - <member>; 4 - from; 5 - <queue> */
- if (pos > 5 || pos < 3)
- return NULL;
- if (pos == 4) /* only one possible match, 'from' */
- return state == 0 ? ast_strdup("from") : NULL;
-
- if (pos == 5) /* No need to duplicate code */
- return complete_queue(line, word, pos, state);
-
- /* here is the case for 3, <member> */
- if (!AST_LIST_EMPTY(&queues)) { /* XXX unnecessary ? the traverse does that for us */
- AST_LIST_TRAVERSE(&queues, q, list) {
- ast_mutex_lock(&q->lock);
- mem_iter = ao2_iterator_init(q->members, 0);
- while ((m = ao2_iterator_next(&mem_iter))) {
- if (++which > state) {
- char *tmp;
- ast_mutex_unlock(&q->lock);
- tmp = ast_strdup(m->interface);
- ao2_ref(m, -1);
- return tmp;
- }
- ao2_ref(m, -1);
- }
- ast_mutex_unlock(&q->lock);
- }
- }
-
- return NULL;
-}
-
-static char queue_show_usage[] =
-"Usage: queue show\n"
-" Provides summary information on a specified queue.\n";
-
-static char qam_cmd_usage[] =
-"Usage: queue add member <channel> to <queue> [penalty <penalty>]\n";
-
-static char qrm_cmd_usage[] =
-"Usage: queue remove member <channel> from <queue>\n";
-
-static struct ast_cli_entry cli_show_queue_deprecated = {
- { "show", "queue", NULL },
- queue_show, NULL,
- NULL, complete_queue_show };
-
-static struct ast_cli_entry cli_add_queue_member_deprecated = {
- { "add", "queue", "member", NULL },
- handle_queue_add_member, NULL,
- NULL, complete_queue_add_member };
-
-static struct ast_cli_entry cli_remove_queue_member_deprecated = {
- { "remove", "queue", "member", NULL },
- handle_queue_remove_member, NULL,
- NULL, complete_queue_remove_member };
-
-static struct ast_cli_entry cli_queue[] = {
- /* Deprecated */
- { { "show", "queues", NULL },
- queue_show, NULL,
- NULL, NULL },
-
- { { "queue", "show", NULL },
- queue_show, "Show status of a specified queue",
- queue_show_usage, complete_queue_show, &cli_show_queue_deprecated },
-
- { { "queue", "add", "member", NULL },
- handle_queue_add_member, "Add a channel to a specified queue",
- qam_cmd_usage, complete_queue_add_member, &cli_add_queue_member_deprecated },
-
- { { "queue", "remove", "member", NULL },
- handle_queue_remove_member, "Removes a channel from a specified queue",
- qrm_cmd_usage, complete_queue_remove_member, &cli_remove_queue_member_deprecated },
-};
-
-static int unload_module(void)
-{
- int res;
-
- if (device_state.thread != AST_PTHREADT_NULL) {
- device_state.stop = 1;
- ast_mutex_lock(&device_state.lock);
- ast_cond_signal(&device_state.cond);
- ast_mutex_unlock(&device_state.lock);
- pthread_join(device_state.thread, NULL);
- }
-
- ast_cli_unregister_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry));
- res = ast_manager_unregister("QueueStatus");
- res |= ast_manager_unregister("Queues");
- res |= ast_manager_unregister("QueueAdd");
- res |= ast_manager_unregister("QueueRemove");
- res |= ast_manager_unregister("QueuePause");
- res |= ast_unregister_application(app_aqm);
- res |= ast_unregister_application(app_rqm);
- res |= ast_unregister_application(app_pqm);
- res |= ast_unregister_application(app_upqm);
- res |= ast_unregister_application(app_ql);
- res |= ast_unregister_application(app);
- res |= ast_custom_function_unregister(&queueagentcount_function);
- res |= ast_custom_function_unregister(&queuemembercount_function);
- res |= ast_custom_function_unregister(&queuememberlist_function);
- res |= ast_custom_function_unregister(&queuewaitingcount_function);
- ast_devstate_del(statechange_queue, NULL);
-
- ast_module_user_hangup_all();
-
- clear_and_free_interfaces();
-
- return res;
-}
-
-static int load_module(void)
-{
- int res;
-
- if (!reload_queues())
- return AST_MODULE_LOAD_DECLINE;
-
- if (queue_persistent_members)
- reload_queue_members();
-
- ast_mutex_init(&device_state.lock);
- ast_cond_init(&device_state.cond, NULL);
- ast_pthread_create(&device_state.thread, NULL, device_state_thread, NULL);
-
- ast_cli_register_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry));
- res = ast_register_application(app, queue_exec, synopsis, descrip);
- res |= ast_register_application(app_aqm, aqm_exec, app_aqm_synopsis, app_aqm_descrip);
- res |= ast_register_application(app_rqm, rqm_exec, app_rqm_synopsis, app_rqm_descrip);
- res |= ast_register_application(app_pqm, pqm_exec, app_pqm_synopsis, app_pqm_descrip);
- res |= ast_register_application(app_upqm, upqm_exec, app_upqm_synopsis, app_upqm_descrip);
- res |= ast_register_application(app_ql, ql_exec, app_ql_synopsis, app_ql_descrip);
- res |= ast_manager_register("Queues", 0, manager_queues_show, "Queues");
- res |= ast_manager_register("QueueStatus", 0, manager_queues_status, "Queue Status");
- res |= ast_manager_register("QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member, "Add interface to queue.");
- res |= ast_manager_register("QueueRemove", EVENT_FLAG_AGENT, manager_remove_queue_member, "Remove interface from queue.");
- res |= ast_manager_register("QueuePause", EVENT_FLAG_AGENT, manager_pause_queue_member, "Makes a queue member temporarily unavailable");
- res |= ast_custom_function_register(&queueagentcount_function);
- res |= ast_custom_function_register(&queuemembercount_function);
- res |= ast_custom_function_register(&queuememberlist_function);
- res |= ast_custom_function_register(&queuewaitingcount_function);
- res |= ast_devstate_add(statechange_queue, NULL);
-
- return res;
-}
-
-static int reload(void)
-{
- reload_queues();
- return 0;
-}
-
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "True Call Queueing",
- .load = load_module,
- .unload = unload_module,
- .reload = reload,
- );
-