Skip to content
Snippets Groups Projects
app_queue.c 83.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 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;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    /* We define a customer "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. */
    
    struct localuser {
    	struct ast_channel *chan;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int stillgoing;
    
    	int metric;
    
    	int allowredirect_in;
    	int allowredirect_out;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int ringbackonly;
    	int musiconhold;
    	int dataquality;
    
    	int allowdisconnect_in;
    	int allowdisconnect_out;
    
    	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 */
    
    	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 answererd with servicelevel*/
    	char monfmt[8];                 /* Format to use when recording calls */
    
    	int monjoin;                    /* Should we join the two files when we are done with the call */
    
    	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 dead;			/* Whether this queue is dead or not */
    	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 rrpos;			/* Round Robin - position */
    
    	int wrapped;			/* Round Robin - wrapped around? */
    
    	int joinempty;			/* Do we care if the queue has no members? */
    
    	int eventwhencalled;			/* Generate an event when the agent is called (before pickup) */
    
    	int leavewhenempty;		/* If all agents leave the queue, remove callers from the queue */
    
    	int reportholdtime;		/* Should we report caller hold time to member? */
    
    	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;
    	loc = strchr(sc->dev, '/');
    	if (loc) {
    		*loc = '\0';
    		loc++;
    	} else {
    		ast_log(LOG_WARNING, "Can't change device with no technology!\n");
    		free(sc);
    		return NULL;
    	}
    	if (option_debug)
    		ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d'\n", sc->dev, loc, sc->state);
    	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);
    	ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d'\n", sc->dev, loc, sc->state);
    	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;
    	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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if ((!has_no_members(q) || q->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;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (q->dead && !q->count) {	
    		/* 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 ring_entry(struct queue_ent *qe, struct localuser *tmp)
    {
    	int res;
    
    	char tech[256];
    	char *location;
    
    
    	if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) {
    
    		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;
    		return 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'\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);
    
    	/* 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;
    		return 0;
    
    	} else {
    		if (qe->parent->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);
    
    	return 0;
    }
    
    static int ring_one(struct queue_ent *qe, struct localuser *outgoing)
    {
    	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)) {
    
    						ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric);
    
    						ring_entry(qe, cur);
    					}
    					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 */
    
    		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;
    
    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;
    }
    
    
    static struct localuser *wait_for_answer(struct queue_ent *qe, struct localuser *outgoing, int *to, int *allowredir_in, int *allowredir_out, int *allowdisconnect_in, int *allowdisconnect_out, char *digit)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct localuser *o;
    	int found;
    	int numlines;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int sentringing = 0;
    	int numbusies = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int orig = *to;
    	struct ast_frame *f;
    
    	struct ast_channel *watchers[AST_MAX_WATCHERS];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int pos;
    	struct ast_channel *winner;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	while(*to && !peer) {
    		o = outgoing;
    		found = -1;
    		pos = 1;
    		numlines = 0;
    		watchers[0] = in;
    		while(o) {
    			/* Keep track of important channels */
    
    			if (o->stillgoing && o->chan) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				watchers[pos++] = o->chan;
    				found = 1;
    			}
    			o = o->next;
    			numlines++;
    		}
    		if (found < 0) {
    
    			if (numlines == (numbusies + numnochan)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				ast_log(LOG_DEBUG, "Everyone is busy at this time\n");
    			} else {
    
    				ast_log(LOG_NOTICE, "No one is answering queue '%s'\n", queue);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    			*to = 0;
    			return NULL;
    		}
    		winner = ast_waitfor_n(watchers, pos, to);
    		o = outgoing;
    		while(o) {
    
    			if (o->stillgoing && (o->chan) &&  (o->chan->_state == AST_STATE_UP)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (!peer) {
    					if (option_verbose > 2)
    						ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
    
    					*allowredir_in = o->allowredirect_in;
    					*allowredir_out = o->allowredirect_out;
    
    					*allowdisconnect_in = o->allowdisconnect_in;
    					*allowdisconnect_out = o->allowdisconnect_out;
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			} else if (o->chan && (o->chan == winner)) {
    
    				if (!ast_strlen_zero(o->chan->call_forward)) {
    					char tmpchan[256]="";
    					char *stuff;
    					char *tech;
    					strncpy(tmpchan, o->chan->call_forward, sizeof(tmpchan) - 1);
    					if ((stuff = strchr(tmpchan, '/'))) {
    						*stuff = '\0';
    						stuff++;
    						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 (status != o->oldstatus) 
    
    Mark Spencer's avatar
    Mark Spencer committed
    						update_dial_status(qe->parent, o->member, 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 {
    						if (o->chan->cid.cid_num)
    							free(o->chan->cid.cid_num);
    						o->chan->cid.cid_num = NULL;
    						if (o->chan->cid.cid_name)
    							free(o->chan->cid.cid_name);
    						o->chan->cid.cid_name = NULL;
    
    						if (in->cid.cid_num) {
    							o->chan->cid.cid_num = strdup(in->cid.cid_num);
    							if (!o->chan->cid.cid_num)
    								ast_log(LOG_WARNING, "Out of memory\n");	
    						}
    						if (in->cid.cid_name) {
    							o->chan->cid.cid_name = strdup(in->cid.cid_name);
    							if (!o->chan->cid.cid_name)
    								ast_log(LOG_WARNING, "Out of memory\n");	
    						}
    						strncpy(o->chan->accountcode, in->accountcode, sizeof(o->chan->accountcode) - 1);
    						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 = malloc(strlen(in->cid.cid_ani) + 1);
    							if (o->chan->cid.cid_ani)
    								strncpy(o->chan->cid.cid_ani, in->cid.cid_ani, strlen(in->cid.cid_ani) + 1);
    							else