Skip to content
Snippets Groups Projects
app_queue.c 37.9 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, 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>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/manager.h> /* JDG */
    
    Mark Spencer's avatar
    Mark Spencer committed
    #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" },
    };
    
    
    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 */
    
    static char *tdesc = "True Call Queueing";
    
    static char *app = "Queue";
    
    static char *synopsis = "Queue a call for a call queue";
    
    static char *descrip =
    
    Mark Spencer's avatar
    Mark Spencer committed
    "  Queue(queuename[|options[|URL][|announceoverride]]):\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 caller to hang up by hitting *.\n"
    "  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"
    "  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"
    "";
    
    
    
    
    
    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;
    
    	char numsubst[256];
    	char tech[40];
    
    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;
    
    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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char context[80];		/* Context when user exits queue */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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;
    	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 */
    	char context[80];		/* Announcement to play */
    
    	int strategy;			/* Queueing strategy */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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? */
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    	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;
    }
    
    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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	q = queues;
    	while(q) {
    		if (!strcasecmp(q->name, queuename)) {
    			/* This is our one */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    				manager_event(EVENT_FLAG_CALL, "Join", 
    
    Mark Spencer's avatar
    Mark Spencer committed
            	                                               	"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 : ""), 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;
    		}
    		q = q->next;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    static void free_members(struct ast_call_queue *q)
    {
    	struct member *curm, *next;
    	curm = q->members;
    	while(curm) {
    		next = curm->next;
    		free(curm);
    		curm = next;
    	}
    	q->members = NULL;
    }
    
    static void destroy_queue(struct ast_call_queue *q)
    {
    	struct ast_call_queue *cur, *prev = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	cur = queues;
    	while(cur) {
    		if (cur == q) {
    			if (prev)
    				prev->next = cur->next;
    			else
    				queues = cur->next;
    		} else {
    			prev = cur;
    		}
    		cur = cur->next;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	free_members(q);
    	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;
    
    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",
    
    Mark Spencer's avatar
    Mark Spencer committed
    				 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 {
    			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 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)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	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)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct localuser *o;
    	int found;
    	int numlines;
    	int sentringing = 0;
    	int numbusies = 0;
    	int orig = *to;
    	struct ast_frame *f;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *watchers[MAX];
    	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) {
    				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)) {
    
    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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    					*allowdisconnect = o->allowdisconnect;
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			} else if (o->chan && (o->chan == winner)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				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);
    
    								*allowredir_in = o->allowredirect_in;
    								*allowredir_out = o->allowredirect_out;
    
    Mark Spencer's avatar
    Mark Spencer committed
    								*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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    			}
    			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 == '*')) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			    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;
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    		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 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ch = qe->parent->head;
    
    		ast_mutex_unlock(&qe->parent->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* 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 */
    
    	cur = q->members;
    	while(cur) {
    		if (user->member == cur) {
    			time(&cur->lastcall);
    			cur->calls++;
    			break;
    		}
    		cur = cur->next;
    	}
    
    	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) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (!q->wrapped) {
    				/* No more channels, start over */
    				q->rrpos = 0;
    			} else {
    				/* Prioritize next entry */
    
    			q->wrapped = 0;
    		}
    		if (pos < q->rrpos) {
    			tmp->metric = 1000 + pos;
    		} else {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (pos > q->rrpos) {
    				/* Indicate there is another priority */
    
    		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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int try_calling(struct queue_ent *qe, char *options, char *announceoverride, char *url)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	struct member *cur;
    	struct localuser *outgoing=NULL, *tmp = NULL;
    	int to;
    
    	int allowredir_in=0;
    	int allowredir_out=0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int allowdisconnect=0;
    	char restofit[AST_MAX_EXTENSION];
    	char *newnum;
    	struct ast_channel *peer;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res = 0, bridge = 0;
    
    	int zapx = 2;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *announce = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Hold the lock while we setup the outgoing calls */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	cur = qe->parent->members;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (strlen(qe->announce))
    		announce = qe->announce;
    	if (announceoverride && strlen(announceoverride))
    		announce = announceoverride;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	while(cur) {
    		/* Get a technology/[device:]number pair */
    		tmp = malloc(sizeof(struct localuser));
    		if (!tmp) {
    			ast_log(LOG_WARNING, "Out of memory\n");
    			goto out;
    		}
    		memset(tmp, 0, sizeof(struct localuser));
    
    		tmp->stillgoing = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (options) {
    			if (strchr(options, 't'))
    
    				tmp->allowredirect_in = 1;
    			if (strchr(options, 'T'))
    				tmp->allowredirect_out = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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;
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* If we're dialing by extension, look at the extension to know what to dial */
    
    		if ((newnum = strstr(tmp->numsubst, "BYEXTENSION"))) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			strncpy(restofit, newnum + strlen("BYEXTENSION"), sizeof(restofit)-1);
    
    			snprintf(newnum, sizeof(tmp->numsubst) - (newnum - tmp->numsubst), "%s%s", qe->chan->exten,restofit);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (option_debug)
    
    				ast_log(LOG_DEBUG, "Dialing by extension %s\n", tmp->numsubst);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    		/* 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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* 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))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    
    		cur = cur->next;
    	}
    	if (qe->parent->timeout)
    		to = qe->parent->timeout * 1000;
    	else
    		to = -1;
    
    	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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!peer) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Musta gotten hung up */
    			res = -1;
    
    		} else {
    			if (digit && valid_exit(qe, digit))
    				res=digit;
    			else
    				/* Nobody answered, next please? */
    				res=0;
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		hanguptree(outgoing, peer);
    		/* Stop music on hold */
    		ast_moh_stop(qe->chan);
    		outgoing = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (announce) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			int res2;
    
    			res2 = ast_autoservice_start(qe->chan);
    			if (!res2)
    				res2 = ast_streamfile(peer, announce, peer->language);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* XXX Need a function to wait on *both* streams XXX */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (!res2)
    				res2 = ast_waitstream(peer, "");
    
    			res2 |= ast_autoservice_stop(qe->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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);			
    
    Mark Spencer's avatar
    Mark Spencer committed
     		/* 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);
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if( bridge == 0 ) res=1; /* JDG: bridge successfull, leave app_queue */
    		else res = bridge; /* bridge error, stay in the queue */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}	
    out:
    	hanguptree(outgoing, NULL);
    	return res;
    }
    
    static int wait_a_bit(struct queue_ent *qe)
    {
    	int retrywait;
    	/* Hold the lock while we setup the outgoing calls */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	retrywait = qe->parent->retry * 1000;
    
    	ast_mutex_unlock(&qe->parent->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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 *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
    			interface = chan->name ;
    	}
    
    	if( ( q = queues) != NULL )
    	{
    		while( q && ( res != 0 ) && (!found) ) 
    		{
    
    			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);
    			}
    
    
    			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;
    }