Skip to content
Snippets Groups Projects
app_queue.c 87.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
     * Asterisk -- A telephony toolkit for Linux.
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * True call queues with optional send URL on answer
    
    Mark Spencer's avatar
    Mark Spencer committed
     * 
    
     * Copyright (C) 1999-2004, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Mark Spencer <markster@digium.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * 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 'peristent_members=<1|0>' KVP under the
     *             '[general]' group in queues.conf. The default is on.
     * 
    
     * 2004-06-04: Priorities in queues added by inAccess Networks (work funded by Hellas On Line (HOL) www.hol.gr).
     *
    
     * 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>
     *
    
    Mark Spencer's avatar
    Mark Spencer committed
     * This program is free software, distributed under the terms of
     * the GNU General Public License
     */
    
    #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/module.h>
    #include <asterisk/translate.h>
    #include <asterisk/say.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/musiconhold.h>
    #include <asterisk/cli.h>
    
    #include <asterisk/manager.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/config.h>
    
    #include <asterisk/monitor.h>
    
    #include <asterisk/utils.h>
    
    #include <asterisk/astdb.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #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 "../astconf.h"
    
    
    #define QUEUE_STRATEGY_RINGALL		0
    #define QUEUE_STRATEGY_ROUNDROBIN	1
    #define QUEUE_STRATEGY_LEASTRECENT	2
    #define QUEUE_STRATEGY_FEWESTCALLS	3
    #define QUEUE_STRATEGY_RANDOM		4
    
    #define QUEUE_STRATEGY_RRMEMORY		5
    
    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" },
    
    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	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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *tdesc = "True Call Queueing";
    
    static char *app = "Queue";
    
    static char *synopsis = "Queue a call for a call queue";
    
    static char *descrip =
    
    "  Queue(queuename[|options[|URL][|announceoverride][|timeout]]):\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "Queues an incoming call in a particular call queue as defined in queues.conf.\n"
    "  This application returns -1 if the originating channel hangs up, or if the\n"
    "call is bridged and  either of the parties in the bridge terminate the call.\n"
    "Returns 0 if the queue is full, nonexistant, or has no members.\n"
    "The option string may contain zero or more of the following characters:\n"
    "      't' -- allow the called user transfer the calling user\n"
    "      'T' -- to allow the calling user to transfer the call.\n"
    "      'd' -- data-quality (modem) call (minimum delay).\n"
    
    "      'h' -- allow callee to hang up by hitting *.\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    "      'H' -- allow caller to hang up by hitting *.\n"
    
    "      'n' -- no retries on the timeout; will exit this application and \n"
    "	      go to the next step.\n"
    
    "      'r' -- ring instead of playing MOH\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"
    
    "it.\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";
    
    /* PHM 06/26/03 */
    
    static char *app_aqm = "AddQueueMember" ;
    static char *app_aqm_synopsis = "Dynamically adds queue members" ;
    static char *app_aqm_descrip =
    
    "   AddQueueMember(queuename[|interface[|penalty]]):\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"
    
    "Returns -1 if there is an error.\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]):\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"
    
    "Returns -1 if there is an error.\n"
    "Example: RemoveQueueMember(techsupport|SIP/3000)\n"
    "";
    
    
    /* Persistent Members astdb family */
    static const char *pm_family = "/Queue/PersistentMembers";
    /* The maximum lengh of each persistent member queue database entry */
    #define PM_MAX_LEN 2048
    /* queues.conf [general] option */
    static int queue_persistent_members = 0;
    
    /* queues.conf per-queue weight option */
    static int use_weight = 0;
    
    
    #define QUEUE_FLAG_RINGBACKONLY		(1 << 0)
    #define QUEUE_FLAG_MUSICONHOLD		(1 << 1)
    #define QUEUE_FLAG_DATAQUALITY		(1 << 2)
    #define QUEUE_FLAG_REDIR_IN		(1 << 3)
    #define QUEUE_FLAG_REDIR_OUT		(1 << 4)
    #define QUEUE_FLAG_DISCON_IN		(1 << 5)
    #define QUEUE_FLAG_DISCON_OUT		(1 << 6)
    #define QUEUE_FLAG_MONJOIN		(1 << 7)	/* Should we join the two files when we are done with the call */
    #define QUEUE_FLAG_DEAD			(1 << 8)	/* Whether the queue is dead or not */
    #define QUEUE_FLAG_JOINEMPTY		(1 << 9)	/* Do we care if the queue has no members? */
    #define QUEUE_FLAG_EVENTWHENCALLED	(1 << 10)	/* Generate an event when the agent is called (before pickup) */
    #define QUEUE_FLAG_LEAVEWHENEMPTY	(1 << 11)	/* If all agents leave the queue, remove callers from the queue */
    #define QUEUE_FLAG_REPORTHOLDTIME	(1 << 12)	/* Should we report caller hold time to answering member? */
    #define QUEUE_FLAG_WRAPPED		(1 << 13)	/* Round Robin - wrapped around? */
    
    
    /* 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 localuser {
    	struct ast_channel *chan;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int stillgoing;
    
    	int metric;
    
    	unsigned int flags;		/* flag bits */
    
    	time_t lastcall;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct localuser *next;
    };
    
    LOCAL_USER_DECL;
    
    struct queue_ent {
    	struct ast_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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char context[80];		/* Context when user exits queue */
    
    	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_pos;                /* Last time we told the user their position */
    
    	int opos;			/* Where we started in the queue */
    	int handled;			/* Whether our call was handled */
    	time_t start;			/* When we started holding */
    
    	time_t expire;			/* When this entry should expire (time out of queue) */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *chan;	/* Our channel */
    	struct queue_ent *next;		/* The next queue entry */
    };
    
    struct member {
    
    	char interface[80];		/* Technology/Location */
    	int penalty;			/* Are we a last resort? */
    	int calls;			/* Number of calls serviced by this member */
    	int dynamic;			/* Are we dynamically added? */
    	int status;			/* Status of queue member */
    	time_t lastcall;		/* When last successful call was hungup */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct member *next;		/* Next member */
    };
    
    struct ast_call_queue {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char name[80];			/* Name of the queue */
    	char moh[80];			/* Name of musiconhold to be used */
    
    	char announce[80];		/* Announcement to play when call is answered */
    	char context[80];		/* Context for this queue */
    
    	unsigned int flags;		/* flag bits */
    
    	int strategy;			/* Queueing strategy */
    
    	int announcefrequency;          /* How often to announce their position */
    
    	int roundingseconds;            /* How many seconds do we round to? */
    
    	int announceholdtime;           /* When to announce holdtime: 0 = never, -1 = every announcement, 1 = only once */
    	int holdtime;                   /* Current avg holdtime for this queue, 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 queue calls answered with servicelevel*/
    
    	char monfmt[8];                 /* Format to use when recording calls */
    	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) */
    
    	int count;			/* How many entries are in the queue */
    	int maxlen;			/* Max number of entries in queue */
    
    	int wrapuptime;			/* Wrapup Time */
    
    	int retry;			/* Retry calling everyone after this amount of time */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int timeout;			/* How long to wait for an answer */
    
    	int weight;                     /* This queue's respective weight */
    
    	int rrpos;			/* Round Robin - position */
    
    	int memberdelay;		/* Seconds to delay connecting member to caller */
    
    	struct member *members;		/* Member channels to be tried */
    	struct queue_ent *head;		/* Start of the actual queue */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_call_queue *next;	/* Next call queue */
    };
    
    static struct ast_call_queue *queues = NULL;
    
    AST_MUTEX_DEFINE_STATIC(qlock);
    
    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(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;
    }
    
    /* Insert the 'new' entry after the 'prev' entry of queue 'q' */
    static inline void insert_entry(struct ast_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;
    }
    
    
    static int has_no_members(struct ast_call_queue *q)
    {
    	struct member *member;
    	int empty = 1;
    	member = q->members;
    	while(empty && member) {
    		switch(member->status) {
    
    		case AST_DEVICE_UNAVAILABLE:
    		case AST_DEVICE_INVALID:
    
    			/* Not logged on, etc */
    			break;
    		default:
    			/* Not empty */
    			empty = 0;
    		}
    		member = member->next;
    	}
    	return empty;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    struct statechange {
    	int state;
    	char dev[0];
    };
    
    static void *changethread(void *data)
    {
    	struct ast_call_queue *q;
    	struct statechange *sc = data;
    	struct member *cur;
    	char *loc;
    
    	char *technology;
    
    	technology = ast_strdupa(sc->dev);
    	loc = strchr(technology, '/');
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (loc) {
    		*loc = '\0';
    		loc++;
    	} else {
    
    		ast_log(LOG_WARNING, "Can't change device '%s' with no technology!\n", sc->dev);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		free(sc);
    		return NULL;
    	}
    	if (option_debug)
    
    		ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d'\n", technology, loc, sc->state);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	ast_mutex_lock(&qlock);
    	for (q = queues; q; q = q->next) {
    		ast_mutex_lock(&q->lock);
    		cur = q->members;
    		while(cur) {
    
    			if (!strcasecmp(sc->dev, cur->interface)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (cur->status != sc->state) {
    					cur->status = sc->state;
    					manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
    						"Queue: %s\r\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    						"Membership: %s\r\n"
    						"Penalty: %d\r\n"
    						"CallsTaken: %d\r\n"
    						"LastCall: %ld\r\n"
    						"Status: %d\r\n",
    
    					q->name, cur->interface, cur->dynamic ? "dynamic" : "static",
    
    Mark Spencer's avatar
    Mark Spencer committed
    					cur->penalty, cur->calls, cur->lastcall, cur->status);
    				}
    			}
    			cur = cur->next;
    		}
    		ast_mutex_unlock(&q->lock);
    	}
    	ast_mutex_unlock(&qlock);
    
    	if (option_debug)
    
    		ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d'\n", technology, loc, sc->state);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	free(sc);
    	return NULL;
    }
    
    static int statechange_queue(const char *dev, int state, void *ign)
    {
    	/* Avoid potential for deadlocks by spawning a new thread to handle
    	   the event */
    	struct statechange *sc;
    	pthread_t t;
    	pthread_attr_t attr;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	sc = malloc(sizeof(struct statechange) + strlen(dev) + 1);
    	if (sc) {
    		sc->state = state;
    		strcpy(sc->dev, dev);
    		pthread_attr_init(&attr);
    		pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    		if (ast_pthread_create(&t, &attr, changethread, sc)) {
    			ast_log(LOG_WARNING, "Failed to create update thread!\n");
    			free(sc);
    		}
    	}
    	return 0;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int join_queue(char *queuename, struct queue_ent *qe)
    {
    	struct ast_call_queue *q;
    	struct queue_ent *cur, *prev = NULL;
    	int res = -1;
    	int pos = 0;
    
    	for (q = queues; q; q = q->next) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (!strcasecmp(q->name, queuename)) {
    			/* This is our one */
    
    			if ((!has_no_members(q) || ast_test_flag(q, QUEUE_FLAG_JOINEMPTY)) && (!q->maxlen || (q->count < q->maxlen))) {
    
    				/* 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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    				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;
    					}
    
    Mark Spencer's avatar
    Mark Spencer committed
    					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);
    
    				strncpy(qe->moh, q->moh, sizeof(qe->moh) - 1);
    				strncpy(qe->announce, q->announce, sizeof(qe->announce) - 1);
    				strncpy(qe->context, q->context, sizeof(qe->context) - 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				q->count++;
    				res = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    				manager_event(EVENT_FLAG_CALL, "Join", 
    
    					"Channel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\n",
    					qe->chan->name, 
    					qe->chan->cid.cid_num ? qe->chan->cid.cid_num : "unknown",
    					qe->chan->cid.cid_name ? qe->chan->cid.cid_name : "unknown",
    					q->name, qe->pos, q->count );
    
    #if 0
    ast_log(LOG_NOTICE, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
    #endif
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    		}
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static void free_members(struct ast_call_queue *q, int all)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Free non-dynamic members */
    	struct member *curm, *next, *prev;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	curm = q->members;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	prev = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	while(curm) {
    		next = curm->next;
    
    		if (all || !curm->dynamic) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (prev)
    				prev->next = next;
    			else
    				q->members = next;
    			free(curm);
    		} else 
    			prev = curm;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		curm = next;
    	}
    }
    
    static void destroy_queue(struct ast_call_queue *q)
    {
    	struct ast_call_queue *cur, *prev = NULL;
    
    	for (cur = queues; cur; cur = cur->next) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (cur == q) {
    			if (prev)
    				prev->next = cur->next;
    			else
    				queues = cur->next;
    		} else {
    			prev = cur;
    		}
    	}
    
    	free_members(q, 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	free(q);
    }
    
    
    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, "");
    	else
    		res = 0;
    
    	if (res) {
    		ast_log(LOG_WARNING, "ast_streamfile failed on %s \n", chan->name);
    		res = 0;
    	}
    	ast_stopstream(chan);
    
    	return res;
    }
    
    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 -1;
    
    	/* 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 -1;
    
    	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);
    		goto posout;
    	} else {
    		res += play_file(qe->chan, qe->parent->sound_thereare);
    
    		res += ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */
    
    		res += play_file(qe->chan, qe->parent->sound_calls);
    	}
    	/* 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;
    	}
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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==1 && qe->last_pos)) ) {
    
    		res += play_file(qe->chan, qe->parent->sound_holdtime);
    
    			if (avgholdmins < 2) {
    				res += play_file(qe->chan, qe->parent->sound_lessthan);
    				res += ast_say_number(qe->chan, 2, AST_DIGIT_ANY, qe->chan->language, (char *)NULL);
    			} else 
    				res += ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, (char*) NULL);
    
    			res += play_file(qe->chan, qe->parent->sound_minutes);
    		}
    		if(avgholdsecs>0) {
    			res += ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, (char*) NULL);
    			res += play_file(qe->chan, qe->parent->sound_seconds);
    		}
    
    
    	}
    
    	posout:
    	/* Set our last_pos indicators */
     	qe->last_pos = now;
    	qe->last_pos_said = qe->pos;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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);
    	ast_moh_start(qe->chan, qe->moh);
    
    	return (res>0);
    }
    
    static void record_abandoned(struct queue_ent *qe)
    {
    	ast_mutex_lock(&qe->parent->lock);
    	qe->parent->callsabandoned++;
    	ast_mutex_unlock(&qe->parent->lock);
    }
    
    static void recalc_holdtime(struct queue_ent *qe)
    {
    	int oldvalue, newvalue;
    
    	/* Calculate holdtime using a recursive boxcar filter */
    	/* Thanks to SRT for this contribution */
    	/* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
    
    	newvalue = time(NULL) - qe->start;
    
    	ast_mutex_lock(&qe->parent->lock);
    	if (newvalue <= qe->parent->servicelevel)
           		qe->parent->callscompletedinsl++;
    	oldvalue = qe->parent->holdtime;
    	qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newvalue) >> 2;
    	ast_mutex_unlock(&qe->parent->lock);
    }
    
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void leave_queue(struct queue_ent *qe)
    {
    	struct ast_call_queue *q;
    	struct queue_ent *cur, *prev = NULL;
    	int pos = 0;
    	q = qe->parent;
    	if (!q)
    		return;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	prev = NULL;
    	cur = q->head;
    	while(cur) {
    		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\n",
    				qe->chan->name, q->name,  q->count);
    
    #if 0
    ast_log(LOG_NOTICE, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
    #endif
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* 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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			cur->pos = ++pos;
    			prev = cur;
    		}
    		cur = cur->next;
    	}
    
    	if (ast_test_flag(q, QUEUE_FLAG_DEAD) && !q->count) {	
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* It's dead and nobody is in it, so kill it */
    		destroy_queue(q);
    	}
    }
    
    static void hanguptree(struct localuser *outgoing, struct ast_channel *exception)
    {
    	/* Hang up a tree of stuff */
    	struct localuser *oo;
    	while(outgoing) {
    		/* Hangup any existing lines we have open */
    
    		if (outgoing->chan && (outgoing->chan != exception))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_hangup(outgoing->chan);
    		oo = outgoing;
    		outgoing=outgoing->next;
    		free(oo);
    	}
    }
    
    
    static int update_status(struct ast_call_queue *q, struct member *member, int status)
    {
    	struct member *cur;
    	/* Since a reload could have taken place, we have to traverse the list to
    		be sure it's still valid */
    	ast_mutex_lock(&q->lock);
    	cur = q->members;
    	while(cur) {
    		if (member == cur) {
    			cur->status = status;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
    				"Queue: %s\r\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    				"Membership: %s\r\n"
    				"Penalty: %d\r\n"
    				"CallsTaken: %d\r\n"
    				"LastCall: %ld\r\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    				"Status: %d\r\n",
    
    					q->name, cur->interface, cur->dynamic ? "dynamic" : "static",
    
    Mark Spencer's avatar
    Mark Spencer committed
    					cur->penalty, cur->calls, cur->lastcall, cur->status);
    
    			break;
    		}
    		cur = cur->next;
    	}
    	q->callscompleted++;
    	ast_mutex_unlock(&q->lock);
    	return 0;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int update_dial_status(struct ast_call_queue *q, struct member *member, int status)
    {
    	if (status == AST_CAUSE_BUSY)
    		status = AST_DEVICE_BUSY;
    	else if (status == AST_CAUSE_UNREGISTERED)
    		status = AST_DEVICE_UNAVAILABLE;
    	else if (status == AST_CAUSE_NOSUCHDRIVER)
    		status = AST_DEVICE_INVALID;
    	else
    		status = AST_DEVICE_UNKNOWN;
    	return update_status(q, member, status);
    }
    
    
    static int compare_weight(struct ast_call_queue *req_q, struct localuser *req_user, char *qname) 
    {
    /* 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  */
    	struct ast_call_queue *q;
    	struct member *mem;
    	int found = 0, weight = 0, calls = 0;
    	char name[80] = "";
    	
    	strncpy(name, req_q->name, sizeof(name) - 1);
    	weight = req_q->weight;
    	calls = req_q->count;
    	
    	ast_mutex_lock(&qlock);
    	for (q = queues; q; q = q->next) { /* spin queues */
    		ast_mutex_lock(&q->lock);
    		if (!strcasecmp(q->name, name)) { /* don't check myself */
    			ast_mutex_unlock(&q->lock);
    			continue; 
    		}
    		if (q->count && q->members) { /* check only if calls waiting and has members */
    			for (mem = q->members; mem; mem = mem->next) {  /* spin members */
    				if (!strcasecmp(mem->interface, req_user->interface)) {
    					ast_log(LOG_DEBUG, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
    					if (q->weight > 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, name, weight, calls);
    						found = 1;
    						strncpy(qname, q->name, sizeof(qname) - 1);
    						break;  /* stop looking for more members */
    					}
    				}
    			}
    		}
    		ast_mutex_unlock(&q->lock);
    		if (found) 
    			break;
    	}
    	ast_mutex_unlock(&qlock);
    	return found;
    }
    
    
    
    
    static int ring_entry(struct queue_ent *qe, struct localuser *tmp, int *busies)
    
    	char tech[256];
    	char *location;
    
    	char qname[80] = "";
    
    	if (use_weight) { /* fast path */
    		if (compare_weight(qe->parent,tmp,qname)) {
    			ast_verbose(VERBOSE_PREFIX_3 "Attempt (%s: %s) delayed by higher priority queue (%s).\n", qe->parent->name, tmp->interface, qname);
    			if (qe->chan->cdr)
    				ast_cdr_busy(qe->chan->cdr);
    			tmp->stillgoing = 0;
    
    	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;
    
    	
    	strncpy(tech, tmp->interface, sizeof(tech) - 1);
    	if ((location = strchr(tech, '/')))
    		*location++ = '\0';
    	else
    		location = "";
    
    
    	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 0
    
    		ast_log(LOG_NOTICE, "Unable to create channel of type '%s' for Queue\n", cur->tech);
    
    #endif			
    		if (qe->chan->cdr)
    			ast_cdr_busy(qe->chan->cdr);
    		tmp->stillgoing = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		update_dial_status(qe->parent, tmp->member, status);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		update_dial_status(qe->parent, tmp->member, status);
    
    	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 = NULL;
    	if (tmp->chan->cid.cid_name)
    		free(tmp->chan->cid.cid_name);
    	tmp->chan->cid.cid_name = NULL;
    	if (tmp->chan->cid.cid_ani)
    		free(tmp->chan->cid.cid_ani);
    	tmp->chan->cid.cid_ani = NULL;
    	if (qe->chan->cid.cid_num)
    		tmp->chan->cid.cid_num = strdup(qe->chan->cid.cid_num);
    	if (qe->chan->cid.cid_name)
    		tmp->chan->cid.cid_name = strdup(qe->chan->cid.cid_name);
    	if (qe->chan->cid.cid_ani)
    		tmp->chan->cid.cid_ani = 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;
    
    	/* Place the call, but don't wait on the answer */
    
    	res = ast_call(tmp->chan, location, 0);
    
    	if (res) {
    		/* Again, keep going even if there's an error */
    		if (option_debug)
    			ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res);
    		else if (option_verbose > 2)
    
    			ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->interface);
    
    		ast_hangup(tmp->chan);
    		tmp->chan = NULL;
    		tmp->stillgoing = 0;
    
    		if (ast_test_flag(qe->parent, QUEUE_FLAG_EVENTWHENCALLED)) {
    
    			manager_event(EVENT_FLAG_AGENT, "AgentCalled",
    
    						"AgentCalled: %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",
    
    						tmp->interface, 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);
    		}
    
    			ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->interface);
    
    static int ring_one(struct queue_ent *qe, struct localuser *outgoing, int *busies)
    
    {
    	struct localuser *cur;
    	struct localuser *best;
    	int bestmetric=0;
    	do {
    		best = NULL;
    		cur = outgoing;
    		while(cur) {
    
    			if (cur->stillgoing &&					/* Not already done */
    				!cur->chan &&					/* Isn't already going */
    
    				(!best || (cur->metric < bestmetric))) {	/* We haven't found one yet, or it's better */
    					bestmetric = cur->metric;
    					best = cur;
    			}
    			cur = cur->next;
    		}
    		if (best) {
    
    			if (!qe->parent->strategy) {
    				/* Ring everyone who shares this best metric (for ringall) */
    				cur = outgoing;
    				while(cur) {
    
    					if (cur->stillgoing && !cur->chan && (cur->metric <= bestmetric)) {
    
    						if (option_debug)
    							ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric);
    
    					}
    					cur = cur->next;
    				}
    			} else {
    				/* Ring just the best channel */
    
    					ast_log(LOG_DEBUG, "Trying '%s' with metric %d\n", best->interface, best->metric);
    
    		}
    	} while (best && !best->chan);
    	if (!best) {
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n");
    
    static int store_next(struct queue_ent *qe, struct localuser *outgoing)
    {
    	struct localuser *cur;
    	struct localuser *best;
    	int bestmetric=0;
    	best = NULL;
    	cur = outgoing;
    	while(cur) {
    
    		if (cur->stillgoing &&					/* Not already done */
    			!cur->chan &&					/* Isn't already going */
    
    			(!best || (cur->metric < bestmetric))) {	/* We haven't found one yet, or it's better */
    				bestmetric = cur->metric;
    				best = cur;
    		}
    		cur = cur->next;
    	}
    	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 (!ast_test_flag(qe->parent, QUEUE_FLAG_WRAPPED)) {
    
    			/* No more channels, start over */
    			qe->parent->rrpos = 0;
    		} else {
    			/* Prioritize next entry */
    			qe->parent->rrpos++;
    		}
    
    	ast_clear_flag(qe->parent, QUEUE_FLAG_WRAPPED);
    
    static int valid_exit(struct queue_ent *qe, char digit)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	if (ast_strlen_zero(qe->context))
    
    	if (ast_exists_extension(qe->chan, qe->context, tmp, 1, qe->chan->cid.cid_num)) {
    
    		strncpy(qe->chan->context, qe->context, sizeof(qe->chan->context) - 1);
    		strncpy(qe->chan->exten, tmp, sizeof(qe->chan->exten) - 1);
    		qe->chan->priority = 0;
    		return 1;
    	}
    	return 0;
    }
    
    
    #define BUILD_STATS do { \
    		o = outgoing; \
    		found = -1; \
    		pos = 1; \
    		numlines = 0; \
    		watchers[0] = in; \
    		while(o) { \
    			/* Keep track of important channels */ \
    			if (o->stillgoing) { \
    				stillgoing = 1; \
    				if (o->chan) { \
    					watchers[pos++] = o->chan; \
    					found = 1; \
    				} \
    			} \
    			o = o->next; \
    			numlines++; \
    		} \
    	} while(0)
    	
    static struct localuser *wait_for_answer(struct queue_ent *qe, struct localuser *outgoing, int *to, struct ast_flags *flags, char *digit, int prebusies)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct localuser *o;
    	int found;
    	int numlines;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int sentringing = 0;