Skip to content
Snippets Groups Projects
app_queue.c 61.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	char queuename[256]="";
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *newnum;
    
    	char *monitorfilename;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *peer;
    
    	struct member *member;
    
    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;
    
    	time_t callstart;
    
    	struct ast_bridge_config config;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Hold the lock while we setup the outgoing calls */
    
    	if (option_debug)
    		ast_log(LOG_DEBUG, "%s is trying to call a queue member.\n", 
    							qe->chan->name);
    
    	strncpy(queuename, qe->parent->name, sizeof(queuename) - 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	cur = qe->parent->members;
    
    	if (!ast_strlen_zero(qe->announce))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		announce = qe->announce;
    
    	if (announceoverride && !ast_strlen_zero(announceoverride))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		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_mutex_unlock(&qe->parent->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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;
    
    			if ((strchr(options, 'n')) && (now - qe->start >= qe->parent->timeout))
    
    				*go_on = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    		if (option_debug) {
    			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);
    
    		tmp->lastcall = cur->lastcall;
    
    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);
    
    	ast_mutex_lock(&qe->parent->lock);
    	if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) {
    		store_next(qe, outgoing);
    	}
    	ast_mutex_unlock(&qe->parent->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!peer) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Musta gotten hung up */
    
    			record_abandoned(qe);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			res = -1;
    
    		} else {
    			if (digit && valid_exit(qe, digit))
    				res=digit;
    			else
    				/* Nobody answered, next please? */
    				res=0;
    		}
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "%s: Nobody answered.\n", qe->chan->name);
    
    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 */
    
    		recalc_holdtime(qe);
    
    		member = lpeer->member;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		hanguptree(outgoing, peer);
    		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);
    
    				res2 = ast_streamfile(peer, announce, peer->language);
    
    				if (!res2)
    					res2 = ast_waitstream(peer, "");
    				else {
    					ast_log(LOG_WARNING, "Announcement file '%s' is unavailable, continuing anyway...\n", announce);
    					res2 = 0;
    				}
    			}
    
    			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_queue_log(queuename, qe->chan->uniqueid, peer->name, "AGENTDUMP", "%s", "");
    
    Mark Spencer's avatar
    Mark Spencer committed
    				ast_hangup(peer);
    				return -1;
    			}
    		}
    
    		/* Stop music on hold */
    		ast_moh_stop(qe->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* 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_queue_log(queuename, qe->chan->uniqueid, peer->name, "SYSCOMPAT", "%s", "");
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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;
    		}
    
    		/* Begin Monitoring */
    		if (qe->parent->monfmt && *qe->parent->monfmt) {
    
    			monitorfilename = pbx_builtin_getvar_helper( qe->chan, "MONITOR_FILENAME");
    
    			if(monitorfilename) {
    				ast_monitor_start( peer, qe->parent->monfmt, monitorfilename, 1 );
    			} else {
    				ast_monitor_start( peer, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1 );
    			}
    			if(qe->parent->monjoin) {
    				ast_monitor_setjoinfiles( peer, 1);
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Drop out of the queue at this point, to prepare for next caller */
    		leave_queue(qe);			
    
     		if( url && !ast_strlen_zero(url) && ast_channel_supports_html(peer) ) {
    
    			if (option_debug)
    	 			ast_log(LOG_DEBUG, "app_queue: sendurl=%s.\n", url);
    
    Mark Spencer's avatar
    Mark Spencer committed
     			ast_channel_sendurl( peer, url );
    
    		ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "CONNECT", "%ld", (long)time(NULL) - qe->start);
    		strncpy(oldcontext, qe->chan->context, sizeof(oldcontext) - 1);
    		strncpy(oldexten, qe->chan->exten, sizeof(oldexten) - 1);
    		time(&callstart);
    
    
    		memset(&config,0,sizeof(struct ast_bridge_config));
            config.allowredirect_in = allowredir_in;
            config.allowredirect_out = allowredir_out;
            config.allowdisconnect = allowdisconnect;
            bridge = ast_bridge_call(qe->chan,peer,&config);
    
    
    		if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) {
    			ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "TRANSFER", "%s|%s", qe->chan->exten, qe->chan->context);
    		} else if (qe->chan->_softhangup) {
    			ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "COMPLETECALLER", "%ld|%ld", (long)(callstart - qe->start), (long)(time(NULL) - callstart));
    		} else {
    			ast_queue_log(queuename, qe->chan->uniqueid, peer->name, "COMPLETEAGENT", "%ld|%ld", (long)(callstart - qe->start), (long)(time(NULL) - callstart));
    		}
    
    
    		if(bridge != AST_PBX_NO_HANGUP_PEER)
    			ast_hangup(peer);
    
    		update_queue(qe->parent, member);
    
    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)
    {
    
    	/* Don't need to hold the lock while we setup the outgoing calls */
    	int retrywait = qe->parent->retry * 1000;
    
    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 ) {
    
    			snprintf( buf, sizeof(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, int penalty )
    
    {
    	struct member * cur ;
    	char * tmp ;
    	
    	/* Add a new member */
    
    	cur = malloc(sizeof(struct member));
    
    	if (cur) {
    		memset(cur, 0, sizeof(struct member));
    
    		cur->penalty = penalty;
    
    		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) ) 
    		{
    
    			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);
    
    	                                if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->callerid))
    						{
    						chan->priority += 100;
    						res = 0 ;
    						}
    				}
    
    			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;
    
    	char *penaltys=NULL;
    	int penalty = 0;
    
    	struct ast_call_queue *q;
    	struct member *save;
    	int found=0 ;
    
    	if (!data) {
    
    		ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename|optional interface|optional penalty)\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++;
    		}
    
    		if (interface) {
    			penaltys = strchr(interface, '|');
    			if (penaltys) {
    				*penaltys = 0;
    				penaltys++;
    			}
    		}
    		if (!interface || !strlen(interface)) {
    
    			strncpy(tmpchan, chan->name, sizeof(tmpchan) - 1);
    			interface = strrchr(tmpchan, '-');
    			if (interface)
    				*interface = '\0';
    			interface = tmpchan;
    		}
    
    		if (penaltys && strlen(penaltys)) {
    			if ((sscanf(penaltys, "%d", &penalty) != 1) || penalty < 0) {
    				ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", penaltys);
    				penalty = 0;
    			}
    		}
    
    	}
    
    	if( ( q = queues) != NULL )
    	{
    		while( q && ( res != 0 ) && (!found) ) 
    		{
    
    			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, penalty ) ;
    
    Mark Spencer's avatar
    Mark Spencer committed
    					if( q->members != NULL ) {
    						q->members->dynamic = 1;
    
    						q->members->next = save ;
    
    Mark Spencer's avatar
    Mark Spencer committed
    					} 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);
    
    			                if (ast_exists_extension(chan, chan->context, chan->exten, chan->priority + 101, chan->callerid))
                                            {
                                                    chan->priority += 100;
                                                    res = 0 ;
                                            }
    				}
    
    			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;
    }
    
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int queue_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct localuser *u;
    	char *queuename;
    	char info[512];
    	char *options = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *url = NULL;
    	char *announceoverride = NULL;
    
    	char *user_priority;
    	int prio;
    
    	char *queuetimeoutstr = NULL;
    
    	/* whether to exit Queue application after the timeout hits */
    	int go_on = 0;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Our queue entry */
    	struct queue_ent qe;
    	
    	if (!data) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_log(LOG_WARNING, "Queue requires an argument (queuename|optional timeout|optional URL)\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return -1;
    	}
    	
    	LOCAL_USER_ADD(u);
    
    
    	/* Setup our queue entry */
    	memset(&qe, 0, sizeof(qe));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	
    	/* 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++;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			url = strchr(options, '|');
    			if (url) {
    				*url = '\0';
    				url++;
    				announceoverride = strchr(url, '|');
    				if (announceoverride) {
    					*announceoverride = '\0';
    					announceoverride++;
    
    					queuetimeoutstr = strchr(announceoverride, '|');
    					if (queuetimeoutstr) {
    						*queuetimeoutstr = '\0';
    						queuetimeoutstr++;
    						qe.queuetimeout = atoi(queuetimeoutstr);
    					} else {
    						qe.queuetimeout = 0;
    
    
    	/* Get the priority from the variable ${QUEUE_PRIO} */
    	user_priority = pbx_builtin_getvar_helper(chan, "QUEUE_PRIO");
    	if (user_priority) {
    		if (sscanf(user_priority, "%d", &prio) == 1) {
    			if (option_debug)
    				ast_log(LOG_DEBUG, "%s: Got priority %d from ${QUEUE_PRIO}.\n",
    								chan->name, prio);
    		} else {
    			ast_log(LOG_WARNING, "${QUEUE_PRIO}: Invalid value (%s), channel %s.\n",
    							user_priority, chan->name);
    			prio = 0;
    		}
    	} else {
    		if (option_debug)
    			ast_log(LOG_DEBUG, "NO QUEUE_PRIO variable found. Using default.\n");
    		prio = 0;
    
    	if (options) {
    		if (strchr(options, 'r')) {
    			ringing = 1;
    
    //	if (option_debug) 
    		ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s, timeout: %d, priority: %d\n",
    				queuename, options, url, announceoverride, qe.queuetimeout, (int)prio);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	qe.chan = chan;
    	qe.start = time(NULL);
    
    	qe.last_pos_said = 0;
    	qe.last_pos = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!join_queue(queuename, &qe)) {
    
    		ast_queue_log(queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", url ? url : "", chan->callerid ? chan->callerid : "");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Start music on hold */
    
    		if (ringing) {
    			ast_indicate(chan, AST_CONTROL_RINGING);
    		} else {              
    			ast_moh_start(chan, qe.moh);
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		for (;;) {
    
    			/* This is the wait loop for callers 2 through maxlen */
    
    
    			res = wait_our_turn(&qe, ringing);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* If they hungup, return immediately */
    			if (res < 0) {
    
    				/* Record this abandoned call */
    				record_abandoned(&qe);
    
    				ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (option_verbose > 2) {
    					ast_verbose(VERBOSE_PREFIX_3 "User disconnected while waiting their turn\n");
    					res = -1;
    				}
    				break;
    			}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    
    			if (valid_exit(&qe, res)) {
    				ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    		if (!res) {
    			for (;;) {
    
    				/* This is the wait loop for the head caller*/
    				/* To exit, they may get their call answered; */
    				/* they may dial a digit from the queue context; */
    				/* or, they may may timeout. */
    
    				/* Leave if we have exceeded our queuetimeout */
    				if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) {
    					res = 0;
    					break;
    				}
    
    				/* Make a position announcement, if enabled */
    
    				if (qe.parent->announcefrequency && !ringing)
    
    					say_position(&qe);
    
    				/* Try calling all queue members for 'timeout' seconds */
    
    				res = try_calling(&qe, options, announceoverride, url, &go_on);
    
    				if (res) {
    					if (res < 0) {
    						if (!qe.handled)
    							ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
    					} else if (res > 0)
    						ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					break;
    
    
    				/* Leave if we have exceeded our queuetimeout */
    				if (qe.queuetimeout && ( (time(NULL) - qe.start) >= qe.queuetimeout) ) {
    					res = 0;
    					break;
    				}
    
    				/* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
    
    Mark Spencer's avatar
    Mark Spencer committed
    				res = wait_a_bit(&qe);
    				if (res < 0) {
    
    					ast_queue_log(queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld", qe.pos, qe.opos, (long)time(NULL) - qe.start);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					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)) {
    					ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					break;
    
    				/* exit after 'timeout' cycle if 'n' option enabled */
    
    				if (go_on) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    					if (option_verbose > 2) {
    						ast_verbose(VERBOSE_PREFIX_3 "Exiting on time-out cycle\n");
    						res = -1;
    					}
    
    					ast_queue_log(queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
    
    					res = 0;
    					break;
    				}
    
    				/* Since this is a priority queue and 
    				 * it is not sure that we are still at the head
    				 * of the queue, go and check for our turn again.
    				 */
    				if (!is_our_turn(&qe)) {
    					ast_log(LOG_DEBUG, "Darn priorities, going back in queue (%s)!\n",
    								qe.chan->name);
    					goto check_turns;
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    		}
    		/* Don't allow return code > 0 */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (res > 0 && res != AST_PBX_KEEPALIVE) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			res = 0;	
    
    			if (ringing) {
    				ast_indicate(chan, -1);
    			} else {
    				ast_moh_stop(chan);
    			}			
    
    			ast_stopstream(chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		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;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Mark all queues as dead for the moment */
    	q = queues;
    	while(q) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		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));
    
    					strncpy(q->name, cat, sizeof(q->name) - 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					new = 1;
    				} else new = 0;
    			} else
    					new = 0;
    			if (q) {
    				if (!new) 
    
    Mark Spencer's avatar
    Mark Spencer committed
    				/* Re-initialize the queue */
    				q->dead = 0;
    				q->retry = 0;
    				q->timeout = -1;
    				q->maxlen = 0;
    
    				q->announcefrequency = 0;
    				q->announceholdtime = 0;
    
    				q->roundingseconds = 0; /* Default - don't announce seconds */
    
    				q->holdtime = 0;
    				q->callscompleted = 0;
    				q->callsabandoned = 0;
    				q->callscompletedinsl = 0;
    				q->servicelevel = 0;
    
    				q->wrapuptime = 0;
    
    				free_members(q, 0);
    
    				q->moh[0] = '\0';
    				q->announce[0] = '\0';
    				q->context[0] = '\0';
    				q->monfmt[0] = '\0';
    				strncpy(q->sound_next, "queue-youarenext", sizeof(q->sound_next) - 1);
    				strncpy(q->sound_thereare, "queue-thereare", sizeof(q->sound_thereare) - 1);
    				strncpy(q->sound_calls, "queue-callswaiting", sizeof(q->sound_calls) - 1);
    				strncpy(q->sound_holdtime, "queue-holdtime", sizeof(q->sound_holdtime) - 1);
    				strncpy(q->sound_minutes, "queue-minutes", sizeof(q->sound_minutes) - 1);
    				strncpy(q->sound_seconds, "queue-seconds", sizeof(q->sound_seconds) - 1);
    				strncpy(q->sound_thanks, "queue-thankyou", sizeof(q->sound_thanks) - 1);
    
    				prev = q->members;
    				if (prev) {
    					/* find the end of any dynamic members */
    					while(prev->next)
    						prev = prev->next;
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				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;
    							}
    
    Mark Spencer's avatar
    Mark Spencer committed
    							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';
    
    Mark Spencer's avatar
    Mark Spencer committed
    							} 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, "monitor-join")) {
    						q->monjoin = ast_true(var->value);
    
    					} else if (!strcasecmp(var->name, "monitor-format")) {
    						strncpy(q->monfmt, var->value, sizeof(q->monfmt) - 1);
    					} else if (!strcasecmp(var->name, "queue-youarenext")) {
    						strncpy(q->sound_next, var->value, sizeof(q->sound_next) - 1);
    					} else if (!strcasecmp(var->name, "queue-thereare")) {
    						strncpy(q->sound_thereare, var->value, sizeof(q->sound_thereare) - 1);
    					} else if (!strcasecmp(var->name, "queue-callswaiting")) {
    						strncpy(q->sound_calls, var->value, sizeof(q->sound_calls) - 1);
    					} else if (!strcasecmp(var->name, "queue-holdtime")) {
    						strncpy(q->sound_holdtime, var->value, sizeof(q->sound_holdtime) - 1);
    					} else if (!strcasecmp(var->name, "queue-minutes")) {
    						strncpy(q->sound_minutes, var->value, sizeof(q->sound_minutes) - 1);
    
    					} else if (!strcasecmp(var->name, "queue-seconds")) {
    						strncpy(q->sound_seconds, var->value, sizeof(q->sound_seconds) - 1);
    
    					} else if (!strcasecmp(var->name, "queue-thankyou")) {
    						strncpy(q->sound_thanks, var->value, sizeof(q->sound_thanks) - 1);
    					} else if (!strcasecmp(var->name, "announce-frequency")) {
    						q->announcefrequency = atoi(var->value);
    
    					} else if (!strcasecmp(var->name, "announce-round-seconds")) {
    						q->roundingseconds = atoi(var->value);
    						if(q->roundingseconds>60 || q->roundingseconds<0) {
    							ast_log(LOG_WARNING, "'%s' isn't a valid value for queue-rounding-seconds using 0 instead at line %d of queue.conf\n", var->value, var->lineno);
    							q->roundingseconds=0;
    						}
    
    					} else if (!strcasecmp(var->name, "announce-holdtime")) {
    						q->announceholdtime = (!strcasecmp(var->value,"once")) ? 1 : ast_true(var->value);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					} else if (!strcasecmp(var->name, "retry")) {
    						q->retry = atoi(var->value);
    
    					} else if (!strcasecmp(var->name, "wrapuptime")) {
    						q->wrapuptime = atoi(var->value);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					} else if (!strcasecmp(var->name, "maxlen")) {
    						q->maxlen = atoi(var->value);
    
    					} else if (!strcasecmp(var->name, "servicelevel")) {
    						q->servicelevel= 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 if (!strcasecmp(var->name, "joinempty")) {
    						q->joinempty = ast_true(var->value);
    
    					} else if (!strcasecmp(var->name, "eventwhencalled")) {
    						q->eventwhencalled = ast_true(var->value);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					} 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) 
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (new) {
    					q->next = queues;
    					queues = q;
    				}
    			}
    		}
    		cat = ast_category_browse(cfg, cat);
    	}
    
    	ast_destroy(cfg);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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 little memory :( XXX\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else
    			ql = q;
    		q = qn;
    	}
    
    static int __queues_show(int fd, int argc, char **argv, int queue_show)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct ast_call_queue *q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct queue_ent *qe;
    	struct member *mem;
    	int pos;
    	time_t now;
    
    	char max[80] = "";
    	char calls[80] = "";
    
    Mark Spencer's avatar
    Mark Spencer committed
    	time(&now);
    
    	if ((!queue_show && argc != 2) || (queue_show && argc != 3))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return RESULT_SHOWUSAGE;
    
    	ast_mutex_lock(&qlock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	q = queues;
    	if (!q) {	
    
    		ast_mutex_unlock(&qlock);
    
    		if (queue_show)
    			ast_cli(fd, "No such queue: %s.\n",argv[2]);
    		else
    			ast_cli(fd, "No queues.\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return RESULT_SUCCESS;
    	}
    	while(q) {
    
    		if (queue_show) {
    			if (strcasecmp(q->name, argv[2]) != 0) {
    				ast_mutex_unlock(&q->lock);
    
    				if (!q) {
    					ast_cli(fd, "No such queue: %s.\n",argv[2]);
    					break;
    				}
    				continue;
    			}
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (q->maxlen)
    			snprintf(max, sizeof(max), "%d", q->maxlen);
    		else
    
    			strncpy(max, "unlimited", sizeof(max) - 1);
    
    		sl = 0;
    		if(q->callscompleted > 0)
    			sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted);
    		ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), C:%d, A:%d, SL:%2.1f%% within %ds\n",
    			q->name, q->count, max, int2strat(q->strategy), q->holdtime, q->callscompleted, q->callsabandoned,sl,q->servicelevel);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (q->members) {
    			ast_cli(fd, "   Members: \n");
    
    			for (mem = q->members; mem; mem = mem->next) {
    				if (mem->penalty)
    
    Mark Spencer's avatar
    Mark Spencer committed
    					snprintf(max, sizeof(max) - 20, " with penalty %d", mem->penalty);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (mem->dynamic)
    
    					strncat(max, " (dynamic)", sizeof(max) - strlen(max) - 1);
    
    				if (mem->calls) {
    					snprintf(calls, sizeof(calls), " has taken %d calls (last was %ld secs ago)",
    
    							mem->calls, (long)(time(NULL) - mem->lastcall));
    
    					strncpy(calls, " has taken no calls yet", sizeof(calls) - 1);
    
    				ast_cli(fd, "      %s/%s%s%s\n", mem->tech, mem->loc, max, calls);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} 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, prio: %d)\n", pos++, qe->chan->name,
    								(long)(now - qe->start) / 60, (long)(now - qe->start) % 60, qe->prio);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else
    			ast_cli(fd, "   No Callers\n");
    		ast_cli(fd, "\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		q = q->next;
    
    		if (queue_show)
    			break;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	ast_mutex_unlock(&qlock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return RESULT_SUCCESS;
    }
    
    
    static int queues_show(int fd, int argc, char **argv)
    {
    	return __queues_show(fd, argc, argv, 0);
    }
    
    static int queue_show(int fd, int argc, char **argv)
    {
    	return __queues_show(fd, argc, argv, 1);
    }
    
    static char *complete_queue(char *line, char *word, int pos, int state)
    {
    	struct ast_call_queue *q;
    	int which=0;
    	
    	ast_mutex_lock(&qlock);
    	q = queues;
    	while(q) {
    		if (!strncasecmp(word, q->name, strlen(word))) {
    			if (++which > state)
    				break;
    		}
    		q = q->next;
    	}
    	ast_mutex_unlock(&qlock);