Skip to content
Snippets Groups Projects
app_queue.c 131 KiB
Newer Older
  • Learn to ignore specific revisions
  • 		AST_APP_ARG(options);
    	);
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "UnpauseQueueMember requires an argument ([queuename]|interface[|options])\n");
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    
    	lu = ast_module_user_add(chan);
    
    	if (args.options) {
    		if (strchr(args.options, 'j'))
    			priority_jump = 1;
    	}
    
    	if (ast_strlen_zero(args.interface)) {
    		ast_log(LOG_WARNING, "Missing interface argument to PauseQueueMember ([queuename]|interface[|options])\n");
    
    		ast_module_user_remove(lu);
    
    	if (set_member_paused(args.queuename, args.interface, 0)) {
    		ast_log(LOG_WARNING, "Attempt to unpause interface %s, not found\n", args.interface);
    
    		if (priority_jump || ast_opt_priority_jumping) {
    
    			if (ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101)) {
    				pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND");
    
    				ast_module_user_remove(lu);
    
    		ast_module_user_remove(lu);
    
    		pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND");
    
    	ast_module_user_remove(lu);
    
    	pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "UNPAUSED");
    
    static int rqm_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    	struct ast_module_user *lu;
    
    	char *parse, *temppos = NULL;
    	int priority_jump = 0;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
    		AST_APP_ARG(options);
    	);
    
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "RemoveQueueMember requires an argument (queuename[|interface[|options]])\n");
    
    		return -1;
    	}
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    
    	lu = ast_module_user_add(chan);
    
    	if (ast_strlen_zero(args.interface)) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		args.interface = ast_strdupa(chan->name);
    
    		temppos = strrchr(args.interface, '-');
    		if (temppos)
    			*temppos = '\0';
    
    	if (args.options) {
    		if (strchr(args.options, 'j'))
    			priority_jump = 1;
    	}
    
    	switch (remove_from_queue(args.queuename, args.interface)) {
    
    		ast_log(LOG_DEBUG, "Removed interface '%s' from queue '%s'\n", args.interface, args.queuename);
    
    		pbx_builtin_setvar_helper(chan, "RQMSTATUS", "REMOVED");
    
    		ast_log(LOG_DEBUG, "Unable to remove interface '%s' from queue '%s': Not there\n", args.interface, args.queuename);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (priority_jump || ast_opt_priority_jumping)
    
    			ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
    		pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOTINQUEUE");
    
    		res = 0;
    		break;
    	case RES_NOSUCHQUEUE:
    
    		ast_log(LOG_WARNING, "Unable to remove interface from queue '%s': No such queue\n", args.queuename);
    		pbx_builtin_setvar_helper(chan, "RQMSTATUS", "NOSUCHQUEUE");
    
    	ast_module_user_remove(lu);
    
    	return res;
    }
    
    static int aqm_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    	struct ast_module_user *lu;
    
    	char *parse, *temppos = NULL;
    	int priority_jump = 0;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
    		AST_APP_ARG(penalty);
    		AST_APP_ARG(options);
    	);
    
    	int penalty = 0;
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "AddQueueMember requires an argument (queuename[|[interface]|[penalty][|options]])\n");
    
    		return -1;
    	}
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    
    	lu = ast_module_user_add(chan);
    
    	if (ast_strlen_zero(args.interface)) {
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    		args.interface = ast_strdupa(chan->name);
    
    		temppos = strrchr(args.interface, '-');
    		if (temppos)
    			*temppos = '\0';
    	}
    
    	if (!ast_strlen_zero(args.penalty)) {
    		if ((sscanf(args.penalty, "%d", &penalty) != 1) || penalty < 0) {
    			ast_log(LOG_WARNING, "Penalty '%s' is invalid, must be an integer >= 0\n", args.penalty);
    			penalty = 0;
    
    	
    	if (args.options) {
    		if (strchr(args.options, 'j'))
    			priority_jump = 1;
    	}
    
    
    	switch (add_to_queue(args.queuename, args.interface, penalty, 0, queue_persistent_members)) {
    
    		ast_log(LOG_NOTICE, "Added interface '%s' to queue '%s'\n", args.interface, args.queuename);
    		pbx_builtin_setvar_helper(chan, "AQMSTATUS", "ADDED");
    
    		ast_log(LOG_WARNING, "Unable to add interface '%s' to queue '%s': Already there\n", args.interface, args.queuename);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (priority_jump || ast_opt_priority_jumping)
    
    			ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
    		pbx_builtin_setvar_helper(chan, "AQMSTATUS", "MEMBERALREADY");
    
    		res = 0;
    		break;
    	case RES_NOSUCHQUEUE:
    
    		ast_log(LOG_WARNING, "Unable to add interface to queue '%s': No such queue\n", args.queuename);
    		pbx_builtin_setvar_helper(chan, "AQMSTATUS", "NOSUCHQUEUE");
    
    		res = 0;
    		break;
    	case RES_OUTOFMEMORY:
    
    		ast_log(LOG_ERROR, "Out of memory adding member %s to queue %s\n", args.interface, args.queuename);
    
    	ast_module_user_remove(lu);
    
    static int ql_exec(struct ast_channel *chan, void *data)
    {
    
    	char *parse;
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(uniqueid);
    		AST_APP_ARG(peer);
    		AST_APP_ARG(event);
    		AST_APP_ARG(params);
    	);
    
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|peer|event[|additionalinfo]\n");
    		return -1;
    	}
    
    
    	u = ast_module_user_add(chan);
    
    
    	parse = ast_strdupa(data);
    
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	if (ast_strlen_zero(args.queuename) || ast_strlen_zero(args.uniqueid)
    	    || ast_strlen_zero(args.peer) || ast_strlen_zero(args.event)) {
    		ast_log(LOG_WARNING, "QueueLog requires arguments (queuename|uniqueid|peer|event[|additionalinfo])\n");
    
    		return -1;
    	}
    
    	ast_queue_log(args.queuename, args.uniqueid, args.peer, args.event, 
    		"%s", args.params ? args.params : "");
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int queue_exec(struct ast_channel *chan, void *data)
    {
    	int res=-1;
    
    	struct ast_module_user *lu;
    
    	/* whether to exit Queue application after the timeout hits */
    	int go_on = 0;
    
    	char *parse;
    	AST_DECLARE_APP_ARGS(args,
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(options);
    		AST_APP_ARG(url);
    		AST_APP_ARG(announceoverride);
    		AST_APP_ARG(queuetimeoutstr);
    		AST_APP_ARG(agi);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Our queue entry */
    	struct queue_ent qe;
    	
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_WARNING, "Queue requires an argument: queuename[|options[|URL][|announceoverride][|timeout]]\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return -1;
    	}
    
    	
    	parse = ast_strdupa(data);
    	AST_STANDARD_APP_ARGS(args, parse);
    
    	lu = ast_module_user_add(chan);
    
    
    	/* Setup our queue entry */
    	memset(&qe, 0, sizeof(qe));
    
    	qe.start = time(NULL);
    
    	/* set the expire time based on the supplied timeout; */
    
    	if (args.queuetimeoutstr)
    		qe.expire = qe.start + atoi(args.queuetimeoutstr);
    
    
    	/* 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",
    
    		} else {
    			ast_log(LOG_WARNING, "${QUEUE_PRIO}: Invalid value (%s), channel %s.\n",
    
    		if (option_debug > 2)
    
    			ast_log(LOG_DEBUG, "NO QUEUE_PRIO variable found. Using default.\n");
    		prio = 0;
    
    	/* Get the maximum penalty from the variable ${QUEUE_MAX_PENALTY} */
    	if ((max_penalty_str = pbx_builtin_getvar_helper(chan, "QUEUE_MAX_PENALTY"))) {
    		if (sscanf(max_penalty_str, "%d", &max_penalty) == 1) {
    			if (option_debug)
    				ast_log(LOG_DEBUG, "%s: Got max penalty %d from ${QUEUE_MAX_PENALTY}.\n",
    					chan->name, max_penalty);
    		} else {
    			ast_log(LOG_WARNING, "${QUEUE_MAX_PENALTY}: Invalid value (%s), channel %s.\n",
    				max_penalty_str, chan->name);
    			max_penalty = 0;
    		}
    	} else {
    		max_penalty = 0;
    	}
    
    
    	if (args.options && (strchr(args.options, 'r')))
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (option_debug)
    
    		ast_log(LOG_DEBUG, "queue: %s, options: %s, url: %s, announce: %s, expires: %ld, priority: %d\n",
    
    			args.queuename, args.options, args.url, args.announceoverride, (long)qe.expire, prio);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	qe.chan = chan;
    
    	qe.last_pos_said = 0;
    	qe.last_pos = 0;
    
    	qe.last_periodic_announce_time = time(NULL);
    
    	qe.last_periodic_announce_sound = 0;
    
    	if (!join_queue(args.queuename, &qe, &reason)) {
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    		ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""),
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			S_OR(chan->cid.cid_num, ""));
    
    		if (ringing) {
    			ast_indicate(chan, AST_CONTROL_RINGING);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		for (;;) {
    
    			/* This is the wait loop for callers 2 through maxlen */
    
    
    			res = wait_our_turn(&qe, ringing, &reason);
    
    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(args.queuename, chan->uniqueid, "NONE", "ABANDON", "%d|%d|%ld",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					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", args.queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    
    Olle Johansson's avatar
    Olle Johansson committed
    				res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    			}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			if (!res)
    
    Mark Spencer's avatar
    Mark Spencer committed
    				break;
    
    			if (valid_exit(&qe, res)) {
    
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%s|%d", qe.digits, 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)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    					record_abandoned(&qe);
    
    					ast_queue_log(args.queuename, chan->uniqueid,"NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
    
    				if (makeannouncement) {
    					/* Make a position announcement, if enabled */
    
    					if (qe.parent->announcefrequency && !ringing &&
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    						(res = say_position(&qe))) {
    						ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%s|%d", qe.digits, qe.pos);
    
    				/* Make a periodic announcement, if enabled */
    
    				if (qe.parent->periodicannouncefrequency && !ringing &&
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					(res = say_periodic_announcement(&qe))) {
    
    					ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%c|%d", res, qe.pos);
    
    				/* Try calling all queue members for 'timeout' seconds */
    
    				res = try_calling(&qe, args.options, args.announceoverride, args.url, &go_on, args.agi);
    
    				if (res) {
    					if (res < 0) {
    
    							ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    								"%d|%d|%ld", qe.pos, qe.opos,
    								(long) time(NULL) - qe.start);
    
    					} else if (valid_exit(&qe, res)) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    						ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY",
    							"%s|%d", qe.digits, qe.pos);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					break;
    
    				stat = get_member_status(qe.parent, qe.max_penalty);
    
    				/* leave the queue if no agents, if enabled */
    
    				if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
    
    					reason = QUEUE_LEAVEEMPTY;
    					res = 0;
    					break;
    				}
    
    				/* leave the queue if no reachable agents, if enabled */
    
    				if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
    
    				/* Leave if we have exceeded our queuetimeout */
    
    				if (qe.expire && (time(NULL) > qe.expire)) {
    
    					ast_queue_log(args.queuename, chan->uniqueid,"NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
    
    					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(args.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", args.queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					}
    
    Olle Johansson's avatar
    Olle Johansson committed
    					res = -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    					break;
    				}
    
    				if (res && valid_exit(&qe, res)) {
    
    					ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY", "%s|%d", qe.digits, qe.pos);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					break;
    
    				/* exit after 'timeout' cycle if 'n' option enabled */
    
    				if (go_on) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    					if (option_verbose > 2)
    
    Mark Spencer's avatar
    Mark Spencer committed
    						ast_verbose(VERBOSE_PREFIX_3 "Exiting on time-out cycle\n");
    
    					ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
    
    Olle Johansson's avatar
    Olle Johansson committed
    					record_abandoned(&qe);
    
    					res = 0;
    					break;
    				}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				/* 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",
    
    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);
    
    		if (reason != QUEUE_UNKNOWN)
    			set_queue_result(chan, reason);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else {
    
    		ast_log(LOG_WARNING, "Unable to join queue '%s'\n", args.queuename);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	ast_module_user_remove(lu);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int queue_function_qac(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
    
    	struct call_queue *q;
    
    	struct ast_module_user *lu;
    
    	buf[0] = '\0';
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
    
    		return -1;
    
    	lu = ast_module_user_add(chan);
    
    	AST_LIST_LOCK(&queues);
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    		if (!strcasecmp(q->name, data)) {
    			ast_mutex_lock(&q->lock);
    			break;
    		}
    	}
    
    	AST_LIST_UNLOCK(&queues);
    
    
    	if (q) {
    		for (m = q->members; m; m = m->next) {
    			/* Count the agents who are logged in and presently answering calls */
    			if ((m->status != AST_DEVICE_UNAVAILABLE) && (m->status != AST_DEVICE_INVALID)) {
    				count++;
    			}
    		}
    		ast_mutex_unlock(&q->lock);
    
    	} else
    		ast_log(LOG_WARNING, "queue %s was not found\n", data);
    
    	snprintf(buf, len, "%d", count);
    
    	ast_module_user_remove(lu);
    
    	return 0;
    }
    
    static int queue_function_queuewaitingcount(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
    {
    	int count = 0;
    
    	struct call_queue *q;
    
    	struct ast_module_user *lu;
    
    
    	buf[0] = '\0';
    	
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
    		return -1;
    	}
    
    
    	lu = ast_module_user_add(chan);
    
    	
    	AST_LIST_LOCK(&queues);
    	AST_LIST_TRAVERSE(&queues, q, list) {
    		if (!strcasecmp(q->name, data)) {
    			ast_mutex_lock(&q->lock);
    			break;
    		}
    
    	AST_LIST_UNLOCK(&queues);
    
    	if (q) {
    		count = q->count;
    		ast_mutex_unlock(&q->lock);
    	} else
    		ast_log(LOG_WARNING, "queue %s was not found\n", data);
    
    
    	ast_module_user_remove(lu);
    
    static int queue_function_queuememberlist(struct ast_channel *chan, char *cmd, char *data, char *buf, size_t len)
    
    	struct call_queue *q;
    
    	struct member *m;
    
    	/* Ensure an otherwise empty list doesn't return garbage */
    	buf[0] = '\0';
    
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_ERROR, "QUEUE_MEMBER_LIST requires an argument: queuename\n");
    
    		return -1;
    
    	u = ast_module_user_add(chan);
    
    	AST_LIST_LOCK(&queues);
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    		if (!strcasecmp(q->name, data)) {
    			ast_mutex_lock(&q->lock);
    			break;
    		}
    	}
    
    	AST_LIST_UNLOCK(&queues);
    
    
    	if (q) {
    		int buflen = 0, count = 0;
    
    		for (m = q->members; m; m = m->next) {
    			/* strcat() is always faster than printf() */
    			if (count++) {
    				strncat(buf + buflen, ",", len - buflen - 1);
    				buflen++;
    			}
    			strncat(buf + buflen, m->interface, len - buflen - 1);
    			buflen += strlen(m->interface);
    			/* Safeguard against overflow (negative length) */
    			if (buflen >= len - 2) {
    				ast_log(LOG_WARNING, "Truncating list\n");
    				break;
    			}
    		}
    		ast_mutex_unlock(&q->lock);
    
    	} else
    		ast_log(LOG_WARNING, "queue %s was not found\n", data);
    
    
    	/* We should already be terminated, but let's make sure. */
    	buf[len - 1] = '\0';
    
    static struct ast_custom_function queueagentcount_function = {
    	.name = "QUEUEAGENTCOUNT",
    	.synopsis = "Count number of agents answering a queue",
    	.syntax = "QUEUEAGENTCOUNT(<queuename>)",
    
    	.desc =
    "Returns the number of members currently associated with the specified queue.\n"
    "This function is deprecated.  You should use QUEUE_MEMBER_COUNT() instead.\n",
    
    static struct ast_custom_function queuemembercount_function = {
    	.name = "QUEUE_MEMBER_COUNT",
    	.synopsis = "Count number of members answering a queue",
    	.syntax = "QUEUE_MEMBER_COUNT(<queuename>)",
    	.desc =
    "Returns the number of members currently associated with the specified queue.\n",
    	.read = queue_function_qac,
    };
    
    
    static struct ast_custom_function queuewaitingcount_function = {
    	.name = "QUEUE_WAITING_COUNT",
    	.synopsis = "Count number of calls currently waiting in a queue",
    	.syntax = "QUEUE_WAITING_COUNT(<queuename>)",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	.desc =
    
    "Returns the number of callers currently waiting in the specified queue.\n",
    	.read = queue_function_queuewaitingcount,
    };
    
    
    static struct ast_custom_function queuememberlist_function = {
    	.name = "QUEUE_MEMBER_LIST",
    	.synopsis = "Returns a list of interfaces on a queue",
    	.syntax = "QUEUE_MEMBER_LIST(<queuename>)",
    	.desc =
    "Returns a comma-separated list of members associated with the specified queue.\n",
    	.read = queue_function_queuememberlist,
    };
    
    
    static int reload_queues(void)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct call_queue *q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_config *cfg;
    	char *cat, *tmp;
    	struct ast_variable *var;
    
    	struct member *prev, *cur, *newm;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int new;
    
    	char *general_val = NULL;
    
    	if (!(cfg = ast_config_load("queues.conf"))) {
    
    		ast_log(LOG_NOTICE, "No call queueing config file (queues.conf), so no call queues\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	memset(interface, 0, sizeof(interface));
    
    	AST_LIST_LOCK(&queues);
    
    	use_weight=0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Mark all queues as dead for the moment */
    
    	AST_LIST_TRAVERSE(&queues, q, list)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Chug through config file */
    
    	cat = NULL;
    	while ((cat = ast_category_browse(cfg, cat)) ) {
    
    		if (!strcasecmp(cat, "general")) {	
    			/* Initialize global settings */
    			queue_persistent_members = 0;
    			if ((general_val = ast_variable_retrieve(cfg, "general", "persistentmembers")))
    				queue_persistent_members = ast_true(general_val);
    
    			autofill_default = 0;
    			if ((general_val = ast_variable_retrieve(cfg, "general", "autofill")))
    				autofill_default = ast_true(general_val);
    
    			montype_default = 0;
    			if ((general_val = ast_variable_retrieve(cfg, "general", "monitor-type")))
    				if (!strcasecmp(general_val, "mixmonitor"))
    					montype_default = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Look for an existing one */
    
    			AST_LIST_TRAVERSE(&queues, q, list) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (!strcmp(q->name, cat))
    					break;
    			}
    			if (!q) {
    				/* Make one then */
    
    				if (!(q = alloc_queue(cat))) {
    					/* TODO: Handle memory allocation failure */
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			} else
    
    Mark Spencer's avatar
    Mark Spencer committed
    			if (q) {
    
    				/* Re-initialize the queue, and clear statistics */
    				init_queue(q);
    				clear_queue(q);
    
    				for (cur = q->members; cur; cur = cur->next) {
    					if (!cur->dynamic) {
    						cur->delme = 1;
    					}
    
    				for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    					if (!strcasecmp(var->name, "member")) {
    						/* Add a new member */
    
    						ast_copy_string(interface, var->value, sizeof(interface));
    						if ((tmp = strchr(interface, ','))) {
    							*tmp = '\0';
    							tmp++;
    							penalty = atoi(tmp);
    							if (penalty < 0) {
    								penalty = 0;
    
    
    						/* Find the old position in the list */
    						for (prev = NULL, cur = q->members; cur; prev = cur, cur = cur->next) {
    							if (!strcmp(cur->interface, interface)) {
    								break;
    							}
    						}
    
    						newm = create_queue_member(interface, penalty, cur ? cur->paused : 0);
    
    
    							/* Delete it now */
    							newm->next = cur->next;
    							if (prev) {
    								prev->next = newm;
    							} else {
    								q->members = newm;
    							}
    							free(cur);
    						} else {
    
    							/* Add them to the master int list if necessary */
    							add_to_interfaces(interface);
    
    							newm->next = q->members;
    							q->members = newm;
    
    Mark Spencer's avatar
    Mark Spencer committed
    						}
    					} else {
    
    						queue_set_param(q, var->name, var->value, var->lineno, 1);
    
    
    				/* Free remaining members marked as delme */
    				for (prev = NULL, newm = NULL, cur = q->members; cur; prev = cur, cur = cur->next) {
    					if (newm) {
    						free(newm);
    						newm = NULL;
    					}
    
    					if (cur->delme) {
    						if (prev) {
    							prev->next = cur->next;
    							newm = cur;
    						} else {
    							q->members = cur->next;
    							newm = cur;
    						}
    
    				if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN)
    					rr_dep_warning();
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (new) {
    
    					AST_LIST_INSERT_HEAD(&queues, q, list);
    				} else
    					ast_mutex_unlock(&q->lock);
    
    	ast_config_destroy(cfg);
    
    	AST_LIST_TRAVERSE_SAFE_BEGIN(&queues, q, list) {
    
    			AST_LIST_REMOVE_CURRENT(&queues, list);
    
    			if (!q->count)
    				destroy_queue(q);
    			else
    				ast_log(LOG_DEBUG, "XXX Leaking a little memory :( XXX\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else {
    
    			ast_mutex_lock(&q->lock);
    
    			for (cur = q->members; cur; cur = cur->next)
    				cur->status = ast_device_state(cur->interface);
    
    			ast_mutex_unlock(&q->lock);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	AST_LIST_TRAVERSE_SAFE_END;
    	AST_LIST_UNLOCK(&queues);
    
    static int __queues_show(struct mansession *s, int manager, int fd, int argc, char **argv, int queue_show)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct call_queue *q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct queue_ent *qe;
    	struct member *mem;
    	int pos;
    	time_t now;
    
    	char max_buf[80];
    	char *max;
    	size_t max_left;
    
    	float sl = 0;
    
    	char *term = manager ? "\r\n" : "\n";
    
    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;
    
    
    	/* We only want to load realtime queues when a specific queue is asked for. */
    	if (queue_show)
    		load_realtime_queue(argv[2]);
    
    
    	AST_LIST_LOCK(&queues);
    	if (AST_LIST_EMPTY(&queues)) {
    		AST_LIST_UNLOCK(&queues);
    
    		if (queue_show) {
    			if (s)
    				astman_append(s, "No such queue: %s.%s",argv[2], term);
    			else
    				ast_cli(fd, "No such queue: %s.%s",argv[2], term);
    		} else {
    			if (s)
    				astman_append(s, "No queues.%s", term);
    			else
    				ast_cli(fd, "No queues.%s", term);
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return RESULT_SUCCESS;
    	}
    
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    		if (queue_show) {
    			if (strcasecmp(q->name, argv[2]) != 0) {
    				ast_mutex_unlock(&q->lock);
    
    				if (!AST_LIST_NEXT(q, list)) {
    
    					ast_cli(fd, "No such queue: %s.%s",argv[2], term);
    
    		max_buf[0] = '\0';
    		max = max_buf;
    		max_left = sizeof(max_buf);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (q->maxlen)
    
    			ast_build_string(&max, &max_left, "%d", q->maxlen);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		else
    
    			ast_build_string(&max, &max_left, "unlimited");
    
    		if (q->callscompleted > 0)
    			sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted);
    
    		if (s)
    			astman_append(s, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds%s",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight,
    				q->callscompleted, q->callsabandoned,sl,q->servicelevel, term);
    
    		else
    			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%s",
    				q->name, q->count, max_buf, int2strat(q->strategy), q->holdtime, q->weight, q->callscompleted, q->callsabandoned,sl,q->servicelevel, term);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (q->members) {
    
    			if (s)
    				astman_append(s, "   Members: %s", term);
    			else
    				ast_cli(fd, "   Members: %s", term);
    
    			for (mem = q->members; mem; mem = mem->next) {
    
    				max_buf[0] = '\0';
    				max = max_buf;
    				max_left = sizeof(max_buf);
    
    					ast_build_string(&max, &max_left, " with penalty %d", mem->penalty);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (mem->dynamic)
    
    					ast_build_string(&max, &max_left, " (dynamic)");
    
    				if (mem->paused)
    
    					ast_build_string(&max, &max_left, " (paused)");
    				ast_build_string(&max, &max_left, " (%s)", devstate2str(mem->status));
    
    					ast_build_string(&max, &max_left, " has taken %d calls (last was %ld secs ago)",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    						mem->calls, (long) (time(NULL) - mem->lastcall));
    
    					ast_build_string(&max, &max_left, " has taken no calls yet");
    
    				if (s)
    					astman_append(s, "      %s%s%s", mem->interface, max_buf, term);
    				else
    					ast_cli(fd, "      %s%s%s", mem->interface, max_buf, term);
    
    		} else if (s)
    			astman_append(s, "   No Members%s", term);
    		else	
    
    			ast_cli(fd, "   No Members%s", term);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (q->head) {
    			pos = 1;
    
    			if (s)
    				astman_append(s, "   Callers: %s", term);
    			else
    				ast_cli(fd, "   Callers: %s", term);
    			for (qe = q->head; qe; qe = qe->next) {
    				if (s)
    
    					astman_append(s, "      %d. %s (wait: %ld:%2.2ld, prio: %d)%s",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    						pos++, qe->chan->name, (long) (now - qe->start) / 60,
    						(long) (now - qe->start) % 60, qe->prio, term);
    
    					ast_cli(fd, "      %d. %s (wait: %ld:%2.2ld, prio: %d)%s", pos++,
    						qe->chan->name, (long) (now - qe->start) / 60,
    						(long) (now - qe->start) % 60, qe->prio, term);
    
    			}
    		} else if (s)
    			astman_append(s, "   No Callers%s", term);
    		else
    
    			ast_cli(fd, "   No Callers%s", term);
    
    		if (s)
    			astman_append(s, "%s", term);
    		else
    			ast_cli(fd, "%s", term);
    
    		if (queue_show)
    			break;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	AST_LIST_UNLOCK(&queues);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return RESULT_SUCCESS;
    }
    
    
    static int queues_show(int fd, int argc, char **argv)
    {
    
    	return __queues_show(NULL, 0, fd, argc, argv, 0);
    
    }
    
    static int queue_show(int fd, int argc, char **argv)
    {
    
    	return __queues_show(NULL, 0, fd, argc, argv, 1);
    
    static char *complete_queue(const char *line, const char *word, int pos, int state)
    
    	struct call_queue *q;
    
    	char *ret = NULL;
    	int which = 0;
    	int wordlen = strlen(word);
    
    	AST_LIST_LOCK(&queues);
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    		if (!strncasecmp(word, q->name, wordlen) && ++which > state) {
    			ret = ast_strdup(q->name);	
    
    	AST_LIST_UNLOCK(&queues);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    /*!\brief callback to display queues status in manager
       \addtogroup Group_AMI
    
    Russell Bryant's avatar
    Russell Bryant committed
     */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int manager_queues_show( struct mansession *s, struct message *m )
    {
    	char *a[] = { "show", "queues" };
    
    	__queues_show(s, 1, -1, 2, a, 0);
    	astman_append(s, "\r\n\r\n");	/* Properly terminate Manager output */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    }
    
    
    /* 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 *queuefilter = astman_get_header(m,"Queue");
    	char *memberfilter = astman_get_header(m,"Member");
    
    	char idText[256] = "";
    
    	struct 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_LIST_LOCK(&queues);
    
    	if (!ast_strlen_zero(id))
    		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
    
    
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    
    		/* List queue properties */
    
    		if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) {