Skip to content
Snippets Groups Projects
app_queue.c 161 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Copyright (C) 1999 - 2006, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Mark Spencer <markster@digium.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * 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.
     */
    
    
     * \brief True call queues with optional send URL on answer
    
     * \author Mark Spencer <markster@digium.com>
     *
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \arg Config in \ref Config_qu queues.conf
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
     *
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \par Development notes
    
     * \note 2004-11-25: Persistent Dynamic Members added by:
    
     *             NetNation Communications (www.netnation.com)
     *             Kevin Lindsay <kevinl@netnation.com>
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
     *
    
     *             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.
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
     *
    
     * \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>
     *
    
    Russell Bryant's avatar
    Russell Bryant committed
     * \ingroup applications
    
    #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/stringfields.h"
    
    #include "asterisk/astobj2.h"
    
    enum {
    	QUEUE_STRATEGY_RINGALL = 0,
    	QUEUE_STRATEGY_LEASTRECENT,
    	QUEUE_STRATEGY_FEWESTCALLS,
    	QUEUE_STRATEGY_RANDOM,
    
    static struct strategy {
    	int strategy;
    	char *name;
    } strategies[] = {
    	{ QUEUE_STRATEGY_RINGALL, "ringall" },
    	{ QUEUE_STRATEGY_LEASTRECENT, "leastrecent" },
    	{ QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
    	{ QUEUE_STRATEGY_RANDOM, "random" },
    
    	{ QUEUE_STRATEGY_RRMEMORY, "rrmemory" },
    
    Mark Spencer's avatar
    Mark Spencer committed
    #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 DEFAULT_MIN_ANNOUNCE_FREQUENCY 15 /* The minimum number of seconds between position announcements
                                                 The default value of 15 provides backwards compatibility */
    
    #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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *app = "Queue";
    
    static char *synopsis = "Queue a call for a call queue";
    
    static char *descrip =
    
    "  Queue(queuename[,options[,URL][,announceoverride][,timeout][,AGI][,macro][,gosub]):\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "Queues an incoming call in a particular call queue as defined in queues.conf.\n"
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    "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"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "The option string may contain zero or more of the following characters:\n"
    
    "      'c' -- continue in the dialplan if the callee hangs up.\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "      'd' -- data-quality (modem) call (minimum delay).\n"
    
    "      'h' -- allow callee to hang up by pressing *.\n"
    "      'H' -- allow caller to hang up by pressing *.\n"
    
    "      'n' -- no retries on the timeout; will exit this application and \n"
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    "             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. Periodic Announcements are still made, if applicable.\n"
    
    "      't' -- allow the called user to transfer the calling user.\n"
    "      'T' -- allow the calling user to transfer the call.\n"
    "      'w' -- allow the called user to write the conversation to disk via Monitor.\n"
    "      'W' -- allow the calling user to write the conversation to disk via Monitor.\n"
    
    "      'k' -- Allow the called party to enable parking of the call by sending\n"
    "             the DTMF sequence defined for call parking in features.conf.\n"
    "      'K' -- Allow the calling party to enable parking of the call by sending\n"
    "             the DTMF sequence defined for call parking in features.conf.\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "  In addition to transferring the call, a call may be parked and then picked\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "up by another user.\n"
    
    James Golovich's avatar
    James Golovich committed
    "  The optional URL will be sent to the called party if the channel supports\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 optional macro parameter will run a macro on the \n"
    "calling party's channel once they are connected to a queue member.\n"
    
    "  The optional gosub parameter will run a gosub 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 | CONTINUE\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 it will return an error.\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 it will return an error.\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[,reason]]):\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. The application will fail if the interface is not found.\n"
    
    "The reason string is entirely optional and is used to add extra information\n"
    "to the appropriate queue_log entries and manager events.\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[,reason]]):\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 reason string is entirely optional and is used to add extra information\n"
    "to the appropriate queue_log entries and manager events.\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";
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \brief Persistent Members astdb family */
    
    static const char *pm_family = "Queue/PersistentMembers";
    
    /* The maximum length of each persistent member queue database entry */
    
    /*! \brief queues.conf [general] option */
    static int queue_keep_stats = 0;
    
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \brief queues.conf [general] option */
    
    static int queue_persistent_members = 0;
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \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;
    
    
    /*! \brief queues.conf [general] option */
    static int shared_lastcall = 0;
    
    
    /*! \brief Subscription to device state change events */
    static struct ast_event_sub *device_state_sub;
    
    
    Jason Parker's avatar
    Jason Parker committed
    /*! \brief queues.conf [general] option */
    static int update_cdr = 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,
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    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" },
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \brief We define a custom "local user" structure because we
    
    Mark Spencer's avatar
    Mark Spencer committed
       use it not only for keeping track of what is in use but
       also for keeping track of who we're dialing. */
    
    
    struct callattempt {
    	struct callattempt *q_next;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *chan;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int stillgoing;
    
    	int metric;
    
    	time_t lastcall;
    
    Mark Spencer's avatar
    Mark Spencer committed
    struct queue_ent {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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 */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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 max_penalty;                    /*!< Limit the members that can take this call to this penalty or lower */
    
    	int linpos;							/*!< If using linear strategy, what position are we at? */
    	int linwrapped;						/*!< Is the linpos wrapped? */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    };
    
    struct member {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	char interface[80];                 /*!< Technology/Location */
    
    	char membername[80];                /*!< Member name to use in queue logs */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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? */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	int status;                         /*!< Status of queue member */
    	int paused;                         /*!< Are we paused (not accepting calls)? */
    	time_t lastcall;                    /*!< When last successful call was hungup */
    
    	struct call_queue *lastqueue;	    /*!< Last queue we received a call */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	unsigned int dead:1;                /*!< Used to detect members deleted in realtime */
    	unsigned int delme:1;               /*!< Flag to delete entry on reload */
    
    struct member_interface {
    
    	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 {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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 dead:1;
    	unsigned int joinempty:2;
    
    	unsigned int eventwhencalled:2;
    
    	unsigned int leavewhenempty:2;
    
    	unsigned int setqueuevar:1;
    	unsigned int setqueueentryvar:1;
    
    	unsigned int reportholdtime:1;
    	unsigned int wrapped:1;
    	unsigned int timeoutrestart:1;
    	unsigned int announceholdtime:2;
    
    	unsigned int announceposition:1;
    
    	unsigned int maskmemberstatus:1;
    	unsigned int realtime:1;
    
    	unsigned int found:1;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	int announcefrequency;              /*!< How often to announce their position */
    
    	int minannouncefrequency;           /*!< The minimum number of seconds between position announcements (def. 15) */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	int periodicannouncefrequency;      /*!< How often to play periodic announcement */
    	int roundingseconds;                /*!< How many seconds do we round to? */
    	int holdtime;                       /*!< Current avg holdtime, based on recursive boxcar filter */
    	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 membermacro[32];               /*!< Macro to run upon member connection */
    
    	char membergosub[32];               /*!< Gosub to run upon member connection */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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_callerannounce[80];      /*!< Sound file: Custom announce for caller, no default */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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 */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	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;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	struct queue_ent *head;             /*!< Head of the list of callers */
    	AST_LIST_ENTRY(call_queue) list;    /*!< Next call queue */
    
    static void update_realtime_members(struct call_queue *q);
    
    static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused);
    
    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;
    	}
    
    static int strat2int(const char *strategy)
    
    
    	for (x = 0; x < sizeof(strategies) / sizeof(strategies[0]); x++) {
    
    		if (!strcasecmp(strategy, strategies[x].name))
    			return strategies[x].strategy;
    	}
    
    static int queue_hash_cb(const void *obj, const int flags)
    {
    	const struct call_queue *q = obj;
    	return ast_str_hash(q->name);
    }
    
    static int queue_cmp_cb(void *obj, void *arg, int flags)
    {
    	struct call_queue *q = obj, *q2 = arg;
    	return !strcasecmp(q->name, q2->name) ? CMP_MATCH : 0;
    }
    
    static inline struct call_queue *queue_ref(struct call_queue *q)
    {
    	ao2_ref(q, 1);
    	return q;
    }
    
    static inline struct call_queue *queue_unref(struct call_queue *q)
    {
    	ao2_ref(q, -1);
    	return q;
    }
    
    
    static void set_queue_variables(struct queue_ent *qe)
    {
    
    	char interfacevar[256]="";
    	float sl = 0;
            
    	if (qe->parent->setqueuevar) {
    	sl = 0;
    	if (qe->parent->callscompleted > 0) 
    		sl = 100 * ((float) qe->parent->callscompletedinsl / (float) qe->parent->callscompleted);
    
    	snprintf(interfacevar,sizeof(interfacevar),
    		"QUEUEMAX=%d|QUEUESTRATEGY=%s|QUEUECALLS=%d|QUEUEHOLDTIME=%d|QUEUECOMPLETED=%d|QUEUEABANDONED=%d|QUEUESRVLEVEL=%d|QUEUESRVLEVELPERF=%2.1f",
    		qe->parent->maxlen, int2strat(qe->parent->strategy), qe->parent->count, qe->parent->holdtime, qe->parent->callscompleted,
    		qe->parent->callsabandoned,  qe->parent->servicelevel, sl);
    	
    	pbx_builtin_setvar(qe->chan, interfacevar); 
    	}
    }
    
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \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,
    
    static enum queue_member_status get_member_status(struct call_queue *q, int max_penalty)
    
    	struct ao2_iterator mem_iter;
    
    	enum queue_member_status result = QUEUE_NO_MEMBERS;
    
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    
    	for (; (member = ao2_iterator_next(&mem_iter)); ao2_ref(member, -1)) {
    		if (max_penalty && (member->penalty > max_penalty))
    
    			/* nothing to do */
    			break;
    		case AST_DEVICE_UNAVAILABLE:
    
    			if (result != QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS) 
    				result = QUEUE_NO_REACHABLE_MEMBERS;
    
    			if (member->paused) {
    				result = QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS;
    			} else {
    
    				ao2_ref(member, -1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    struct statechange {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int state;
    	char dev[0];
    };
    
    
    static void *handle_statechange(struct statechange *sc)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct call_queue *q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct member *cur;
    
    	struct ao2_iterator mem_iter;
    
    	struct member_interface *curint;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *loc;
    
    	char *technology;
    
    	technology = ast_strdupa(sc->dev);
    	loc = strchr(technology, '/');
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (loc) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} 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))
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			break;
    
    		ast_debug(3, "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));
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	}
    
    	ast_debug(1, "Device '%s/%s' changed to state '%d' (%s)\n", technology, loc, sc->state, devstate2str(sc->state));
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while ((q = ao2_iterator_next(&queue_iter))) {
    		ao2_lock(q);
    
    		mem_iter = ao2_iterator_init(q->members, 0);
    		while ((cur = ao2_iterator_next(&mem_iter))) {
    
    			char *interface;
    			char *slash_pos;
    			interface = ast_strdupa(cur->interface);
    			if ((slash_pos = strchr(interface, '/')))
    				if ((slash_pos = strchr(slash_pos + 1, '/')))
    					*slash_pos = '\0';
    
    
    			if (strcasecmp(sc->dev, interface)) {
    				ao2_ref(cur, -1);
    
    
    			if (cur->status != sc->state) {
    				cur->status = sc->state;
    
    				if (q->maskmemberstatus) {
    					ao2_ref(cur, -1);
    
    
    				manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					"Queue: %s\r\n"
    					"Location: %s\r\n"
    
    					"MemberName: %s\r\n"
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					"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",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    
    			ao2_ref(cur, -1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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,
    };
    
    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)
    
    	if (sc)
    		free(sc);
    
    	while ((sc = AST_LIST_REMOVE_HEAD(&device_state.state_change_q, entry)))
    		free(sc);
    
    
    static int statechange_queue(const char *dev, enum ast_device_state state)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return 0;
    }
    
    
    static void device_state_cb(const struct ast_event *event, void *unused)
    {
    	enum ast_device_state state;
    	const char *device;
    
    	state = ast_event_get_ie_uint(event, AST_EVENT_IE_STATE);
    	device = ast_event_get_ie_str(event, AST_EVENT_IE_DEVICE);
    
    	if (ast_strlen_zero(device)) {
    		ast_log(LOG_ERROR, "Received invalid event that had no device IE\n");
    		return;
    	}
    
    	statechange_queue(device, state);
    }
    
    
    static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused)
    
    	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 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 strcasecmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH;
    
    static void init_queue(struct call_queue *q)
    
    	q->dead = 0;
    	q->retry = DEFAULT_RETRY;
    	q->timeout = -1;
    	q->maxlen = 0;
    	q->announcefrequency = 0;
    
    	q->minannouncefrequency = DEFAULT_MIN_ANNOUNCE_FREQUENCY;
    
    	q->roundingseconds = 0; /* Default - don't announce seconds */
    	q->servicelevel = 0;
    
    	q->setqueuevar = 0;
    	q->setqueueentryvar = 0;
    
    	q->moh[0] = '\0';
    	q->announce[0] = '\0';
    	q->context[0] = '\0';
    	q->monfmt[0] = '\0';
    
    	q->periodicannouncefrequency = 0;
    
    	q->sound_callerannounce[0] = '\0';	/* Default, don't announce the caller that he has been answered */
    
    	if(!q->members) {
    		if(q->strategy == QUEUE_STRATEGY_LINEAR)
    			/* linear strategy depends on order, so we have to place all members in a single bucket */
    			q->members = ao2_container_alloc(1, member_hash_fn, member_cmp_fn);
    		else
    			q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
    	}
    
    	q->membercount = 0;
    
    	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++) {
    
    Matt O'Gorman's avatar
    Matt O'Gorman committed
    		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))
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			break;
    
    	if (curint) {
    		AST_LIST_UNLOCK(&interfaces);
    		return 0;
    	}
    
    	ast_debug(1, "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);
    
    static int interface_exists_global(const char *interface)
    
    	struct call_queue *q;
    
    	struct member *mem, tmpmem;
    
    	ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while ((q = ao2_iterator_next(&queue_iter))) {
    
    		if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) {
    
    			ao2_ref(mem, -1);
    
    static int remove_from_interfaces(const char *interface)
    
    	struct member_interface *curint;
    
    
    	AST_LIST_LOCK(&interfaces);
    	AST_LIST_TRAVERSE_SAFE_BEGIN(&interfaces, curint, list) {
    
    		if (!strcasecmp(curint->interface, interface)) {
    			if (!interface_exists_global(interface)) {
    
    				ast_debug(1, "Removing %s from the list of interfaces that make up all of our queue members.\n", interface);
    
    				AST_LIST_REMOVE_CURRENT(&interfaces, list);
    
    		}
    	}
    	AST_LIST_TRAVERSE_SAFE_END;
    	AST_LIST_UNLOCK(&interfaces);
    
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	return 0;
    
    	struct member_interface *curint;
    
    	while ((curint = AST_LIST_REMOVE_HEAD(&interfaces, list)))
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \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, "setqueuevar")) {
    		q->setqueuevar = ast_true(val);
    	} else if (!strcasecmp(param, "setqueueentryvar")) {
    		q->setqueueentryvar = ast_true(val);
    
    	} else if (!strcasecmp(param, "monitor-format")) {
    		ast_copy_string(q->monfmt, val, sizeof(q->monfmt));
    
    	} else if (!strcasecmp(param, "membermacro")) {
    		ast_copy_string(q->membermacro, val, sizeof(q->membermacro));
    
    	} else if (!strcasecmp(param, "membergosub")) {
    		ast_copy_string(q->membergosub, val, sizeof(q->membergosub));
    
    	} 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")) {