aboutsummaryrefslogtreecommitdiffstats
path: root/apps/app_queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/app_queue.c')
-rw-r--r--apps/app_queue.c5140
1 files changed, 5140 insertions, 0 deletions
diff --git a/apps/app_queue.c b/apps/app_queue.c
new file mode 100644
index 000000000..474ba9d88
--- /dev/null
+++ b/apps/app_queue.c
@@ -0,0 +1,5140 @@
+/*
+ * 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,
+ );
+