Skip to content
Snippets Groups Projects
app_queue.c 86.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    	/* Parse our arguments XXX Check for failure XXX */
    
    	strncpy(info, (char *) data, sizeof(info) - 1);
    	queuename = strsep(&info_ptr, "|");
    	options = strsep(&info_ptr, "|");
    	url = strsep(&info_ptr, "|");
    	announceoverride = strsep(&info_ptr, "|");
    	queuetimeoutstr = info_ptr;
    
    	/* set the expire time based on the supplied timeout; */
    	if (queuetimeoutstr)
    		qe.expire = qe.start + atoi(queuetimeoutstr);
    	else
    		qe.expire = 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 > 2)
    
    			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, expires: %ld, priority: %d\n",
    				queuename, options, url, announceoverride, (long)qe.expire, (int)prio);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	qe.chan = chan;
    
    	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->cid.cid_num ? chan->cid.cid_num : "");
    
    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 from queue %s while waiting their turn\n", queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					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) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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; */
    
    
    				/* Leave if we have exceeded our queuetimeout */
    
    				if (qe.expire && (time(NULL) > qe.expire)) {
    
    				if (makeannouncement) {
    					/* 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 the queue if no agents, if enabled */
    
    				if (ast_test_flag(qe.parent, QUEUE_FLAG_LEAVEWHENEMPTY) && has_no_members(qe.parent)) {
    
    				/* Leave if we have exceeded our queuetimeout */
    
    				if (qe.expire && (time(NULL) > qe.expire)) {
    
    					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 from queue %s when they almost made it\n", queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    						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)) {
    
    					if (option_debug)
    						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 */
    
    		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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	cfg = ast_load("queues.conf");
    	if (!cfg) {
    
    		ast_log(LOG_NOTICE, "No call queueing config file (queues.conf), so no call queues\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return;
    	}
    
    	use_weight=0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Mark all queues as dead for the moment */
    	q = queues;
    	while(q) {
    
    		ast_set_flag(q, QUEUE_FLAG_DEAD);
    
    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")) {	/* Define queue */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* 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 */
    
    				ast_clear_flag(q, QUEUE_FLAG_DEAD);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				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);
    
    				strncpy(q->sound_lessthan, "queue-less-than", sizeof(q->sound_lessthan) - 1);
    
    				strncpy(q->sound_reporthold, "queue-reporthold", sizeof(q->sound_reporthold) - 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->interface, var->value, sizeof(cur->interface) - 1);
    							if ((tmp = strchr(cur->interface, ','))) {
    
    								*tmp = '\0';
    								tmp++;
    								cur->penalty = atoi(tmp);
    								if (cur->penalty < 0)
    									cur->penalty = 0;
    							}
    
    							if (!strchr(cur->interface, '/'))
    
    Mark Spencer's avatar
    Mark Spencer committed
    								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") || !strcasecmp(var->name, "musiconhold")) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    						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")) {
    
    						ast_set2_flag(q, ast_true(var->value), QUEUE_FLAG_MONJOIN);
    
    					} 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-lessthan")) {
    						strncpy(q->sound_lessthan, var->value, sizeof(q->sound_lessthan) - 1);
    
    					} else if (!strcasecmp(var->name, "queue-thankyou")) {
    						strncpy(q->sound_thanks, var->value, sizeof(q->sound_thanks) - 1);
    
    					} else if (!strcasecmp(var->name, "queue-reporthold")) {
    						strncpy(q->sound_reporthold, var->value, sizeof(q->sound_reporthold) - 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")) {
    
    						ast_set2_flag(q, ast_true(var->value), QUEUE_FLAG_JOINEMPTY);
    
    					} else if (!strcasecmp(var->name, "leavewhenempty")) {
    
    						ast_set2_flag(q, ast_true(var->value), QUEUE_FLAG_LEAVEWHENEMPTY);
    
    					} else if (!strcasecmp(var->name, "eventwhencalled")) {
    
    						ast_set2_flag(q, ast_true(var->value), QUEUE_FLAG_EVENTWHENCALLED);
    
    					} else if (!strcasecmp(var->name, "reportholdtime")) {
    
    						ast_set2_flag(q, ast_true(var->value), QUEUE_FLAG_REPORTHOLDTIME);
    
    					} else if (!strcasecmp(var->name, "memberdelay")) {
    						q->memberdelay = atoi(var->value);
    
    					} else if (!strcasecmp(var->name, "weight")) {
    						q->weight = atoi(var->value);
    						if (q->weight)
    							use_weight++;
    
    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;
    				}
    			}
    
    			/* Initialize global settings */
    			queue_persistent_members = 0;
    			if ((general_val = ast_variable_retrieve(cfg, "general", "persistentmembers")))
    			    queue_persistent_members = ast_true(general_val);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    		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 (ast_test_flag(q, QUEUE_FLAG_DEAD)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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 {
    
    			for (cur = q->members; cur; cur = cur->next)
    				cur->status = ast_device_state(cur->interface);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ql = q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		q = qn;
    	}
    
    static char *status2str(int status, char *buf, int buflen)
    {
    	switch(status) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	case AST_DEVICE_UNKNOWN:
    		strncpy(buf, "unknown", buflen - 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	case AST_DEVICE_NOT_INUSE:
    		strncpy(buf, "notinuse", buflen - 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	case AST_DEVICE_INUSE:
    		strncpy(buf, "inuse", buflen - 1);
    		break;
    	case AST_DEVICE_BUSY:
    		strncpy(buf, "busy", buflen - 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	case AST_DEVICE_INVALID:
    		strncpy(buf, "invalid", buflen - 1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	case AST_DEVICE_UNAVAILABLE:
    		strncpy(buf, "unavailable", buflen - 1);
    
    		break;
    	default:
    		snprintf(buf, buflen, "unknown status %d", status);
    	}
    	return buf;
    }
    
    
    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), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds\n",
    			q->name, q->count, max, int2strat(q->strategy), q->holdtime, q->weight, 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->status)
    					snprintf(max + strlen(max), sizeof(max) - strlen(max), " (%s)", status2str(mem->status, tmpbuf, sizeof(tmpbuf)));
    
    				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\n", mem->interface, 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);
    
    	for (q = queues; q; q = q->next) {
    
    		if (!strncasecmp(word, q->name, strlen(word))) {
    			if (++which > state)
    				break;
    		}
    	}
    	ast_mutex_unlock(&qlock);
    	return q ? strdup(q->name) : NULL;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    /* 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);
    } 
    
    
    /* 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;
    
    	float sl = 0;
    	struct member *mem;
    
    	astman_send_ack(s, m, "Queue status will follow");
    
    	time(&now);
    
    	ast_mutex_lock(&qlock);
    
    	if (!ast_strlen_zero(id)) {
    
    		snprintf(idText,256,"ActionID: %s\r\n",id);
    	}
    
    	for (q = queues; q; q = q->next) {
    
    
    		/* List queue properties */
    		if(q->callscompleted > 0)
    			sl = 100*((float)q->callscompletedinsl/(float)q->callscompleted);
    
    		ast_mutex_lock(&s->lock);
    
    		ast_cli(s->fd, "Event: QueueParams\r\n"
    					"Queue: %s\r\n"
    					"Max: %d\r\n"
    					"Calls: %d\r\n"
    
    					"Holdtime: %d\r\n"
    					"Completed: %d\r\n"
    					"Abandoned: %d\r\n"
    					"ServiceLevel: %d\r\n"
    					"ServicelevelPerf: %2.1f\r\n"
    
    					"Weight: %d\r\n"
    
    					"\r\n",
    
    						q->name, q->maxlen, q->count, q->holdtime, q->callscompleted,
    
    						q->callsabandoned, q->servicelevel, sl, q->weight, idText);
    
    
    		/* List Queue Members */
    
    		for (mem = q->members; mem; mem = mem->next) 
    
    			ast_cli(s->fd, "Event: QueueMember\r\n"
    				"Queue: %s\r\n"
    
    				"Membership: %s\r\n"
    				"Penalty: %d\r\n"
    				"CallsTaken: %d\r\n"
    				"LastCall: %ld\r\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    				"Status: %d\r\n"
    
    					q->name, mem->interface, mem->dynamic ? "dynamic" : "static",
    
    Mark Spencer's avatar
    Mark Spencer committed
    					mem->penalty, mem->calls, mem->lastcall, mem->status, idText);
    
    
    		/* List Queue Entries */
    
    
    		pos = 1;
    		for (qe = q->head; qe; qe = qe->next) 
    
    			ast_cli(s->fd, "Event: QueueEntry\r\n"
    
    				"Queue: %s\r\n"
    				"Position: %d\r\n"
    				"Channel: %s\r\n"
    				"CallerID: %s\r\n"
    
    				"CallerIDName: %s\r\n"
    
    				"Wait: %ld\r\n"
    
    				"\r\n", 
    
    					q->name, pos++, qe->chan->name, 
    					qe->chan->cid.cid_num ? qe->chan->cid.cid_num : "unknown",
    					qe->chan->cid.cid_name ? qe->chan->cid.cid_name : "unknown",
    					(long)(now - qe->start), idText);
    
    		ast_mutex_unlock(&s->lock);
    
    	ast_mutex_unlock(&qlock);
    
    	return RESULT_SUCCESS;
    }
    
    
    static int manager_add_queue_member(struct mansession *s, struct message *m)
    {
    	char *queuename, *interface, *penalty_s;
    	int penalty = 0;
    
    	queuename = astman_get_header(m, "Queue");
    	interface = astman_get_header(m, "Interface");
    	penalty_s = astman_get_header(m, "Penalty");
    
    	if (ast_strlen_zero(queuename)) {
    		astman_send_error(s, m, "'Queue' not specified.");
    		return 0;
    	}
    
    	if (ast_strlen_zero(interface)) {
    		astman_send_error(s, m, "'Interface' not specified.");
    		return 0;
    	}
    
    	if (ast_strlen_zero(penalty_s))
    		penalty = 0;
    	else if (sscanf(penalty_s, "%d", &penalty) != 1) {
    		penalty = 0;
    	}
    
    	switch (add_to_queue(queuename, interface, penalty)) {
    	case RES_OKAY:
    		astman_send_ack(s, m, "Added interface to queue");
    		break;
    	case RES_EXISTS:
    		astman_send_error(s, m, "Unable to add interface: Already there");
    		break;
    	case RES_NOSUCHQUEUE:
    		astman_send_error(s, m, "Unable to add interface to queue: No such queue");
    		break;
    	case RES_OUTOFMEMORY:
    		astman_send_error(s, m, "Out of memory");
    		break;
    	}
    	return 0;
    }
    
    static int manager_remove_queue_member(struct mansession *s, struct message *m)
    {
    	char *queuename, *interface;
    
    	queuename = astman_get_header(m, "Queue");
    	interface = astman_get_header(m, "Interface");
    
    	if (ast_strlen_zero(queuename) || ast_strlen_zero(interface)) {
    		astman_send_error(s, m, "Need 'Queue' and 'Interface' parameters.");
    		return 0;
    	}
    
    	switch (remove_from_queue(queuename, interface)) {
    	case RES_OKAY:
    		astman_send_ack(s, m, "Removed interface from queue");
    		break;
    	case RES_EXISTS:
    		astman_send_error(s, m, "Unable to remove interface: Not there");
    		break;
    	case RES_NOSUCHQUEUE:
    		astman_send_error(s, m, "Unable to remove interface from queue: No such queue");
    		break;
    	case RES_OUTOFMEMORY:
    		astman_send_error(s, m, "Out of memory");
    		break;
    	}
    	return 0;
    }
    
    static int handle_add_queue_member(int fd, int argc, char *argv[])
    {
    	char *queuename, *interface;
    	int penalty;
    
    	if ((argc != 6) && (argc != 8)) {
    		return RESULT_SHOWUSAGE;
    	} else if (strcmp(argv[4], "to")) {
    		return RESULT_SHOWUSAGE;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if ((argc == 8) && strcmp(argv[6], "penalty")) {
    
    		return RESULT_SHOWUSAGE;
    	}
    
    	queuename = argv[5];
    	interface = argv[3];
    	if (argc == 8) {
    		if (sscanf(argv[7], "%d", &penalty) == 1) {
    			if (penalty < 0) {
    				ast_cli(fd, "Penalty must be >= 0\n");
    				penalty = 0;
    			}
    		} else {
    			ast_cli(fd, "Penalty must be an integer >= 0\n");
    			penalty = 0;
    		}
    	} else {
    		penalty = 0;
    	}
    
    	switch (add_to_queue(queuename, interface, penalty)) {
    	case RES_OKAY:
    		ast_cli(fd, "Added interface '%s' to queue '%s'\n", interface, queuename);
    		return RESULT_SUCCESS;
    	case RES_EXISTS:
    		ast_cli(fd, "Unable to add interface '%s' to queue '%s': Already there\n", interface, queuename);
    		return RESULT_FAILURE;
    	case RES_NOSUCHQUEUE:
    		ast_cli(fd, "Unable to add interface to queue '%s': No such queue\n", queuename);
    		return RESULT_FAILURE;
    	case RES_OUTOFMEMORY:
    		ast_cli(fd, "Out of memory\n");
    		return RESULT_FAILURE;
    	default:
    		return RESULT_FAILURE;
    	}
    }
    
    static char *complete_add_queue_member(char *line, char *word, int pos, int state)
    {
    	/* 0 - add; 1 - queue; 2 - member; 3 - <member>; 4 - to; 5 - <queue>; 6 - penalty; 7 - <penalty> */
    	switch (pos) {
    	case 3:
    		/* Don't attempt to complete name of member (infinite possibilities) */
    		return NULL;
    	case 4:
    		if (state == 0) {
    			return strdup("to");
    		} else {
    			return NULL;
    		}
    	case 5:
    		/* No need to duplicate code */
    		return complete_queue(line, word, pos, state);
    	case 6:
    		if (state == 0) {
    			return strdup("penalty");
    		} else {
    			return NULL;
    		}
    	case 7:
    		if (state < 100) {	/* 0-99 */
    			char *num = malloc(3);
    			if (num) {
    				sprintf(num, "%d", state);
    			}
    			return num;
    		} else {
    			return NULL;
    		}
    	default:
    		return NULL;
    	}
    }
    
    static int handle_remove_queue_member(int fd, int argc, char *argv[])
    {
    	char *queuename, *interface;
    
    	if (argc != 6) {
    		return RESULT_SHOWUSAGE;
    	} else if (strcmp(argv[4], "from")) {
    		return RESULT_SHOWUSAGE;
    	}
    
    	queuename = argv[5];
    	interface = argv[3];
    
    	switch (remove_from_queue(queuename, interface)) {
    	case RES_OKAY:
    		ast_cli(fd, "Removed interface '%s' from queue '%s'\n", interface, queuename);
    		return RESULT_SUCCESS;
    	case RES_EXISTS:
    		ast_cli(fd, "Unable to remove interface '%s' from queue '%s': Not there\n", interface, queuename);
    		return RESULT_FAILURE;
    	case RES_NOSUCHQUEUE:
    		ast_cli(fd, "Unable to remove interface from queue '%s': No such queue\n", queuename);
    		return RESULT_FAILURE;
    	case RES_OUTOFMEMORY:
    		ast_cli(fd, "Out of memory\n");
    		return RESULT_FAILURE;
    	default:
    		return RESULT_FAILURE;
    	}
    }
    
    static char *complete_remove_queue_member(char *line, char *word, int pos, int state)
    {
    	int which = 0;
    	struct ast_call_queue *q;
    	struct member *m;
    
    	/* 0 - add; 1 - queue; 2 - member; 3 - <member>; 4 - to; 5 - <queue> */
    	if ((pos > 5) || (pos < 3)) {
    		return NULL;
    	}
    	if (pos == 4) {
    		if (state == 0) {
    			return strdup("from");
    		} else {
    			return NULL;
    		}
    	}
    
    	if (pos == 5) {
    		/* No need to duplicate code */
    		return complete_queue(line, word, pos, state);
    	}
    
    	if (queues != NULL) {
    		for (q = queues ; q ; q = q->next) {
    			ast_mutex_lock(&q->lock);
    			for (m = q->members ; m ; m = m->next) {
    				if (++which > state) {
    					ast_mutex_unlock(&q->lock);
    
    					return strdup(m->interface);
    
    Mark Spencer's avatar
    Mark Spencer committed
    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 };
    
    
    static char show_queue_usage[] = 
    "Usage: show queue\n"
    "       Provides summary information on a specified queue.\n";
    
    static struct ast_cli_entry cli_show_queue = {
    	{ "show", "queue", NULL }, queue_show, 
    	"Show status of a specified queue", show_queue_usage, complete_queue };
    
    
    static char aqm_cmd_usage[] =
    "Usage: add queue member <channel> to <queue> [penalty <penalty>]\n";
    
    static struct ast_cli_entry cli_add_queue_member = {
    	{ "add", "queue", "member", NULL }, handle_add_queue_member,
    	"Add a channel to a specified queue", aqm_cmd_usage, complete_add_queue_member };
    
    static char rqm_cmd_usage[] =
    "Usage: remove queue member <channel> from <queue>\n";
    
    static struct ast_cli_entry cli_remove_queue_member = {
    	{ "remove", "queue", "member", NULL }, handle_remove_queue_member,
    	"Removes a channel from a specified queue", rqm_cmd_usage, complete_remove_queue_member };
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    int unload_module(void)
    {
    	STANDARD_HANGUP_LOCALUSERS;
    
    	ast_cli_unregister(&cli_show_queue);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	ast_cli_unregister(&cli_show_queues);
    
    	ast_cli_unregister(&cli_add_queue_member);
    	ast_cli_unregister(&cli_remove_queue_member);
    	ast_manager_unregister("Queues");
    	ast_manager_unregister("QueueStatus");
    	ast_manager_unregister("QueueAdd");
    	ast_manager_unregister("QueueRemove");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	ast_devstate_del(statechange_queue, NULL);
    
    	ast_unregister_application(app_aqm);
    	ast_unregister_application(app_rqm);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return ast_unregister_application(app);
    }
    
    int load_module(void)
    {
    	int res;
    	res = ast_register_application(app, queue_exec, synopsis, descrip);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!res) {
    
    		ast_cli_register(&cli_show_queue);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_cli_register(&cli_show_queues);
    
    		ast_cli_register(&cli_add_queue_member);
    		ast_cli_register(&cli_remove_queue_member);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_devstate_add(statechange_queue, NULL);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		ast_manager_register( "Queues", 0, manager_queues_show, "Queues" );
    
    		ast_manager_register( "QueueStatus", 0, manager_queues_status, "Queue Status" );
    
    		ast_manager_register( "QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member, "Add interface to queue." );
    		ast_manager_register( "QueueRemove", EVENT_FLAG_AGENT, manager_remove_queue_member, "Remove interface from queue." );
    
    		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) ;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	reload_queues();
    
    	
    	if (queue_persistent_members)
    	    reload_queue_members();
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    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;
    }