Skip to content
Snippets Groups Projects
app_queue.c 170 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	/* 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) {
    
    			ast_debug(1, "%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')))
    
    	if (args.options && (strchr(args.options, 'c')))
    		qcontinue = 1;
    
    
    	ast_debug(1, "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;
    
    	qe.valid_digits = 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 {
    
    		/* This is the wait loop for callers 2 through maxlen */
    		res = wait_our_turn(&qe, ringing, &reason);
    
    		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 timeout. */
    
    			enum queue_member_status stat;
    
    			/* Leave if we have exceeded our queuetimeout */
    			if (qe.expire && (time(NULL) > qe.expire)) {
    				record_abandoned(&qe);
    				reason = QUEUE_TIMEOUT;
    				res = 0;
    				ast_queue_log(args.queuename, chan->uniqueid,"NONE", "EXITWITHTIMEOUT", "%d|%d|%ld", 
    					qe.pos, qe.opos, (long) time(NULL) - qe.start);
    				break;
    			}
    
    			if (makeannouncement) {
    				/* Make a position announcement, if enabled */
    
    				if (qe.parent->announcefrequency)
    					if ((res = say_position(&qe,ringing)))
    
    			}
    			makeannouncement = 1;
    
    			/* Make a periodic announcement, if enabled */
    
    			if (qe.parent->periodicannouncefrequency)
    				if ((res = say_periodic_announcement(&qe,ringing)))
    
    			/* Try calling all queue members for 'timeout' seconds */
    
    			res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi, args.macro, args.gosub, ringing);
    
    			stat = get_member_status(qe.parent, qe.max_penalty);
    
    			/* exit after 'timeout' cycle if 'n' option enabled */
    
    			if (noption && tries >= qe.parent->membercount) {
    
    				ast_verb(3, "Exiting on time-out cycle\n");
    
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos);
    				record_abandoned(&qe);
    				reason = QUEUE_TIMEOUT;
    				res = 0;
    				break;
    			}
    
    			/* leave the queue if no agents, if enabled */
    			if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
    				record_abandoned(&qe);
    				reason = QUEUE_LEAVEEMPTY;
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
    				res = 0;
    				break;
    			}
    
    			/* leave the queue if no reachable agents, if enabled */
    			if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) {
    				record_abandoned(&qe);
    				reason = QUEUE_LEAVEUNAVAIL;
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start));
    				res = 0;
    				break;
    			}
    			if ((qe.parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
    				record_abandoned(&qe);
    				reason = QUEUE_LEAVEUNAVAIL;
    				res = 0;
    				break;
    			}
    
    			/* Leave if we have exceeded our queuetimeout */
    			if (qe.expire && (time(NULL) > qe.expire)) {
    				record_abandoned(&qe);
    				reason = QUEUE_TIMEOUT;
    				res = 0;
    				ast_queue_log(qe.parent->name, qe.chan->uniqueid,"NONE", "EXITWITHTIMEOUT", "%d|%d|%ld", qe.pos, qe.opos, (long) time(NULL) - qe.start);
    				break;
    			}
    
    			/* If using dynamic realtime members, we should regenerate the member list for this queue */
    			update_realtime_members(qe.parent);
    
    
    			/* OK, we didn't get anybody; wait for 'retry' seconds; may get a digit to exit with */
    			res = wait_a_bit(&qe);
    			if (res)
    				goto stop;
    
    			/* 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_debug(1, "Darn priorities, going back in queue (%s)!\n", qe.chan->name);
    				goto check_turns;
    
    
    stop:
    		if (res) {
    			if (res < 0) {
    				if (!qe.handled) {
    					record_abandoned(&qe);
    					ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ABANDON",
    						"%d|%d|%ld", qe.pos, qe.opos,
    						(long) time(NULL) - qe.start);
    				}
    				res = -1;
    			} else if (qe.valid_digits) {
    				ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHKEY",
    					"%s|%d", qe.digits, qe.pos);
    			}
    		}
    
    
    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
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int queue_function_var(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    
    	struct call_queue *q, tmpq = {
    		.name = data,	
    	};
    
    
    	char interfacevar[256]="";
            float sl = 0;
    
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
    		return -1;
    	}
    
    
    	if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
    		ao2_lock(q);
    
            	if (q->setqueuevar) {
    		        sl = 0;
    			res = 0;
    
    		        if (q->callscompleted > 0)
    		                sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted);
    
    		        snprintf(interfacevar,sizeof(interfacevar),
                    		"QUEUEMAX=%d|QUEUESTRATEGY=%s|QUEUECALLS=%d|QUEUEHOLDTIME=%d|QUEUECOMPLETED=%d|QUEUEABANDONED=%d|QUEUESRVLEVEL=%d|QUEUESRVLEVELPERF=%2.1f",
    		                q->maxlen, int2strat(q->strategy), q->count, q->holdtime, q->callscompleted, q->callsabandoned,  q->servicelevel, sl);
    
    		        pbx_builtin_setvar(chan, interfacevar);
    	        }
    
    
    	} else
    		ast_log(LOG_WARNING, "queue %s was not found\n", data);
    
    	snprintf(buf, len, "%d", res);
    
    	return 0;
    }
    
    
    static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    
    	struct ao2_iterator mem_iter;
    
    	struct call_queue *q;
    
    
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
    		return -1;
    	}
    
    	if ((option = strchr(data, ',')))
    		*option++ = '\0';
    	else
    		option = "logged";
    
    	if ((q = load_realtime_queue(data))) {
    
    		ao2_lock(q);
    		if(!strcasecmp(option, "logged")) {
    			mem_iter = ao2_iterator_init(q->members, 0);
    			while ((m = ao2_iterator_next(&mem_iter))) {
    				/* Count the agents who are logged in and presently answering calls */
    				if ((m->status != AST_DEVICE_UNAVAILABLE) && (m->status != AST_DEVICE_INVALID)) {
    					count++;
    				}
    				ao2_ref(m, -1);
    			}
    		} else if(!strcasecmp(option, "free")) {
    			mem_iter = ao2_iterator_init(q->members, 0);
    			while ((m = ao2_iterator_next(&mem_iter))) {
    				/* Count the agents who are logged in and presently answering calls */
    				if ((m->status == AST_DEVICE_NOT_INUSE) && (!m->paused)) {
    					count++;
    				}
    				ao2_ref(m, -1);
    			}
    		} else /* must be "count" */
    			count = q->membercount;
    		ao2_unlock(q);
    		queue_unref(q);
    	} else
    		ast_log(LOG_WARNING, "queue %s was not found\n", data);
    
    	snprintf(buf, len, "%d", count);
    
    	return 0;
    }
    
    static int queue_function_qac_dep(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    	int count = 0;
    	struct member *m;
    
    	struct call_queue *q;
    
    	struct ao2_iterator mem_iter;
    	static int depflag = 1;
    
    		depflag = 0;
    		ast_log(LOG_NOTICE, "The function QUEUE_MEMBER_COUNT has been deprecated in favor of the QUEUE_MEMBER function and will not be in further releases.\n");
    	}
    
    	if (ast_strlen_zero(data)) {
    
    		ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
    
    		return -1;
    
    	if((q = load_realtime_queue(data))) {
    
    		mem_iter = ao2_iterator_init(q->members, 0);
    		while ((m = ao2_iterator_next(&mem_iter))) {
    
    			/* Count the agents who are logged in and presently answering calls */
    			if ((m->status != AST_DEVICE_UNAVAILABLE) && (m->status != AST_DEVICE_INVALID)) {
    				count++;
    			}
    
    	} else
    		ast_log(LOG_WARNING, "queue %s was not found\n", data);
    
    	snprintf(buf, len, "%d", count);
    
    static int queue_function_queuewaitingcount(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    
    	struct call_queue *q, tmpq = {
    		.name = data,	
    	};
    
    
    	buf[0] = '\0';
    	
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd);
    		return -1;
    	}
    
    
    	if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
    		ao2_lock(q);
    
    	} else
    		ast_log(LOG_WARNING, "queue %s was not found\n", data);
    
    
    static int queue_function_queuememberlist(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    
    	struct call_queue *q, tmpq = {
    		.name = data,	
    	};
    
    	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;
    
    	if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
    
    		int buflen = 0, count = 0;
    
    		struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
    
    		while ((m = ao2_iterator_next(&mem_iter))) {
    
    			/* strcat() is always faster than printf() */
    			if (count++) {
    				strncat(buf + buflen, ",", len - buflen - 1);
    				buflen++;
    			}
    
    			strncat(buf + buflen, m->membername, len - buflen - 1);
    			buflen += strlen(m->membername);
    
    			/* Safeguard against overflow (negative length) */
    			if (buflen >= len - 2) {
    
    				ast_log(LOG_WARNING, "Truncating list\n");
    				break;
    			}
    
    	} 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';
    
    /*! \brief Dialplan function QUEUE_MEMBER_PENALTY() 
     * Gets the members penalty. */
    
    static int queue_function_memberpenalty_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
    {
    
    	int penalty;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
            );
    	/* Make sure the returned value on error is NULL. */
    	buf[0] = '\0';
    
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER_PENALTY(<queuename>,<interface>)\n");
    		return -1;
    	}
    
    	AST_STANDARD_APP_ARGS(args, data);
    
    	if (args.argc < 2) {
    		ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER_PENALTY(<queuename>,<interface>)\n");
    		return -1;
    	}
    
    	penalty = get_member_penalty (args.queuename, args.interface);
    	
    	if (penalty >= 0) /* remember that buf is already '\0' */
    		snprintf (buf, len, "%d", penalty);
    
    	return 0;
    }
    
    /*! Dialplan function QUEUE_MEMBER_PENALTY() 
     * Sets the members penalty. */
    
    static int queue_function_memberpenalty_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
    {
    
    	int penalty;
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(queuename);
    		AST_APP_ARG(interface);
    	);
    
    	if (ast_strlen_zero(data)) {
    		ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER_PENALTY(<queuename>,<interface>)\n");
    		return -1;
    	}
    
    	AST_STANDARD_APP_ARGS(args, data);
    
    	if (args.argc < 2) {
    		ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER_PENALTY(<queuename>,<interface>)\n");
    		return -1;
    	}
    
    	penalty = atoi(value);
    	if (penalty < 0) {
    		ast_log(LOG_ERROR, "Invalid penalty\n");
    		return -1;
    	}
    
    	if (ast_strlen_zero(args.interface)) {
    		ast_log (LOG_ERROR, "<interface> parameter can't be null\n");
    		return -1;
    	}
    
    	/* if queuename = NULL then penalty will be set for interface in all the queues. */
    	set_member_penalty(args.queuename, args.interface, penalty);
    
    	return 0;
    }
    
    
    static struct ast_custom_function queuevar_function = {
    	.name = "QUEUE_VARIABLES",
    	.synopsis = "Return Queue information in variables",
    	.syntax = "QUEUE_VARIABLES(<queuename>)",
    	.desc =
    "Makes the following queue variables available.\n"
    "QUEUEMAX maxmimum number of calls allowed\n"
    "QUEUESTRATEGY the strategy of the queue\n"
    "QUEUECALLS number of calls currently in the queue\n"
    "QUEUEHOLDTIME current average hold time\n"
    "QUEUECOMPLETED number of completed calls for the queue\n"
    "QUEUEABANDONED number of abandoned calls\n"
    "QUEUESRVLEVEL queue service level\n"
    "QUEUESRVLEVELPERF current service level performance\n"
    "Returns 0 if queue is found and setqueuevar is defined, -1 otherwise",
    	.read = queue_function_var,
    };
    
    
    static struct ast_custom_function queuemembercount_function = {
    
    	.name = "QUEUE_MEMBER",
    	.synopsis = "Count number of members answering a queue",
    	.syntax = "QUEUE_MEMBER(<queuename>, <option>)",
    	.desc =
    "Returns the number of members currently associated with the specified queue.\n"
    "One of three options may be passed to determine the count returned:\n"
    	"\"logged\" - Returns the number of logged-in members for the specified queue\n"
    	"\"free\" - Returns the number of logged-in members for the specified queue available to take a call\n"
    	"\"count\" - Returns the total number of members for the specified queue\n",
    	.read = queue_function_qac,
    };
    
    static struct ast_custom_function queuemembercount_dep = {
    
    	.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\n"
    "This function has been deprecated in favor of the QUEUE_MEMBER function\n",
    	.read = queue_function_qac_dep,
    
    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 struct ast_custom_function queuememberpenalty_function = {
    	.name = "QUEUE_MEMBER_PENALTY",
    	.synopsis = "Gets or sets queue members penalty.",
    	.syntax = "QUEUE_MEMBER_PENALTY(<queuename>,<interface>)",
    	.desc =
    "Gets or sets queue members penalty\n",
    	.read = queue_function_memberpenalty_read,
    	.write = queue_function_memberpenalty_write,
    };
    
    
    
    static int reload_queues(int reload)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_config *cfg;
    	char *cat, *tmp;
    	struct ast_variable *var;
    
    	struct member *cur, *newm;
    
    	struct ao2_iterator mem_iter;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int new;
    
    	char parse[80];
    	char *interface;
    
    	char *membername = NULL;
    
    	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
    
    	AST_DECLARE_APP_ARGS(args,
    		AST_APP_ARG(interface);
    		AST_APP_ARG(penalty);
    		AST_APP_ARG(membername);
    	);
    
    	if (!(cfg = ast_config_load("queues.conf", config_flags))) {
    
    		ast_log(LOG_NOTICE, "No call queueing config file (queues.conf), so no call queues\n");
    
    	} else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
    		return 0;
    
    	use_weight=0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Mark all queues as dead for the moment */
    
    	queue_iter = ao2_iterator_init(queues, F_AO2I_DONTLOCK);
    	while ((q = ao2_iterator_next(&queue_iter))) {
    
    		if(!q->realtime) {
    
    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_keep_stats = 0;
    			if ((general_val = ast_variable_retrieve(cfg, "general", "keepstats")))
    				queue_keep_stats = ast_true(general_val);
    
    			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);
    
    Jason Parker's avatar
    Jason Parker committed
    			if ((general_val = ast_variable_retrieve(cfg, "general", "monitor-type"))) {
    
    				if (!strcasecmp(general_val, "mixmonitor"))
    					montype_default = 1;
    
    Jason Parker's avatar
    Jason Parker committed
    			}
    			update_cdr = 0;
    			if ((general_val = ast_variable_retrieve(cfg, "general", "updatecdr")))
    				update_cdr = ast_true(general_val);
    
    			shared_lastcall = 0;
    			if ((general_val = ast_variable_retrieve(cfg, "general", "shared_lastcall")))
    				shared_lastcall = ast_true(general_val);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Look for an existing one */
    
    			struct call_queue tmpq = {
    				.name = cat,
    			};
    			if (!(q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				/* 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) {
    
    				/* Check if a queue with this name already exists */
    				if (q->found) {
    					ast_log(LOG_WARNING, "Queue '%s' already defined! Skipping!\n", cat);
    					if(!new)
    
    				/* Due to the fact that the "linear" strategy will have a different allocation
    				 * scheme for queue members, we must devise the queue's strategy before other initializations
    				 */
    				if((tmpvar = ast_variable_retrieve(cfg, cat, "strategy"))) {
    					q->strategy = strat2int(tmpvar);
    					if (q->strategy < 0) {
    						ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
    						tmpvar, q->name);
    						q->strategy = QUEUE_STRATEGY_RINGALL;
    					}
    				} else
    					q->strategy = QUEUE_STRATEGY_RINGALL;
    
    				/* Re-initialize the queue, and clear statistics */
    				init_queue(q);
    
    				mem_iter = ao2_iterator_init(q->members, 0);
    				while ((cur = ao2_iterator_next(&mem_iter))) {
    
    					if (!cur->dynamic) {
    						cur->delme = 1;
    					}
    
    					ao2_ref(cur, -1);
    
    				for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    					if (!strcasecmp(var->name, "member")) {
    
    						struct member tmpmem;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    						/* Add a new member */
    
    						ast_copy_string(parse, var->value, sizeof(parse));
    						
    						AST_NONSTANDARD_APP_ARGS(args, parse, ',');
    
    						interface = args.interface;
    						if(!ast_strlen_zero(args.penalty)) {
    							tmp = args.penalty;
    							while (*tmp && *tmp < 33) tmp++;
    
    							penalty = atoi(tmp);
    							if (penalty < 0) {
    								penalty = 0;
    
    						if (!ast_strlen_zero(args.membername)) {
    							membername = args.membername;
    							while (*membername && *membername < 33) membername++;
    
    						/* Find the old position in the list */
    
    						ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
    						cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK);
    
    						newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0);
    
    						ao2_link(q->members, newm);
    
    						ao2_ref(newm, -1);
    						newm = NULL;
    
    						if (cur)
    							ao2_ref(cur, -1);
    						else {
    
    							/* Add them to the master int list if necessary */
    							add_to_interfaces(interface);
    
    							q->membercount++;
    
    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 */
    
    				mem_iter = ao2_iterator_init(q->members, 0);
    				while ((cur = ao2_iterator_next(&mem_iter))) {
    
    					if (! cur->delme) {
    						ao2_ref(cur, -1);
    
    
    					remove_from_interfaces(cur->interface);
    
    					q->membercount--;
    
    					ao2_unlink(q->members, cur);
    					ao2_ref(cur, -1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (new) {
    
    				} else
    
    	ast_config_destroy(cfg);
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while ((q = ao2_iterator_next(&queue_iter))) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else {
    
    			mem_iter = ao2_iterator_init(q->members, 0);
    			while ((cur = ao2_iterator_next(&mem_iter))) {
    
    				if (cur->dynamic)
    
    					q->membercount++;
    
    				cur->status = ast_device_state(cur->interface);
    
    				ao2_ref(cur, -1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    /*! \brief direct ouput to manager or cli with proper terminator */
    static void do_print(struct mansession *s, int fd, const char *str)
    {
    	if (s)
    		astman_append(s, "%s\r\n", str);
    	else
    		ast_cli(fd, "%s\n", str);
    }
    
    
    static char *__queues_show(struct mansession *s, int fd, int argc, char **argv)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct call_queue *q;
    
    	struct ast_str *out = ast_str_alloca(240);
    
    	int found = 0;
    	time_t now = time(NULL);
    
    	struct ao2_iterator mem_iter;
    
    	if (argc != 2 && argc != 3)
    
    		return CLI_SHOWUSAGE;
    
    
    	/* We only want to load realtime queues when a specific queue is asked for. */
    
    	if (argc == 3)	/* specific queue */
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while ((q = ao2_iterator_next(&queue_iter))) {
    
    		if (argc == 3 && strcasecmp(q->name, argv[2])) {
    
    		found = 1;
    
    		ast_str_set(&out, 0, "%-12.12s has %d calls (max ", q->name, q->count);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (q->maxlen)
    
    			ast_str_append(&out, 0, "%d", q->maxlen);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		else
    
    			ast_str_append(&out, 0, "unlimited");
    
    		if (q->callscompleted > 0)
    			sl = 100 * ((float) q->callscompletedinsl / (float) q->callscompleted);
    
    		ast_str_append(&out, 0, ") in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds",
    			int2strat(q->strategy), q->holdtime, q->weight,
    			q->callscompleted, q->callsabandoned,sl,q->servicelevel);
    		do_print(s, fd, out->str);
    
    		if (!ao2_container_count(q->members))
    
    			do_print(s, fd, "   No Members");
    		else {
    			struct member *mem;
    
    			do_print(s, fd, "   Members: ");
    
    			mem_iter = ao2_iterator_init(q->members, 0);
    			while ((mem = ao2_iterator_next(&mem_iter))) {
    
    				ast_str_set(&out, 0, "      %s", mem->membername);
    
    					ast_str_append(&out, 0, " with penalty %d", mem->penalty);
    
    				ast_str_append(&out, 0, "%s%s%s (%s)",
    
    					mem->dynamic ? " (dynamic)" : "",
    
    					mem->realtime ? " (realtime)" : "",
    
    					mem->paused ? " (paused)" : "",
    					devstate2str(mem->status));
    				if (mem->calls)
    					ast_str_append(&out, 0, " has taken %d calls (last was %ld secs ago)",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    						mem->calls, (long) (time(NULL) - mem->lastcall));
    
    					ast_str_append(&out, 0, " has taken no calls yet");
    				do_print(s, fd, out->str);
    
    				ao2_ref(mem, -1);
    
    		}
    		if (!q->head)
    			do_print(s, fd, "   No Callers");
    		else {
    			struct queue_ent *qe;
    			int pos = 1;
    
    			do_print(s, fd, "   Callers: ");
    
    			for (qe = q->head; qe; qe = qe->next) {
    
    				ast_str_set(&out, 0, "      %d. %s (wait: %ld:%2.2ld, prio: %d)",
    					pos++, qe->chan->name, (long) (now - qe->start) / 60,
    					(long) (now - qe->start) % 60, qe->prio);
    				do_print(s, fd, out->str);
    
    		}
    		do_print(s, fd, "");	/* blank line between entries */
    
    		ao2_unlock(q);
    		if (argc == 3)	{ /* print a specific entry */
    			queue_unref(q);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if (!found) {
    		if (argc == 3)
    			ast_str_set(&out, 0, "No such queue: %s.", argv[2]);
    		else
    			ast_str_set(&out, 0, "No queues.");
    		do_print(s, fd, out->str);
    	}
    
    	return CLI_SUCCESS;
    
    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);
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while((q = ao2_iterator_next(&queue_iter))) {
    
    		if (!strncasecmp(word, q->name, wordlen) && ++which > state) {
    
    			ret = ast_strdup(q->name);
    
    static char *complete_queue_show(const char *line, const char *word, int pos, int state)
    {
    	if (pos == 2)
    		return complete_queue(line, word, pos, state);
    	return NULL;
    }
    
    
    static char *queue_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
    {
    	switch ( cmd ) {
    	case CLI_INIT:
    		e->command = "queue show";
    		e->usage =
    			"Usage: queue show\n"
    			"       Provides summary information on a specified queue.\n";
    		return NULL;
    	case CLI_GENERATE:
    		return complete_queue_show(a->line, a->word, a->pos, a->n);	
    	}
    
    	return __queues_show(NULL, a->fd, a->argc, a->argv);
    }
    
    
    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
     */
    
    static int manager_queues_show(struct mansession *s, const struct message *m)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	char *a[] = { "queue", "show" };
    
    	__queues_show(s, -1, 2, a);
    
    	astman_append(s, "\r\n\r\n");	/* Properly terminate Manager output */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    }
    
    /* Dump summary of queue info */
    
    static int manager_queues_summary(struct mansession *s, const struct message *m)
    
    {
    	time_t now;
    	int qmemcount = 0;
    	int qmemavail = 0;
    	int qchancount = 0;
    
    	int qlongestholdtime = 0;
    
    	const char *id = astman_get_header(m, "ActionID");
    	const char *queuefilter = astman_get_header(m, "Queue");
    
    	char idText[256] = "";
    	struct call_queue *q;
    	struct queue_ent *qe;
    	struct member *mem;
    
    	struct ao2_iterator mem_iter;
    
    
    	astman_send_ack(s, m, "Queue summary will follow");
    	time(&now);
    	if (!ast_strlen_zero(id))
    		snprintf(idText, 256, "ActionID: %s\r\n", id);
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while((q = ao2_iterator_next(&queue_iter))) {
    		ao2_lock(q);
    
    
    		/* List queue properties */
    		if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) {
    
    			/* Reset the necessary local variables if no queuefilter is set*/
    			qmemcount = 0;
    			qmemavail = 0;
    			qchancount = 0;
    			qlongestholdtime = 0;
    
    
    			/* List Queue Members */
    
    			mem_iter = ao2_iterator_init(q->members, 0);
    			while ((mem = ao2_iterator_next(&mem_iter))) {
    
    				if ((mem->status != AST_DEVICE_UNAVAILABLE) && (mem->status != AST_DEVICE_INVALID)) {
    					++qmemcount;
    					if (((mem->status == AST_DEVICE_NOT_INUSE) || (mem->status == AST_DEVICE_UNKNOWN)) && !(mem->paused)) {
    						++qmemavail;
    					}
    				}
    
    				ao2_ref(mem, -1);
    
    			}
    			for (qe = q->head; qe; qe = qe->next) {
    
    				if ((now - qe->start) > qlongestholdtime) {
    					qlongestholdtime = now - qe->start;
    				}
    
    				++qchancount;
    			}
    			astman_append(s, "Event: QueueSummary\r\n"
    				"Queue: %s\r\n"
    				"LoggedIn: %d\r\n"
    				"Available: %d\r\n"
    				"Callers: %d\r\n" 
    				"HoldTime: %d\r\n"
    
    				"LongestHoldTime: %d\r\n"
    
    				q->name, qmemcount, qmemavail, qchancount, q->holdtime, qlongestholdtime, idText);
    
    	}
    	astman_append(s,
    		"Event: QueueSummaryComplete\r\n"
    		"%s"
    		"\r\n", idText);
    
    	return RESULT_SUCCESS;
    }
    
    
    /* Dump queue status */
    
    static int manager_queues_status(struct mansession *s, const struct message *m)
    
    {
    	time_t now;
    	int pos;
    
    	const char *id = astman_get_header(m,"ActionID");
    	const char *queuefilter = astman_get_header(m,"Queue");
    	const char *memberfilter = astman_get_header(m,"Member");
    
    	char idText[256] = "";
    
    	struct call_queue *q;
    
    	struct queue_ent *qe;
    
    	float sl = 0;
    	struct member *mem;
    
    	struct ao2_iterator mem_iter;
    
    	astman_send_ack(s, m, "Queue status will follow");
    
    	time(&now);
    
    	if (!ast_strlen_zero(id))
    		snprintf(idText, sizeof(idText), "ActionID: %s\r\n", id);
    
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while ((q = ao2_iterator_next(&queue_iter))) {
    		ao2_lock(q);
    
    
    		/* List queue properties */
    
    		if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) {
    
    			sl = ((q->callscompleted > 0) ? 100 * ((float)q->callscompletedinsl / (float)q->callscompleted) : 0);
    
    			astman_append(s, "Event: QueueParams\r\n"