mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 10:47:18 +00:00 
			
		
		
		
	git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@1708 65c4cc65-6c06-0410-ace0-fbb531ad65f3
		
			
				
	
	
		
			1510 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1510 lines
		
	
	
		
			39 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|  * Asterisk -- A telephony toolkit for Linux.
 | |
|  *
 | |
|  * True call queues with optional send URL on answer
 | |
|  * 
 | |
|  * Copyright (C) 1999, Mark Spencer
 | |
|  *
 | |
|  * Mark Spencer <markster@linux-support.net>
 | |
|  *
 | |
|  * 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>
 | |
| #include <asterisk/parking.h>
 | |
| #include <asterisk/musiconhold.h>
 | |
| #include <asterisk/cli.h>
 | |
| #include <asterisk/manager.h> /* JDG */
 | |
| #include <asterisk/config.h>
 | |
| #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 <pthread.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
 | |
| 
 | |
| 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" },
 | |
| };
 | |
| 
 | |
| #define DEFAULT_RETRY		5
 | |
| #define DEFAULT_TIMEOUT		15
 | |
| #define RECHECK				1		/* Recheck every second to see we we're at the top yet */
 | |
| 
 | |
| 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]]):\n"
 | |
| "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 caller to hang up by hitting *.\n"
 | |
| "  In addition to transferring the call, a call may be parked and then picked\n"
 | |
| "up by another user.\n"
 | |
| "  The optionnal URL will be sent to the called party if the channel supports\n"
 | |
| "it.\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]):\n"
 | |
| "Dynamically adds interface to an existing queue\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"
 | |
| "Returns -1 if there is an error.\n"
 | |
| "Example: RemoveQueueMember(techsupport|SIP/3000)\n"
 | |
| "";
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /* 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;
 | |
| 	char numsubst[256];
 | |
| 	char tech[40];
 | |
| 	int stillgoing;
 | |
| 	int metric;
 | |
| 	int allowredirect_in;
 | |
| 	int allowredirect_out;
 | |
| 	int ringbackonly;
 | |
| 	int musiconhold;
 | |
| 	int dataquality;
 | |
| 	int allowdisconnect;
 | |
| 	struct member *member;
 | |
| 	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 */
 | |
| 	char context[80];		/* Context when user exits queue */
 | |
| 	int pos;					/* Where we are in the queue */
 | |
| 	time_t start;				/* When we started holding */
 | |
| 	struct ast_channel *chan;	/* Our channel */
 | |
| 	struct queue_ent *next;		/* The next queue entry */
 | |
| };
 | |
| 
 | |
| struct member {
 | |
| 	char tech[80];				/* Technology */
 | |
| 	char loc[256];				/* Location */
 | |
| 	int penalty;				/* Are we a last resort? */
 | |
| 	int calls;
 | |
| 	int dynamic;				/* Are we dynamically added? */
 | |
| 	time_t lastcall;	/* When last successful call was hungup */
 | |
| 	struct member *next;		/* Next member */
 | |
| };
 | |
| 
 | |
| struct ast_call_queue {
 | |
| 	ast_mutex_t	lock;	
 | |
| 	char name[80];			/* Name of the queue */
 | |
| 	char moh[80];			/* Name of musiconhold to be used */
 | |
| 	char announce[80];		/* Announcement to play */
 | |
| 	char context[80];		/* Announcement to play */
 | |
| 	int strategy;			/* Queueing strategy */
 | |
| 	int announcetimeout;	/* How often to announce their position */
 | |
| 	int count;				/* How many entries are in the queue */
 | |
| 	int maxlen;				/* Max number of entries in queue */
 | |
| 
 | |
| 	int dead;				/* Whether this queue is dead or not */
 | |
| 	int retry;				/* Retry calling everyone after this amount of time */
 | |
| 	int timeout;			/* How long to wait for an answer */
 | |
| 	
 | |
| 	/* Queue strategy things */
 | |
| 	
 | |
| 	int rrpos;				/* Round Robin - position */
 | |
| 	int wrapped;			/* Round Robin - wrapped around? */
 | |
| 
 | |
| 	struct member *members;	/* Member channels to be tried */
 | |
| 	struct queue_ent *head;	/* Start of the actual queue */
 | |
| 	struct ast_call_queue *next;	/* Next call queue */
 | |
| };
 | |
| 
 | |
| static struct ast_call_queue *queues = NULL;
 | |
| static ast_mutex_t qlock = AST_MUTEX_INITIALIZER;
 | |
| 
 | |
| 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;
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 	ast_mutex_lock(&qlock);
 | |
| 	q = queues;
 | |
| 	while(q) {
 | |
| 		if (!strcasecmp(q->name, queuename)) {
 | |
| 			/* This is our one */
 | |
| 			ast_mutex_lock(&q->lock);
 | |
| 			if (q->members && (!q->maxlen || (q->count < q->maxlen))) {
 | |
| 				/* There's space for us, put us at the end */
 | |
| 				prev = NULL;
 | |
| 				cur = q->head;
 | |
| 				while(cur) {
 | |
| 					cur->pos = ++pos;
 | |
| 					prev = cur;
 | |
| 					cur = cur->next;
 | |
| 				}
 | |
| 				if (prev)
 | |
| 					prev->next = qe;
 | |
| 				else
 | |
| 					q->head = qe;
 | |
| 				/* Fix additional pointers and
 | |
| 				  information  */
 | |
| 				qe->next = NULL;
 | |
| 				qe->parent = q;
 | |
| 				qe->pos = ++pos;
 | |
| 				strncpy(qe->moh, q->moh, sizeof(qe->moh));
 | |
| 				strncpy(qe->announce, q->announce, sizeof(qe->announce));
 | |
| 				strncpy(qe->context, q->context, sizeof(qe->context));
 | |
| 				q->count++;
 | |
| 				res = 0;
 | |
| 				manager_event(EVENT_FLAG_CALL, "Join", 
 | |
|         	                                               	"Channel: %s\r\nCallerID: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\n",
 | |
| 	                                                       	qe->chan->name, (qe->chan->callerid ? qe->chan->callerid : "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
 | |
| 			}
 | |
| 			ast_mutex_unlock(&q->lock);
 | |
| 			break;
 | |
| 		}
 | |
| 		q = q->next;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&qlock);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static void free_members(struct ast_call_queue *q, int all)
 | |
| {
 | |
| 	/* Free non-dynamic members */
 | |
| 	struct member *curm, *next, *prev;
 | |
| 	curm = q->members;
 | |
| 	prev = NULL;
 | |
| 	while(curm) {
 | |
| 		next = curm->next;
 | |
| 		if (all || !curm->dynamic) {
 | |
| 			if (prev)
 | |
| 				prev->next = next;
 | |
| 			else
 | |
| 				q->members = next;
 | |
| 			free(curm);
 | |
| 		} else 
 | |
| 			prev = curm;
 | |
| 		curm = next;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void destroy_queue(struct ast_call_queue *q)
 | |
| {
 | |
| 	struct ast_call_queue *cur, *prev = NULL;
 | |
| 	ast_mutex_lock(&qlock);
 | |
| 	cur = queues;
 | |
| 	while(cur) {
 | |
| 		if (cur == q) {
 | |
| 			if (prev)
 | |
| 				prev->next = cur->next;
 | |
| 			else
 | |
| 				queues = cur->next;
 | |
| 		} else {
 | |
| 			prev = cur;
 | |
| 		}
 | |
| 		cur = cur->next;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&qlock);
 | |
| 	free_members(q, 1);
 | |
| 	free(q);
 | |
| }
 | |
| 
 | |
| 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;
 | |
| 	ast_mutex_lock(&q->lock);
 | |
| 
 | |
| 	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
 | |
| 			/* Take us out of the queue */
 | |
| 			if (prev)
 | |
| 				prev->next = cur->next;
 | |
| 			else
 | |
| 				q->head = cur->next;
 | |
| 		} else {
 | |
| 			cur->pos = ++pos;
 | |
| 			prev = cur;
 | |
| 		}
 | |
| 		cur = cur->next;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&q->lock);
 | |
| 	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))
 | |
| 			ast_hangup(outgoing->chan);
 | |
| 		oo = outgoing;
 | |
| 		outgoing=outgoing->next;
 | |
| 		free(oo);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static int ring_entry(struct queue_ent *qe, struct localuser *tmp)
 | |
| {
 | |
| 	int res;
 | |
| 	/* Request the peer */
 | |
| 	tmp->chan = ast_request(tmp->tech, qe->chan->nativeformats, tmp->numsubst);
 | |
| 	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;
 | |
| 		return 0;
 | |
| 	}
 | |
| 	tmp->chan->appl = "AppQueue";
 | |
| 	tmp->chan->data = "(Outgoing Line)";
 | |
| 	tmp->chan->whentohangup = 0;
 | |
| 	if (tmp->chan->callerid)
 | |
| 		free(tmp->chan->callerid);
 | |
| 	if (tmp->chan->ani)
 | |
| 		free(tmp->chan->ani);
 | |
| 	if (qe->chan->callerid)
 | |
| 		tmp->chan->callerid = strdup(qe->chan->callerid);
 | |
| 	else
 | |
| 		tmp->chan->callerid = NULL;
 | |
| 	if (qe->chan->ani)
 | |
| 		tmp->chan->ani = strdup(qe->chan->ani);
 | |
| 	else
 | |
| 		tmp->chan->ani = NULL;
 | |
| 	/* 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, tmp->numsubst, 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->numsubst);
 | |
| 		ast_hangup(tmp->chan);
 | |
| 		tmp->chan = NULL;
 | |
| 		tmp->stillgoing = 0;
 | |
| 		return 0;
 | |
| 	} else
 | |
| 		if (option_verbose > 2)
 | |
| 			ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->numsubst);
 | |
| 	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/%s' with metric %d\n", cur->tech, cur->numsubst, cur->metric);
 | |
| 						ring_entry(qe, cur);
 | |
| 					}
 | |
| 					cur = cur->next;
 | |
| 				}
 | |
| 			} else {
 | |
| 				/* Ring just the best channel */
 | |
| 				ast_log(LOG_DEBUG, "Trying '%s/%s' with metric %d\n", best->tech, best->numsubst, best->metric);
 | |
| 				ring_entry(qe, best);
 | |
| 			}
 | |
| 		}
 | |
| 	} while (best && !best->chan);
 | |
| 	if (!best) {
 | |
| 		ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n");
 | |
| 		return 0;
 | |
| 	}
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| static int valid_exit(struct queue_ent *qe, char digit)
 | |
| {
 | |
| 	char tmp[2];
 | |
| 	if (!strlen(qe->context))
 | |
| 		return 0;
 | |
| 	tmp[0] = digit;
 | |
| 	tmp[1] = '\0';
 | |
| 	if (ast_exists_extension(qe->chan, qe->context, tmp, 1, qe->chan->callerid)) {
 | |
| 		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 MAX 256
 | |
| 
 | |
| static struct localuser *wait_for_answer(struct queue_ent *qe, struct localuser *outgoing, int *to, int *allowredir_in, int *allowredir_out, int *allowdisconnect, char *digit)
 | |
| {
 | |
| 	char *queue = qe->parent->name;
 | |
| 	struct localuser *o;
 | |
| 	int found;
 | |
| 	int numlines;
 | |
| 	int sentringing = 0;
 | |
| 	int numbusies = 0;
 | |
| 	int orig = *to;
 | |
| 	struct ast_frame *f;
 | |
| 	struct localuser *peer = NULL;
 | |
| 	struct ast_channel *watchers[MAX];
 | |
| 	int pos;
 | |
| 	struct ast_channel *winner;
 | |
| 	struct ast_channel *in = qe->chan;
 | |
| 	
 | |
| 	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) {
 | |
| 				watchers[pos++] = o->chan;
 | |
| 				found = 1;
 | |
| 			}
 | |
| 			o = o->next;
 | |
| 			numlines++;
 | |
| 		}
 | |
| 		if (found < 0) {
 | |
| 			if (numlines == numbusies) {
 | |
| 				ast_log(LOG_DEBUG, "Everyone is busy at this time\n");
 | |
| 			} else {
 | |
| 				ast_log(LOG_NOTICE, "No one is answered queue %s\n", queue);
 | |
| 			}
 | |
| 			*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)) {
 | |
| 				if (!peer) {
 | |
| 					if (option_verbose > 2)
 | |
| 						ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
 | |
| 					peer = o;
 | |
| 					*allowredir_in = o->allowredirect_in;
 | |
| 					*allowredir_out = o->allowredirect_out;
 | |
| 					*allowdisconnect = o->allowdisconnect;
 | |
| 				}
 | |
| 			} else if (o->chan && (o->chan == winner)) {
 | |
| 				f = ast_read(winner);
 | |
| 				if (f) {
 | |
| 					if (f->frametype == AST_FRAME_CONTROL) {
 | |
| 						switch(f->subclass) {
 | |
| 					    case AST_CONTROL_ANSWER:
 | |
| 							/* This is our guy if someone answered. */
 | |
| 							if (!peer) {
 | |
| 								if (option_verbose > 2)
 | |
| 									ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
 | |
| 								peer = o;
 | |
| 								*allowredir_in = o->allowredirect_in;
 | |
| 								*allowredir_out = o->allowredirect_out;
 | |
| 								*allowdisconnect = o->allowdisconnect;
 | |
| 							}
 | |
| 							break;
 | |
| 						case AST_CONTROL_BUSY:
 | |
| 							if (option_verbose > 2)
 | |
| 								ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
 | |
| 							o->stillgoing = 0;
 | |
| 							if (in->cdr)
 | |
| 								ast_cdr_busy(in->cdr);
 | |
| 							ast_hangup(o->chan);
 | |
| 							o->chan = NULL;
 | |
| 							if (qe->parent->strategy)
 | |
| 								ring_one(qe, outgoing);
 | |
| 							numbusies++;
 | |
| 							break;
 | |
| 						case AST_CONTROL_CONGESTION:
 | |
| 							if (option_verbose > 2)
 | |
| 								ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
 | |
| 							o->stillgoing = 0;
 | |
| 							if (in->cdr)
 | |
| 								ast_cdr_busy(in->cdr);
 | |
| 							ast_hangup(o->chan);
 | |
| 							o->chan = NULL;
 | |
| 							if (qe->parent->strategy)
 | |
| 								ring_one(qe, outgoing);
 | |
| 							numbusies++;
 | |
| 							break;
 | |
| 						case AST_CONTROL_RINGING:
 | |
| 							if (option_verbose > 2)
 | |
| 								ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
 | |
| 							if (!sentringing) {
 | |
| #if 0
 | |
| 								ast_indicate(in, AST_CONTROL_RINGING);
 | |
| #endif								
 | |
| 								sentringing++;
 | |
| 							}
 | |
| 							break;
 | |
| 						case AST_CONTROL_OFFHOOK:
 | |
| 							/* Ignore going off hook */
 | |
| 							break;
 | |
| 						default:
 | |
| 							ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
 | |
| 						}
 | |
| 					}
 | |
| 					ast_frfree(f);
 | |
| 				} else {
 | |
| 					o->stillgoing = 0;
 | |
| 					ast_hangup(o->chan);
 | |
| 					o->chan = NULL;
 | |
| 					if (qe->parent->strategy)
 | |
| 						ring_one(qe, outgoing);
 | |
| 				}
 | |
| 			}
 | |
| 			o = o->next;
 | |
| 		}
 | |
| 		if (winner == in) {
 | |
| 			f = ast_read(in);
 | |
| #if 0
 | |
| 			if (f && (f->frametype != AST_FRAME_VOICE))
 | |
| 					printf("Frame type: %d, %d\n", f->frametype, f->subclass);
 | |
| 			else if (!f || (f->frametype != AST_FRAME_VOICE))
 | |
| 				printf("Hangup received on %s\n", in->name);
 | |
| #endif
 | |
| 			if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
 | |
| 				/* Got hung up */
 | |
| 				*to=-1;
 | |
| 				return NULL;
 | |
| 			}
 | |
| 			if (f && (f->frametype == AST_FRAME_DTMF) && allowdisconnect && (f->subclass == '*')) {
 | |
| 			    if (option_verbose > 3)
 | |
| 				ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
 | |
| 				*to=0;
 | |
| 				return NULL;
 | |
| 			}
 | |
| 			if (f && (f->frametype == AST_FRAME_DTMF) && (f->subclass != '*') && valid_exit(qe, f->subclass)) {
 | |
| 				if (option_verbose > 3)
 | |
| 					ast_verbose(VERBOSE_PREFIX_3 "User pressed digit: %c", f->subclass);
 | |
| 				*to=0;
 | |
| 				*digit=f->subclass;
 | |
| 				return NULL;
 | |
| 			}
 | |
| 		}
 | |
| 		if (!*to && (option_verbose > 2))
 | |
| 			ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig);
 | |
| 	}
 | |
| 
 | |
| 	return peer;
 | |
| 	
 | |
| }
 | |
| 
 | |
| static int wait_our_turn(struct queue_ent *qe)
 | |
| {
 | |
| 	struct queue_ent *ch;
 | |
| 	int res = 0;
 | |
| 	for (;;) {
 | |
| 		/* Atomically read the parent head -- does not need a lock */
 | |
| 		ch = qe->parent->head;
 | |
| 		/* If we are now at the top of the head, break out */
 | |
| 		if (qe->parent->head == qe)
 | |
| 			break;
 | |
| 		/* Wait a second before checking again */
 | |
| 		res = ast_waitfordigit(qe->chan, RECHECK * 1000);
 | |
| 		if (res)
 | |
| 			break;
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int update_queue(struct ast_call_queue *q, struct localuser *user)
 | |
| {
 | |
| 	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 (user->member == cur) {
 | |
| 			time(&cur->lastcall);
 | |
| 			cur->calls++;
 | |
| 			break;
 | |
| 		}
 | |
| 		cur = cur->next;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&q->lock);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int calc_metric(struct ast_call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct localuser *tmp)
 | |
| {
 | |
| 	switch (q->strategy) {
 | |
| 	case QUEUE_STRATEGY_RINGALL:
 | |
| 		/* Everyone equal, except for penalty */
 | |
| 		tmp->metric = mem->penalty * 1000000;
 | |
| 		break;
 | |
| 	case QUEUE_STRATEGY_ROUNDROBIN:
 | |
| 		if (!pos) {
 | |
| 			if (!q->wrapped) {
 | |
| 				/* No more channels, start over */
 | |
| 				q->rrpos = 0;
 | |
| 			} else {
 | |
| 				/* Prioritize next entry */
 | |
| 				q->rrpos++;
 | |
| 			}
 | |
| 			q->wrapped = 0;
 | |
| 		}
 | |
| 		if (pos < q->rrpos) {
 | |
| 			tmp->metric = 1000 + pos;
 | |
| 		} else {
 | |
| 			if (pos > q->rrpos) {
 | |
| 				/* Indicate there is another priority */
 | |
| 				q->wrapped = 1;
 | |
| 			}
 | |
| 			tmp->metric = pos;
 | |
| 		}
 | |
| 		tmp->metric += mem->penalty * 1000000;
 | |
| 		break;
 | |
| 	case QUEUE_STRATEGY_RANDOM:
 | |
| 		tmp->metric = rand() % 1000;
 | |
| 		tmp->metric += mem->penalty * 1000000;
 | |
| 		break;
 | |
| 	case QUEUE_STRATEGY_FEWESTCALLS:
 | |
| 		tmp->metric = mem->calls;
 | |
| 		tmp->metric += mem->penalty * 1000000;
 | |
| 		break;
 | |
| 	case QUEUE_STRATEGY_LEASTRECENT:
 | |
| 		if (!mem->lastcall)
 | |
| 			tmp->metric = 0;
 | |
| 		else
 | |
| 			tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
 | |
| 		tmp->metric += mem->penalty * 1000000;
 | |
| 		break;
 | |
| 	default:
 | |
| 		ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
 | |
| 		break;
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int try_calling(struct queue_ent *qe, char *options, char *announceoverride, char *url)
 | |
| {
 | |
| 	struct member *cur;
 | |
| 	struct localuser *outgoing=NULL, *tmp = NULL;
 | |
| 	int to;
 | |
| 	int allowredir_in=0;
 | |
| 	int allowredir_out=0;
 | |
| 	int allowdisconnect=0;
 | |
| 	char restofit[AST_MAX_EXTENSION];
 | |
| 	char *newnum;
 | |
| 	struct ast_channel *peer;
 | |
| 	struct localuser *lpeer;
 | |
| 	int res = 0, bridge = 0;
 | |
| 	int zapx = 2;
 | |
| 	int x=0;
 | |
| 	char *announce = NULL;
 | |
| 	char digit = 0;
 | |
| 	/* Hold the lock while we setup the outgoing calls */
 | |
| 	ast_mutex_lock(&qe->parent->lock);
 | |
| 	cur = qe->parent->members;
 | |
| 	if (strlen(qe->announce))
 | |
| 		announce = qe->announce;
 | |
| 	if (announceoverride && strlen(announceoverride))
 | |
| 		announce = announceoverride;
 | |
| 	while(cur) {
 | |
| 		/* Get a technology/[device:]number pair */
 | |
| 		tmp = malloc(sizeof(struct localuser));
 | |
| 		if (!tmp) {
 | |
| 			ast_mutex_unlock(&qe->parent->lock);
 | |
| 			ast_log(LOG_WARNING, "Out of memory\n");
 | |
| 			goto out;
 | |
| 		}
 | |
| 		memset(tmp, 0, sizeof(struct localuser));
 | |
| 		tmp->stillgoing = -1;
 | |
| 		if (options) {
 | |
| 			if (strchr(options, 't'))
 | |
| 				tmp->allowredirect_in = 1;
 | |
| 			if (strchr(options, 'T'))
 | |
| 				tmp->allowredirect_out = 1;
 | |
| 			if (strchr(options, 'r'))
 | |
| 				tmp->ringbackonly = 1;
 | |
| 			if (strchr(options, 'm'))
 | |
| 				tmp->musiconhold = 1;
 | |
| 			if (strchr(options, 'd'))
 | |
| 				tmp->dataquality = 1;
 | |
| 			if (strchr(options, 'H'))
 | |
| 				tmp->allowdisconnect = 1;
 | |
| 		}
 | |
| 		if (url) {
 | |
| 			ast_log(LOG_DEBUG, "Queue with URL=%s_\n", url);
 | |
| 		} else 
 | |
| 			ast_log(LOG_DEBUG, "Simple queue (no URL)\n");
 | |
| 
 | |
| 		tmp->member = cur;		/* Never directly dereference!  Could change on reload */
 | |
| 		strncpy(tmp->tech, cur->tech, sizeof(tmp->tech)-1);
 | |
| 		strncpy(tmp->numsubst, cur->loc, sizeof(tmp->numsubst)-1);
 | |
| 		/* If we're dialing by extension, look at the extension to know what to dial */
 | |
| 		if ((newnum = strstr(tmp->numsubst, "BYEXTENSION"))) {
 | |
| 			strncpy(restofit, newnum + strlen("BYEXTENSION"), sizeof(restofit)-1);
 | |
| 			snprintf(newnum, sizeof(tmp->numsubst) - (newnum - tmp->numsubst), "%s%s", qe->chan->exten,restofit);
 | |
| 			if (option_debug)
 | |
| 				ast_log(LOG_DEBUG, "Dialing by extension %s\n", tmp->numsubst);
 | |
| 		}
 | |
| 		/* Special case: If we ring everyone, go ahead and ring them, otherwise
 | |
| 		   just calculate their metric for the appropriate strategy */
 | |
| 		calc_metric(qe->parent, cur, x++, qe, tmp);
 | |
| 		/* Put them in the list of outgoing thingies...  We're ready now. 
 | |
| 		   XXX If we're forcibly removed, these outgoing calls won't get
 | |
| 		   hung up XXX */
 | |
| 		tmp->next = outgoing;
 | |
| 		outgoing = tmp;		
 | |
| 		/* If this line is up, don't try anybody else */
 | |
| 		if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
 | |
| 			break;
 | |
| 
 | |
| 		cur = cur->next;
 | |
| 	}
 | |
| 	if (qe->parent->timeout)
 | |
| 		to = qe->parent->timeout * 1000;
 | |
| 	else
 | |
| 		to = -1;
 | |
| 	ring_one(qe, outgoing);
 | |
| 	ast_mutex_unlock(&qe->parent->lock);
 | |
| 	lpeer = wait_for_answer(qe, outgoing, &to, &allowredir_in, &allowredir_out, &allowdisconnect, &digit);
 | |
| 	if (lpeer)
 | |
| 		peer = lpeer->chan;
 | |
| 	else
 | |
| 		peer = NULL;
 | |
| 	if (!peer) {
 | |
| 		if (to) {
 | |
| 			/* Musta gotten hung up */
 | |
| 			res = -1;
 | |
| 		} else {
 | |
| 			if (digit && valid_exit(qe, digit))
 | |
| 				res=digit;
 | |
| 			else
 | |
| 				/* Nobody answered, next please? */
 | |
| 				res=0;
 | |
| 		}
 | |
| 		goto out;
 | |
| 	}
 | |
| 	if (peer) {
 | |
| 		/* Ah ha!  Someone answered within the desired timeframe.  Of course after this
 | |
| 		   we will always return with -1 so that it is hung up properly after the 
 | |
| 		   conversation.  */
 | |
| 		if (!strcmp(qe->chan->type,"Zap")) {
 | |
| 			if (tmp->dataquality) zapx = 0;
 | |
| 			ast_channel_setoption(qe->chan,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
 | |
| 		}			
 | |
| 		if (!strcmp(peer->type,"Zap")) {
 | |
| 			if (tmp->dataquality) zapx = 0;
 | |
| 			ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&zapx,sizeof(char),0);
 | |
| 		}
 | |
| 		/* Update parameters for the queue */
 | |
| 		update_queue(qe->parent, lpeer);
 | |
| 		hanguptree(outgoing, peer);
 | |
| 		/* Stop music on hold */
 | |
| 		ast_moh_stop(qe->chan);
 | |
| 		outgoing = NULL;
 | |
| 		if (announce) {
 | |
| 			int res2;
 | |
| 			res2 = ast_autoservice_start(qe->chan);
 | |
| 			if (!res2)
 | |
| 				res2 = ast_streamfile(peer, announce, peer->language);
 | |
| 			if (!res2)
 | |
| 				res2 = ast_waitstream(peer, "");
 | |
| 			res2 |= ast_autoservice_stop(qe->chan);
 | |
| 			if (res2) {
 | |
| 				/* Agent must have hung up */
 | |
| 				ast_log(LOG_WARNING, "Agent on %s hungup on the customer.  They're going to be pissed.\n", peer->name);
 | |
| 				ast_hangup(peer);
 | |
| 				return -1;
 | |
| 			}
 | |
| 		}
 | |
| 		/* If appropriate, log that we have a destination channel */
 | |
| 		if (qe->chan->cdr)
 | |
| 			ast_cdr_setdestchan(qe->chan->cdr, peer->name);
 | |
| 		/* Make sure channels are compatible */
 | |
| 		res = ast_channel_make_compatible(qe->chan, peer);
 | |
| 		if (res < 0) {
 | |
| 			ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name);
 | |
| 			ast_hangup(peer);
 | |
| 			return -1;
 | |
| 		}
 | |
| 		/* Drop out of the queue at this point, to prepare for next caller */
 | |
| 		leave_queue(qe);			
 | |
|  		/* JDG: sendurl */
 | |
|  		if( url && strlen(url) && ast_channel_supports_html(peer) ) {
 | |
|  			ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url);
 | |
|  			ast_channel_sendurl( peer, url );
 | |
|  		} /* /JDG */
 | |
| 		bridge = ast_bridge_call(qe->chan, peer, allowredir_in, allowredir_out, allowdisconnect);
 | |
| 
 | |
| 		if(bridge != AST_PBX_NO_HANGUP_PEER)
 | |
| 			ast_hangup(peer);
 | |
| 
 | |
| 		if( bridge == 0 ) res=1; /* JDG: bridge successfull, leave app_queue */
 | |
| 		else res = bridge; /* bridge error, stay in the queue */
 | |
| 	}	
 | |
| out:
 | |
| 	hanguptree(outgoing, NULL);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static int wait_a_bit(struct queue_ent *qe)
 | |
| {
 | |
| 	/* Don't need to hold the lock while we setup the outgoing calls */
 | |
| 	int retrywait = qe->parent->retry * 1000;
 | |
| 	return ast_waitfordigit(qe->chan, retrywait);
 | |
| }
 | |
| 
 | |
| // [PHM 06/26/03]
 | |
| 
 | |
| static struct member * interface_exists( struct ast_call_queue * q, char * interface )
 | |
| {
 | |
| 	struct member * ret = NULL ;
 | |
| 	struct member *mem;
 | |
| 	char buf[500] ;
 | |
| 
 | |
| 	if( q != NULL )
 | |
| 	{
 | |
| 		mem = q->members ;
 | |
| 
 | |
| 		while( mem != NULL ) {
 | |
| 			sprintf( buf, "%s/%s", mem->tech, mem->loc);
 | |
| 
 | |
| 			if( strcmp( buf, interface ) == 0 ) {
 | |
| 				ret = mem ;
 | |
| 				break ;
 | |
| 			}
 | |
| 			else
 | |
| 				mem = mem->next ;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return( ret ) ;
 | |
| }
 | |
| 
 | |
| 
 | |
| static struct member * create_queue_node( char * interface )
 | |
| {
 | |
| 	struct member * cur ;
 | |
| 	char * tmp ;
 | |
| 	
 | |
| 	/* Add a new member */
 | |
| 
 | |
| 	cur = malloc(sizeof(struct member));
 | |
| 
 | |
| 	if (cur) {
 | |
| 		memset(cur, 0, sizeof(struct member));
 | |
| 		strncpy(cur->tech, interface, sizeof(cur->tech) - 1);
 | |
| 		if ((tmp = strchr(cur->tech, '/')))
 | |
| 			*tmp = '\0';
 | |
| 		if ((tmp = strchr(interface, '/'))) {
 | |
| 			tmp++;
 | |
| 			strncpy(cur->loc, tmp, sizeof(cur->loc) - 1);
 | |
| 		} else
 | |
| 			ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
 | |
| 	}
 | |
| 
 | |
| 	return( cur ) ;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int rqm_exec(struct ast_channel *chan, void *data)
 | |
| {
 | |
| 	int res=-1;
 | |
| 	struct localuser *u;
 | |
| 	char *queuename;
 | |
| 	struct member * node ;
 | |
| 	struct member * look ;
 | |
| 	char info[512];
 | |
| 	char tmpchan[256]="";
 | |
| 	char *interface=NULL;
 | |
| 	struct ast_call_queue *q;
 | |
| 	int found=0 ;
 | |
| 
 | |
| 	if (!data) {
 | |
| 		ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename|optional interface)\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 	
 | |
| 	LOCAL_USER_ADD(u); // not sure if we need this, but better be safe than sorry ;-)
 | |
| 	
 | |
| 	/* Parse our arguments XXX Check for failure XXX */
 | |
| 	strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
 | |
| 	queuename = info;
 | |
| 	if (queuename) {
 | |
| 		interface = strchr(queuename, '|');
 | |
| 		if (interface) {
 | |
| 			*interface = '\0';
 | |
| 			interface++;
 | |
| 		}
 | |
| 		else {
 | |
| 			strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
 | |
| 			interface = strrchr(tmpchan, '-');
 | |
| 			if (interface)
 | |
| 				*interface = '\0';
 | |
| 			interface = tmpchan;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if( ( q = queues) != NULL )
 | |
| 	{
 | |
| 		while( q && ( res != 0 ) && (!found) ) 
 | |
| 		{
 | |
| 			ast_mutex_lock(&q->lock);
 | |
| 			if( strcmp( q->name, queuename) == 0 )
 | |
| 			{
 | |
| 				// found queue, try to remove  interface
 | |
| 				found=1 ;
 | |
| 
 | |
| 				if( ( node = interface_exists( q, interface ) ) != NULL )
 | |
| 				{
 | |
| 					if( ( look = q->members ) == node )
 | |
| 					{
 | |
| 						// 1st
 | |
| 						q->members = node->next;
 | |
| 					}
 | |
| 					else
 | |
| 					{
 | |
| 						while( look != NULL )
 | |
| 							if( look->next == node )
 | |
| 							{
 | |
| 								look->next = node->next ;
 | |
| 								break ;
 | |
| 							}
 | |
| 							else
 | |
| 								look = look->next ;
 | |
| 					}
 | |
| 
 | |
| 					free( node ) ;
 | |
| 
 | |
| 					ast_log(LOG_NOTICE, "Removed interface '%s' to queue '%s'\n", 
 | |
| 						interface, queuename);
 | |
| 					res = 0 ;
 | |
| 				}
 | |
| 				else
 | |
| 					ast_log(LOG_WARNING, "Unable to remove interface '%s' from queue '%s': "
 | |
| 						"Not there\n", interface, queuename);
 | |
| 			}
 | |
| 
 | |
| 			ast_mutex_unlock(&q->lock);
 | |
| 			q = q->next;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if( ! found )
 | |
| 		ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", queuename);
 | |
| 
 | |
| 	LOCAL_USER_REMOVE(u);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| static int aqm_exec(struct ast_channel *chan, void *data)
 | |
| {
 | |
| 	int res=-1;
 | |
| 	struct localuser *u;
 | |
| 	char *queuename;
 | |
| 	char info[512];
 | |
| 	char tmpchan[512]="";
 | |
| 	char *interface=NULL;
 | |
| 	struct ast_call_queue *q;
 | |
| 	struct member *save;
 | |
| 	int found=0 ;
 | |
| 
 | |
| 	if (!data) {
 | |
| 		ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename|optional interface)\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 	
 | |
| 	LOCAL_USER_ADD(u); // not sure if we need this, but better be safe than sorry ;-)
 | |
| 	
 | |
| 	/* Parse our arguments XXX Check for failure XXX */
 | |
| 	strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
 | |
| 	queuename = info;
 | |
| 	if (queuename) {
 | |
| 		interface = strchr(queuename, '|');
 | |
| 		if (interface) {
 | |
| 			*interface = '\0';
 | |
| 			interface++;
 | |
| 		}
 | |
| 		else {
 | |
| 			strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
 | |
| 			interface = strrchr(tmpchan, '-');
 | |
| 			if (interface)
 | |
| 				*interface = '\0';
 | |
| 			interface = tmpchan;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if( ( q = queues) != NULL )
 | |
| 	{
 | |
| 		while( q && ( res != 0 ) && (!found) ) 
 | |
| 		{
 | |
| 			ast_mutex_lock(&q->lock);
 | |
| 			if( strcmp( q->name, queuename) == 0 )
 | |
| 			{
 | |
| 				// found queue, try to enable interface
 | |
| 				found=1 ;
 | |
| 
 | |
| 				if( interface_exists( q, interface ) == NULL )
 | |
| 				{
 | |
| 					save = q->members ;
 | |
| 					q->members = create_queue_node( interface ) ;
 | |
| 
 | |
| 					if( q->members != NULL ) {
 | |
| 						q->members->dynamic = 1;
 | |
| 						q->members->next = save ;
 | |
| 					} else
 | |
| 						q->members = save ;
 | |
| 
 | |
| 					ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", interface, queuename);
 | |
| 					res = 0 ;
 | |
| 				}
 | |
| 				else
 | |
| 					ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': "
 | |
| 						"Already there\n", interface, queuename);
 | |
| 			}
 | |
| 
 | |
| 			ast_mutex_unlock(&q->lock);
 | |
| 			q = q->next;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if( ! found )
 | |
| 		ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", queuename);
 | |
| 
 | |
| 	LOCAL_USER_REMOVE(u);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int queue_exec(struct ast_channel *chan, void *data)
 | |
| {
 | |
| 	int res=-1;
 | |
| 	struct localuser *u;
 | |
| 	char *queuename;
 | |
| 	char info[512];
 | |
| 	char *options = NULL;
 | |
| 	char *url = NULL;
 | |
| 	char *announceoverride = NULL;
 | |
| 	
 | |
| 	/* Our queue entry */
 | |
| 	struct queue_ent qe;
 | |
| 	
 | |
| 	if (!data) {
 | |
| 		ast_log(LOG_WARNING, "Queue requires an argument (queuename|optional timeout|optional URL)\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 	
 | |
| 	LOCAL_USER_ADD(u);
 | |
| 	
 | |
| 	/* Parse our arguments XXX Check for failure XXX */
 | |
| 	strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
 | |
| 	queuename = info;
 | |
| 	if (queuename) {
 | |
| 		options = strchr(queuename, '|');
 | |
| 		if (options) {
 | |
| 			*options = '\0';
 | |
| 			options++;
 | |
| 			url = strchr(options, '|');
 | |
| 			if (url) {
 | |
| 				*url = '\0';
 | |
| 				url++;
 | |
| 				announceoverride = strchr(url, '|');
 | |
| 				if (announceoverride) {
 | |
| 					*announceoverride = '\0';
 | |
| 					announceoverride++;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	printf("queue: %s, options: %s, url: %s, announce: %s\n",
 | |
| 		queuename, options, url, announceoverride);
 | |
| 	/* Setup our queue entry */
 | |
| 	memset(&qe, 0, sizeof(qe));
 | |
| 	qe.chan = chan;
 | |
| 	qe.start = time(NULL);
 | |
| 	if (!join_queue(queuename, &qe)) {
 | |
| 		/* Start music on hold */
 | |
| 		ast_moh_start(chan, qe.moh);
 | |
| 		for (;;) {
 | |
| 			res = wait_our_turn(&qe);
 | |
| 			/* If they hungup, return immediately */
 | |
| 			if (res < 0) {
 | |
| 				if (option_verbose > 2) {
 | |
| 					ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n");
 | |
| 					res = -1;
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 			if (!res)
 | |
| 				break;
 | |
| 			if (valid_exit(&qe, res))
 | |
| 				break;
 | |
| 		}
 | |
| 		if (!res) {
 | |
| 			for (;;) {
 | |
| 				res = try_calling(&qe, options, announceoverride, url);
 | |
| 				if (res)
 | |
| 					break;
 | |
| 				res = wait_a_bit(&qe);
 | |
| 				if (res < 0) {
 | |
| 					if (option_verbose > 2) {
 | |
| 						ast_verbose(VERBOSE_PREFIX_3 "User disconnected when they almost made it\n");
 | |
| 						res = -1;
 | |
| 					}
 | |
| 					break;
 | |
| 				}
 | |
| 				if (res && valid_exit(&qe, res))
 | |
| 					break;
 | |
| 			}
 | |
| 		}
 | |
| 		/* Don't allow return code > 0 */
 | |
| 		if (res > 0 && res != AST_PBX_KEEPALIVE) {
 | |
| 			res = 0;	
 | |
| 			ast_moh_stop(chan);
 | |
| 		}
 | |
| 		leave_queue(&qe);
 | |
| 	} else {
 | |
| 		ast_log(LOG_WARNING, "Unable to join queue '%s'\n", queuename);
 | |
| 		res =  0;
 | |
| 	}
 | |
| 	LOCAL_USER_REMOVE(u);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| static void reload_queues(void)
 | |
| {
 | |
| 	struct ast_call_queue *q, *ql, *qn;
 | |
| 	struct ast_config *cfg;
 | |
| 	char *cat, *tmp;
 | |
| 	struct ast_variable *var;
 | |
| 	struct member *prev, *cur;
 | |
| 	int new;
 | |
| 	cfg = ast_load("queues.conf");
 | |
| 	if (!cfg) {
 | |
| 		ast_log(LOG_NOTICE, "No call queueing config file, so no call queues\n");
 | |
| 		return;
 | |
| 	}
 | |
| 	ast_mutex_lock(&qlock);
 | |
| 	/* Mark all queues as dead for the moment */
 | |
| 	q = queues;
 | |
| 	while(q) {
 | |
| 		q->dead = 1;
 | |
| 		q = q->next;
 | |
| 	}
 | |
| 	/* Chug through config file */
 | |
| 	cat = ast_category_browse(cfg, NULL);
 | |
| 	while(cat) {
 | |
| 		if (strcasecmp(cat, "general")) {
 | |
| 			/* Look for an existing one */
 | |
| 			q = queues;
 | |
| 			while(q) {
 | |
| 				if (!strcmp(q->name, cat))
 | |
| 					break;
 | |
| 				q = q->next;
 | |
| 			}
 | |
| 			if (!q) {
 | |
| 				/* Make one then */
 | |
| 				q = malloc(sizeof(struct ast_call_queue));
 | |
| 				if (q) {
 | |
| 					/* Initialize it */
 | |
| 					memset(q, 0, sizeof(struct ast_call_queue));
 | |
| 					ast_mutex_init(&q->lock);
 | |
| 					strncpy(q->name, cat, sizeof(q->name));
 | |
| 					new = 1;
 | |
| 				} else new = 0;
 | |
| 			} else
 | |
| 					new = 0;
 | |
| 			if (q) {
 | |
| 				if (!new) 
 | |
| 					ast_mutex_lock(&q->lock);
 | |
| 				/* Re-initialize the queue */
 | |
| 				q->dead = 0;
 | |
| 				q->retry = 0;
 | |
| 				q->timeout = -1;
 | |
| 				q->maxlen = 0;
 | |
| 				free_members(q, 0);
 | |
| 				strcpy(q->moh, "");
 | |
| 				strcpy(q->announce, "");
 | |
| 				strcpy(q->context, "");
 | |
| 				prev = q->members;
 | |
| 				if (prev) {
 | |
| 					/* find the end of any dynamic members */
 | |
| 					while(prev->next)
 | |
| 						prev = prev->next;
 | |
| 				}
 | |
| 				var = ast_variable_browse(cfg, cat);
 | |
| 				while(var) {
 | |
| 					if (!strcasecmp(var->name, "member")) {
 | |
| 						/* Add a new member */
 | |
| 						cur = malloc(sizeof(struct member));
 | |
| 						if (cur) {
 | |
| 							memset(cur, 0, sizeof(struct member));
 | |
| 							strncpy(cur->tech, var->value, sizeof(cur->tech) - 1);
 | |
| 							if ((tmp = strchr(cur->tech, ','))) {
 | |
| 								*tmp = '\0';
 | |
| 								tmp++;
 | |
| 								cur->penalty = atoi(tmp);
 | |
| 								if (cur->penalty < 0)
 | |
| 									cur->penalty = 0;
 | |
| 							}
 | |
| 							if ((tmp = strchr(cur->tech, '/')))
 | |
| 								*tmp = '\0';
 | |
| 							if ((tmp = strchr(var->value, '/'))) {
 | |
| 								tmp++;
 | |
| 								strncpy(cur->loc, tmp, sizeof(cur->loc) - 1);
 | |
| 								if ((tmp = strchr(cur->loc, ',')))
 | |
| 									*tmp = '\0';
 | |
| 							} else
 | |
| 								ast_log(LOG_WARNING, "No location at line %d of queue.conf\n", var->lineno);
 | |
| 							if (prev)
 | |
| 								prev->next = cur;
 | |
| 							else
 | |
| 								q->members = cur;
 | |
| 							prev = cur;
 | |
| 						}
 | |
| 					} else if (!strcasecmp(var->name, "music")) {
 | |
| 						strncpy(q->moh, var->value, sizeof(q->moh) - 1);
 | |
| 					} else if (!strcasecmp(var->name, "announce")) {
 | |
| 						strncpy(q->announce, var->value, sizeof(q->announce) - 1);
 | |
| 					} else if (!strcasecmp(var->name, "context")) {
 | |
| 						strncpy(q->context, var->value, sizeof(q->context) - 1);
 | |
| 					} else if (!strcasecmp(var->name, "timeout")) {
 | |
| 						q->timeout = atoi(var->value);
 | |
| 					} else if (!strcasecmp(var->name, "retry")) {
 | |
| 						q->retry = atoi(var->value);
 | |
| 					} else if (!strcasecmp(var->name, "maxlen")) {
 | |
| 						q->maxlen = atoi(var->value);
 | |
| 					} else if (!strcasecmp(var->name, "strategy")) {
 | |
| 						q->strategy = strat2int(var->value);
 | |
| 						if (q->strategy < 0) {
 | |
| 							ast_log(LOG_WARNING, "'%s' isn't a valid strategy, using ringall instead\n", var->value);
 | |
| 							q->strategy = 0;
 | |
| 						}
 | |
| 					} else {
 | |
| 						ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queue.conf\n", cat, var->name, var->lineno);
 | |
| 					}
 | |
| 					var = var->next;
 | |
| 				}
 | |
| 				if (q->retry < 1)
 | |
| 					q->retry = DEFAULT_RETRY;
 | |
| 				if (q->timeout < 0)
 | |
| 					q->timeout = DEFAULT_TIMEOUT;
 | |
| 				if (q->maxlen < 0)
 | |
| 					q->maxlen = 0;
 | |
| 				if (!new) 
 | |
| 					ast_mutex_unlock(&q->lock);
 | |
| 				if (new) {
 | |
| 					q->next = queues;
 | |
| 					queues = q;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 		cat = ast_category_browse(cfg, cat);
 | |
| 	}
 | |
| 	ast_destroy(cfg);
 | |
| 	q = queues;
 | |
| 	ql = NULL;
 | |
| 	while(q) {
 | |
| 		qn = q->next;
 | |
| 		if (q->dead) {
 | |
| 			if (ql)
 | |
| 				ql->next = q->next;
 | |
| 			else
 | |
| 				queues = q->next;
 | |
| 			if (!q->count) {
 | |
| 				free(q);
 | |
| 			} else
 | |
| 				ast_log(LOG_WARNING, "XXX Leaking a litttle memory :( XXX\n");
 | |
| 		} else
 | |
| 			ql = q;
 | |
| 		q = qn;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&qlock);
 | |
| }
 | |
| 
 | |
| static int queues_show(int fd, int argc, char **argv)
 | |
| {
 | |
| 	struct ast_call_queue *q;
 | |
| 	struct queue_ent *qe;
 | |
| 	struct member *mem;
 | |
| 	int pos;
 | |
| 	time_t now;
 | |
| 	char max[80];
 | |
| 	char calls[80];
 | |
| 	
 | |
| 	time(&now);
 | |
| 	if (argc != 2)
 | |
| 		return RESULT_SHOWUSAGE;
 | |
| 	ast_mutex_lock(&qlock);
 | |
| 	q = queues;
 | |
| 	if (!q) {	
 | |
| 		ast_mutex_unlock(&qlock);
 | |
| 		ast_cli(fd, "No queues.\n");
 | |
| 		return RESULT_SUCCESS;
 | |
| 	}
 | |
| 	while(q) {
 | |
| 		ast_mutex_lock(&q->lock);
 | |
| 		if (q->maxlen)
 | |
| 			snprintf(max, sizeof(max), "%d", q->maxlen);
 | |
| 		else
 | |
| 			strcpy(max, "unlimited");
 | |
| 		ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy\n", q->name, q->count, max, int2strat(q->strategy));
 | |
| 		if (q->members) {
 | |
| 			ast_cli(fd, "   Members: \n");
 | |
| 			for (mem = q->members; mem; mem = mem->next) {
 | |
| 				if (mem->penalty)
 | |
| 					snprintf(max, sizeof(max) - 20, " with penalty %d", mem->penalty);
 | |
| 				else
 | |
| 					strcpy(max, "");
 | |
| 				if (mem->dynamic)
 | |
| 					strcat(max, " (dynamic)");
 | |
| 				if (mem->calls) {
 | |
| 					snprintf(calls, sizeof(calls), " has taken %d calls (last was %ld secs ago)",
 | |
| 							mem->calls, (long)(time(NULL) - mem->lastcall));
 | |
| 				} else
 | |
| 					strcpy(calls, " has taken no calls yet");
 | |
| 				ast_cli(fd, "      %s/%s%s%s\n", mem->tech, mem->loc, max, calls);
 | |
| 			}
 | |
| 		} else
 | |
| 			ast_cli(fd, "   No Members\n");
 | |
| 		if (q->head) {
 | |
| 			pos = 1;
 | |
| 			ast_cli(fd, "   Callers: \n");
 | |
| 			for (qe = q->head; qe; qe = qe->next) 
 | |
| 				ast_cli(fd, "      %d. %s (wait: %ld:%2.2ld)\n", pos++, qe->chan->name,
 | |
| 								(long)(now - qe->start) / 60, (long)(now - qe->start) % 60);
 | |
| 		} else
 | |
| 			ast_cli(fd, "   No Callers\n");
 | |
| 		ast_cli(fd, "\n");
 | |
| 		ast_mutex_unlock(&q->lock);
 | |
| 		q = q->next;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&qlock);
 | |
| 	return RESULT_SUCCESS;
 | |
| }
 | |
| 
 | |
| /* JDG: callback to display queues status in manager */
 | |
| static int manager_queues_show( struct mansession *s, struct message *m )
 | |
| {
 | |
| 	char *a[] = { "show", "queues" };
 | |
| 	return queues_show( s->fd, 2, a );
 | |
| } /* /JDG */
 | |
| 
 | |
| 
 | |
| /* Dump queue status */
 | |
| static int manager_queues_status( struct mansession *s, struct message *m )
 | |
| {
 | |
| 	time_t now;
 | |
| 	int pos;
 | |
| 	char *id = astman_get_header(m,"ActionID");
 | |
| 	char idText[256] = "";
 | |
| 	struct ast_call_queue *q;
 | |
| 	struct queue_ent *qe;
 | |
| 	astman_send_ack(s, m, "Queue status will follow");
 | |
| 	time(&now);
 | |
| 	ast_mutex_lock(&qlock);
 | |
| 	q = queues;
 | |
| 	if (id && &id) {
 | |
| 		snprintf(idText,256,"ActionID: %s\r\n",id);
 | |
| 	}
 | |
| 	while(q) {
 | |
| 		ast_mutex_lock(&q->lock);
 | |
| 		ast_cli(s->fd, "Event: QueueParams\r\n"
 | |
| 					"Queue: %s\r\n"
 | |
| 					"Max: %d\r\n"
 | |
| 					"Calls: %d\r\n"
 | |
| 					"%s"
 | |
| 					"\r\n",
 | |
| 						q->name, q->maxlen, q->count,idText);
 | |
| #if 0
 | |
| 		/* Do we care about queue members? */					
 | |
| 		for (mem = q->members; mem; mem = mem->next) 
 | |
| 			ast_cli(fd, "      %s/%s\n", mem->tech, mem->loc);
 | |
| #endif			
 | |
| 		pos = 1;
 | |
| 		for (qe = q->head; qe; qe = qe->next) 
 | |
| 			ast_cli(s->fd, "Event: QueueMember\r\n"
 | |
| 				"Queue: %s\r\n"
 | |
| 				"Position: %d\r\n"
 | |
| 				"Channel: %s\r\n"
 | |
| 				"CallerID: %s\r\n"
 | |
| 				"Wait: %ld\r\n"
 | |
| 				"%s"
 | |
| 				"\r\n", 
 | |
| 					q->name, pos++, qe->chan->name, (qe->chan->callerid ? qe->chan->callerid : ""), (long)(now - qe->start), idText);
 | |
| 		ast_mutex_unlock(&q->lock);
 | |
| 		q = q->next;
 | |
| 	}
 | |
| 	ast_mutex_unlock(&qlock);
 | |
| 	return RESULT_SUCCESS;
 | |
| }
 | |
| 
 | |
| static char show_queues_usage[] = 
 | |
| "Usage: show queues\n"
 | |
| "       Provides summary information on call queues.\n";
 | |
| 
 | |
| static struct ast_cli_entry cli_show_queues = {
 | |
| 	{ "show", "queues", NULL }, queues_show, 
 | |
| 	"Show status of queues", show_queues_usage, NULL };
 | |
| 
 | |
| int unload_module(void)
 | |
| {
 | |
| 	STANDARD_HANGUP_LOCALUSERS;
 | |
| 	ast_cli_unregister(&cli_show_queues);
 | |
| 	ast_manager_unregister( "Queues" );
 | |
| 	ast_manager_unregister( "QueueStatus" );
 | |
| 	return ast_unregister_application(app);
 | |
| }
 | |
| 
 | |
| int load_module(void)
 | |
| {
 | |
| 	int res;
 | |
| 	res = ast_register_application(app, queue_exec, synopsis, descrip);
 | |
| 	if (!res) {
 | |
| 		ast_cli_register(&cli_show_queues);
 | |
| 		ast_manager_register( "Queues", 0, manager_queues_show, "Queues" );
 | |
| 		ast_manager_register( "QueueStatus", 0, manager_queues_status, "Queue Status" );
 | |
| 
 | |
| 		// [PHM 06/26/03]
 | |
| 		ast_register_application(app_aqm, aqm_exec, app_aqm_synopsis, app_aqm_descrip) ;
 | |
| 		ast_register_application(app_rqm, rqm_exec, app_rqm_synopsis, app_rqm_descrip) ;
 | |
| 	}
 | |
| 	reload_queues();
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| 
 | |
| int reload(void)
 | |
| {
 | |
| 	reload_queues();
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| char *description(void)
 | |
| {
 | |
| 	return tdesc;
 | |
| }
 | |
| 
 | |
| int usecount(void)
 | |
| {
 | |
| 	int res;
 | |
| 	STANDARD_USECOUNT(res);
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| char *key()
 | |
| {
 | |
| 	return ASTERISK_GPL_KEY;
 | |
| }
 |