Skip to content
Snippets Groups Projects
app_queue.c 331 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			if (conditions & QUEUE_EMPTY_UNAVAILABLE) {
    				ast_debug(4, "%s is unavailable because his device state is 'unavailable'\n", member->membername);
    				break;
    			}
    
    		case AST_DEVICE_INUSE:
    			if (conditions & QUEUE_EMPTY_INUSE) {
    				ast_debug(4, "%s is unavailable because his device state is 'inuse'\n", member->membername);
    				break;
    			}
    
    		case AST_DEVICE_RINGING:
    			if (conditions & QUEUE_EMPTY_RINGING) {
    				ast_debug(4, "%s is unavailable because his device state is 'ringing'\n", member->membername);
    				break;
    			}
    
    			goto default_case;
    
    		case AST_DEVICE_UNKNOWN:
    			if (conditions & QUEUE_EMPTY_UNKNOWN) {
    				ast_debug(4, "%s is unavailable because his device state is 'unknown'\n", member->membername);
    				break;
    			}
    
    			if (member->paused && (conditions & QUEUE_EMPTY_PAUSED)) {
    				ast_debug(4, "%s is unavailable because he is paused'\n", member->membername);
    				break;
    			} else if ((conditions & QUEUE_EMPTY_WRAPUP) && member->lastcall && q->wrapuptime && (time(NULL) - q->wrapuptime < member->lastcall)) {
    				ast_debug(4, "%s is unavailable because it has only been %d seconds since his last call (wrapup time is %d)\n", member->membername, (int) (time(NULL) - member->lastcall), q->wrapuptime);
    				break;
    
    				ao2_ref(member, -1);
    
    				ao2_iterator_destroy(&mem_iter);
    
    				ast_debug(4, "%s is available.\n", member->membername);
    				return 0;
    
    	ao2_iterator_destroy(&mem_iter);
    
    /*! \brief set a member's status based on device state of that member's state_interface.
    
     * Lock interface list find sc, iterate through each queues queue_member list for member to
     * update state inside queues
    */
    
    static void update_status(struct call_queue *q, struct member *m, const int status)
    
    	m->status = status;
    
    	queue_publish_member_blob(queue_member_status_type(), queue_member_blob_create(q, m));
    
    /*!
     * \internal \brief Determine if a queue member is available
     * \retval 1 if the member is available
     * \retval 0 if the member is not available
     */
    static int is_member_available(struct member *mem)
    {
    	int available = 0;
    
    	switch (mem->status) {
    		case AST_DEVICE_INVALID:
    		case AST_DEVICE_UNAVAILABLE:
    			break;
    		case AST_DEVICE_INUSE:
    		case AST_DEVICE_BUSY:
    		case AST_DEVICE_RINGING:
    		case AST_DEVICE_RINGINUSE:
    		case AST_DEVICE_ONHOLD:
    			if (!mem->ringinuse) {
    				break;
    			}
    			/* else fall through */
    		case AST_DEVICE_NOT_INUSE:
    		case AST_DEVICE_UNKNOWN:
    			if (!mem->paused) {
    				available = 1;
    			}
    			break;
    	}
    
    	return available;
    }
    
    
    /*! \brief set a member's status based on device state of that member's interface*/
    
    static void device_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *msg)
    
    	struct ao2_iterator miter, qiter;
    
    	struct ast_device_state_message *dev_state;
    
    	struct member *m;
    	struct call_queue *q;
    	char interface[80], *slash_pos;
    
    	int found = 0;			/* Found this member in any queue */
    	int found_member;		/* Found this member in this queue */
    	int avail = 0;			/* Found an available member in this queue */
    
    	if (ast_device_state_message_type() != stasis_message_type(msg)) {
    		return;
    	}
    
    	dev_state = stasis_message_data(msg);
    	if (dev_state->eid) {
    		/* ignore non-aggregate states */
    		return;
    	}
    
    
    	qiter = ao2_iterator_init(queues, 0);
    
    	while ((q = ao2_t_iterator_next(&qiter, "Iterate over queues"))) {
    
    		avail = 0;
    		found_member = 0;
    
    		miter = ao2_iterator_init(q->members, 0);
    		for (; (m = ao2_iterator_next(&miter)); ao2_ref(m, -1)) {
    
    			if (!found_member) {
    				ast_copy_string(interface, m->state_interface, sizeof(interface));
    
    				if ((slash_pos = strchr(interface, '/'))) {
    					if (!strncasecmp(interface, "Local/", 6) && (slash_pos = strchr(slash_pos + 1, '/'))) {
    						*slash_pos = '\0';
    					}
    				}
    
    
    				if (!strcasecmp(interface, dev_state->device)) {
    
    					found_member = 1;
    
    					update_status(q, m, dev_state->state);
    
    			/* check every member until we find one NOT_INUSE */
    
    			if (!avail) {
    				avail = is_member_available(m);
    
    			}
    			if (avail && found_member) {
    				/* early exit as we've found an available member and the member of interest */
    
    
    		if (found_member) {
    			found = 1;
    			if (avail) {
    
    				ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s_avail", q->name);
    
    				ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s_avail", q->name);
    
    		ao2_iterator_destroy(&miter);
    
    	ao2_iterator_destroy(&qiter);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (found) {
    
    		ast_debug(1, "Device '%s' changed to state '%d' (%s)\n",
    			dev_state->device,
    			dev_state->state,
    			ast_devstate2str(dev_state->state));
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else {
    
    		ast_debug(3, "Device '%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n",
    			dev_state->device,
    			dev_state->state,
    			ast_devstate2str(dev_state->state));
    
    /*! \brief Helper function which converts from extension state to device state values */
    static int extensionstate2devicestate(int state)
    {
    	switch (state) {
    	case AST_EXTENSION_NOT_INUSE:
    		state = AST_DEVICE_NOT_INUSE;
    		break;
    	case AST_EXTENSION_INUSE:
    		state = AST_DEVICE_INUSE;
    		break;
    	case AST_EXTENSION_BUSY:
    		state = AST_DEVICE_BUSY;
    		break;
    	case AST_EXTENSION_RINGING:
    		state = AST_DEVICE_RINGING;
    		break;
    	case AST_EXTENSION_ONHOLD:
    		state = AST_DEVICE_ONHOLD;
    		break;
    	case AST_EXTENSION_UNAVAILABLE:
    		state = AST_DEVICE_UNAVAILABLE;
    		break;
    	case AST_EXTENSION_REMOVED:
    	case AST_EXTENSION_DEACTIVATED:
    	default:
    		state = AST_DEVICE_INVALID;
    		break;
    	}
    
    	return state;
    }
    
    
    static int extension_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
    
    {
    	struct ao2_iterator miter, qiter;
    	struct member *m;
    	struct call_queue *q;
    
    	int state = info->exten_state;
    
    	int found = 0, device_state = extensionstate2devicestate(state);
    
    
    	/* only interested in extension state updates involving device states */
    	if (info->reason != AST_HINT_UPDATE_DEVICE) {
    		return 0;
    	}
    
    
    	qiter = ao2_iterator_init(queues, 0);
    
    	while ((q = ao2_t_iterator_next(&qiter, "Iterate through queues"))) {
    
    		ao2_lock(q);
    
    		miter = ao2_iterator_init(q->members, 0);
    		for (; (m = ao2_iterator_next(&miter)); ao2_ref(m, -1)) {
    			if (!strcmp(m->state_context, context) && !strcmp(m->state_exten, exten)) {
    				update_status(q, m, device_state);
    				ao2_ref(m, -1);
    				found = 1;
    				break;
    			}
    		}
    		ao2_iterator_destroy(&miter);
    
    		ao2_unlock(q);
    
    	}
    	ao2_iterator_destroy(&qiter);
    
            if (found) {
    		ast_debug(1, "Extension '%s@%s' changed to state '%d' (%s)\n", exten, context, device_state, ast_devstate2str(device_state));
    	} else {
    		ast_debug(3, "Extension '%s@%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n",
    			  exten, context, device_state, ast_devstate2str(device_state));
    	}
    
    	return 0;
    }
    
    /*! \brief Return the current state of a member */
    static int get_queue_member_status(struct member *cur)
    {
    	return ast_strlen_zero(cur->state_exten) ? ast_device_state(cur->state_interface) : extensionstate2devicestate(ast_extension_state(NULL, cur->state_context, cur->state_exten));
    }
    
    
    /*! \brief allocate space for new queue member and set fields based on parameters passed */
    
    static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused, const char *state_interface, int ringinuse)
    
    	if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
    
    		cur->penalty = penalty;
    		cur->paused = paused;
    		ast_copy_string(cur->interface, interface, sizeof(cur->interface));
    
    		if (!ast_strlen_zero(state_interface)) {
    
    			ast_copy_string(cur->state_interface, state_interface, sizeof(cur->state_interface));
    
    			ast_copy_string(cur->state_interface, interface, sizeof(cur->state_interface));
    
    		}
    		if (!ast_strlen_zero(membername)) {
    
    			ast_copy_string(cur->membername, membername, sizeof(cur->membername));
    
    			ast_copy_string(cur->membername, interface, sizeof(cur->membername));
    
    		}
    		if (!strchr(cur->interface, '/')) {
    
    			ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
    
    		if (!strncmp(cur->state_interface, "hint:", 5)) {
    			char *tmp = ast_strdupa(cur->state_interface), *context = tmp;
    
    			char *exten = strsep(&context, "@") + 5;
    
    			ast_copy_string(cur->state_exten, exten, sizeof(cur->state_exten));
    			ast_copy_string(cur->state_context, S_OR(context, "default"), sizeof(cur->state_context));
    		}
    		cur->status = get_queue_member_status(cur);
    
    static int compress_char(const char c)
    {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (c < 32) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else if (c > 96) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	return c - 32;
    
    }
    
    static int member_hash_fn(const void *obj, const int flags)
    {
    	const struct member *mem = obj;
    
    	const char *interface = (flags & OBJ_KEY) ? obj : mem->interface;
    	const char *chname = strchr(interface, '/');
    
    
    	if (!chname) {
    		chname = interface;
    	}
    	for (i = 0; i < 5 && chname[i]; i++) {
    
    		ret += compress_char(chname[i]) << (i * 6);
    
    static int member_cmp_fn(void *obj1, void *obj2, int flags)
    
    	struct member *mem1 = obj1;
    	struct member *mem2 = obj2;
    	const char *interface = (flags & OBJ_KEY) ? obj2 : mem2->interface;
    
    	return strcasecmp(mem1->interface, interface) ? 0 : CMP_MATCH | CMP_STOP;
    
     * \brief Initialize Queue default values.
     * \note the queue's lock  must be held before executing this function
    */
    
    static void init_queue(struct call_queue *q)
    
    	q->timeout = DEFAULT_TIMEOUT;
    
    	q->minannouncefrequency = DEFAULT_MIN_ANNOUNCE_FREQUENCY;
    
    	q->announcepositionlimit = 10; /* Default 10 positions */
    	q->announceposition = ANNOUNCEPOSITION_YES; /* Default yes */
    
    	q->roundingseconds = 0; /* Default - don't announce seconds */
    	q->servicelevel = 0;
    
    	q->announce_to_first_user = 0;
    
    	q->setqueuevar = 0;
    	q->setqueueentryvar = 0;
    
    	q->reportholdtime = 0;
    	q->wrapuptime = 0;
    
    David Vossel's avatar
     
    David Vossel committed
    	q->penaltymemberslimit = 0;
    
    	q->joinempty = 0;
    	q->leavewhenempty = 0;
    	q->memberdelay = 0;
    	q->weight = 0;
    	q->timeoutrestart = 0;
    
    	q->periodicannouncefrequency = 0;
    
    	q->randomperiodicannounce = 0;
    	q->numperiodicannounce = 0;
    
    	q->timeoutpriority = TIMEOUT_PRIORITY_APP;
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	q->autopausedelay = 0;
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->strategy == QUEUE_STRATEGY_LINEAR || q->strategy == QUEUE_STRATEGY_RRORDERED) {
    
    			/* linear strategy depends on order, so we have to place all members in a single bucket */
    			q->members = ao2_container_alloc(1, member_hash_fn, member_cmp_fn);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else {
    
    			q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
    
    
    	ast_string_field_set(q, sound_next, "queue-youarenext");
    	ast_string_field_set(q, sound_thereare, "queue-thereare");
    	ast_string_field_set(q, sound_calls, "queue-callswaiting");
    
    	ast_string_field_set(q, queue_quantity1, "queue-quantity1");
    	ast_string_field_set(q, queue_quantity2, "queue-quantity2");
    
    	ast_string_field_set(q, sound_holdtime, "queue-holdtime");
    	ast_string_field_set(q, sound_minutes, "queue-minutes");
    
    	ast_string_field_set(q, sound_minute, "queue-minute");
    
    	ast_string_field_set(q, sound_seconds, "queue-seconds");
    	ast_string_field_set(q, sound_thanks, "queue-thankyou");
    	ast_string_field_set(q, sound_reporthold, "queue-reporthold");
    
    
    	if (!q->sound_periodicannounce[0]) {
    		q->sound_periodicannounce[0] = ast_str_create(32);
    	}
    
    	if (q->sound_periodicannounce[0]) {
    
    		ast_str_set(&q->sound_periodicannounce[0], 0, "queue-periodic-announce");
    
    
    	for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->sound_periodicannounce[i]) {
    
    			ast_str_set(&q->sound_periodicannounce[i], 0, "%s", "");
    
    Olle Johansson's avatar
    Olle Johansson committed
    	while ((pr_iter = AST_LIST_REMOVE_HEAD(&q->rules,list))) {
    
    
    	/* On restart assume no members are available.
    	 * The queue_avail hint is a boolean state to indicate whether a member is available or not.
    	 *
    	 * This seems counter intuitive, but is required to light a BLF
    	 * AST_DEVICE_INUSE indicates no members are available.
    	 * AST_DEVICE_NOT_INUSE indicates a member is available.
    	 */
    
    	ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s_avail", q->name);
    
    static void clear_queue(struct call_queue *q)
    
    {
    	q->holdtime = 0;
    	q->callscompleted = 0;
    	q->callsabandoned = 0;
    	q->callscompletedinsl = 0;
    
    	q->talktime = 0;
    
    	if (q->members) {
    		struct member *mem;
    		struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
    		while ((mem = ao2_iterator_next(&mem_iter))) {
    			mem->calls = 0;
    
    		ao2_iterator_destroy(&mem_iter);
    
     * \brief Change queue penalty by adding rule.
     *
    
     * Check rule for errors with time or fomatting, see if rule is relative to rest
    
     * of queue, iterate list of rules to find correct insertion point, insert and return.
     * \retval -1 on failure
    
     * \retval 0 on success
     * \note Call this with the rule_lists locked
    
    static int insert_penaltychange(const char *list_name, const char *content, const int linenum)
    
    {
    	char *timestr, *maxstr, *minstr, *contentdup;
    	struct penalty_rule *rule = NULL, *rule_iter;
    	struct rule_list *rl_iter;
    
    	int penaltychangetime, inserted = 0;
    
    
    	if (!(rule = ast_calloc(1, sizeof(*rule)))) {
    		return -1;
    	}
    
    	contentdup = ast_strdupa(content);
    
    	if (!(maxstr = strchr(contentdup, ','))) {
    		ast_log(LOG_WARNING, "Improperly formatted penaltychange rule at line %d. Ignoring.\n", linenum);
    		ast_free(rule);
    		return -1;
    	}
    
    	*maxstr++ = '\0';
    	timestr = contentdup;
    
    
    	if ((penaltychangetime = atoi(timestr)) < 0) {
    
    		ast_log(LOG_WARNING, "Improper time parameter specified for penaltychange rule at line %d. Ignoring.\n", linenum);
    		ast_free(rule);
    		return -1;
    	}
    
    
    	rule->time = penaltychangetime;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if ((minstr = strchr(maxstr,','))) {
    
    	/* The last check will evaluate true if either no penalty change is indicated for a given rule
    	 * OR if a min penalty change is indicated but no max penalty change is */
    	if (*maxstr == '+' || *maxstr == '-' || *maxstr == '\0') {
    		rule->max_relative = 1;
    	}
    
    	rule->max_value = atoi(maxstr);
    
    	if (!ast_strlen_zero(minstr)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (*minstr == '+' || *minstr == '-') {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else { /*there was no minimum specified, so assume this means no change*/
    
    
    	/*We have the rule made, now we need to insert it where it belongs*/
    	AST_LIST_TRAVERSE(&rule_lists, rl_iter, list){
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (strcasecmp(rl_iter->name, list_name)) {
    
    
    		AST_LIST_TRAVERSE_SAFE_BEGIN(&rl_iter->rules, rule_iter, list) {
    			if (rule->time < rule_iter->time) {
    				AST_LIST_INSERT_BEFORE_CURRENT(rule, list);
    				inserted = 1;
    				break;
    			}
    		}
    		AST_LIST_TRAVERSE_SAFE_END;
    
    		if (!inserted) {
    			AST_LIST_INSERT_TAIL(&rl_iter->rules, rule, list);
    
    			inserted = 1;
    
    	if (!inserted) {
    		ast_log(LOG_WARNING, "Unknown rule list name %s; ignoring.\n", list_name);
    		ast_free(rule);
    		return -1;
    	}
    
    static void parse_empty_options(const char *value, enum empty_conditions *empty, int joinempty)
    
    {
    	char *value_copy = ast_strdupa(value);
    	char *option = NULL;
    	while ((option = strsep(&value_copy, ","))) {
    		if (!strcasecmp(option, "paused")) {
    			*empty |= QUEUE_EMPTY_PAUSED;
    		} else if (!strcasecmp(option, "penalty")) {
    			*empty |= QUEUE_EMPTY_PENALTY;
    		} else if (!strcasecmp(option, "inuse")) {
    			*empty |= QUEUE_EMPTY_INUSE;
    		} else if (!strcasecmp(option, "ringing")) {
    			*empty |= QUEUE_EMPTY_RINGING;
    		} else if (!strcasecmp(option, "invalid")) {
    			*empty |= QUEUE_EMPTY_INVALID;
    		} else if (!strcasecmp(option, "wrapup")) {
    			*empty |= QUEUE_EMPTY_WRAPUP;
    		} else if (!strcasecmp(option, "unavailable")) {
    			*empty |= QUEUE_EMPTY_UNAVAILABLE;
    		} else if (!strcasecmp(option, "unknown")) {
    			*empty |= QUEUE_EMPTY_UNKNOWN;
    		} else if (!strcasecmp(option, "loose")) {
    			*empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID);
    		} else if (!strcasecmp(option, "strict")) {
    			*empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID | QUEUE_EMPTY_PAUSED | QUEUE_EMPTY_UNAVAILABLE);
    
    		} else if ((ast_false(option) && joinempty) || (ast_true(option) && !joinempty)) {
    
    			*empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID | QUEUE_EMPTY_PAUSED);
    
    		} else if ((ast_false(option) && !joinempty) || (ast_true(option) && joinempty)) {
    
    		} else {
    			ast_log(LOG_WARNING, "Unknown option %s for '%s'\n", option, joinempty ? "joinempty" : "leavewhenempty");
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*! \brief Configure a queue parameter.
    
     * The failunknown flag is set for config files (and static realtime) to show
     * errors for unknown parameters. It is cleared for dynamic realtime to allow
     *  extra fields in the tables.
     * \note For error reporting, line number is passed for .conf static configuration,
     * for Realtime queues, linenum is -1.
    */
    
    static void queue_set_param(struct call_queue *q, const char *param, const char *val, int linenum, int failunknown)
    
    	if (!strcasecmp(param, "musicclass") ||
    
    		!strcasecmp(param, "music") || !strcasecmp(param, "musiconhold")) {
    
    	} else if (!strcasecmp(param, "announce")) {
    
    		ast_string_field_set(q, announce, val);
    
    	} else if (!strcasecmp(param, "context")) {
    
    		ast_string_field_set(q, context, val);
    
    	} else if (!strcasecmp(param, "timeout")) {
    		q->timeout = atoi(val);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->timeout < 0) {
    
    	} else if (!strcasecmp(param, "ringinuse")) {
    		q->ringinuse = ast_true(val);
    
    	} else if (!strcasecmp(param, "setinterfacevar")) {
    		q->setinterfacevar = ast_true(val);
    
    	} else if (!strcasecmp(param, "setqueuevar")) {
    		q->setqueuevar = ast_true(val);
    	} else if (!strcasecmp(param, "setqueueentryvar")) {
    		q->setqueueentryvar = ast_true(val);
    
    	} else if (!strcasecmp(param, "monitor-format")) {
    		ast_copy_string(q->monfmt, val, sizeof(q->monfmt));
    
    	} else if (!strcasecmp(param, "membermacro")) {
    
    		ast_string_field_set(q, membermacro, val);
    
    	} else if (!strcasecmp(param, "membergosub")) {
    
    		ast_string_field_set(q, membergosub, val);
    
    	} else if (!strcasecmp(param, "queue-youarenext")) {
    
    		ast_string_field_set(q, sound_next, val);
    
    	} else if (!strcasecmp(param, "queue-thereare")) {
    
    		ast_string_field_set(q, sound_thereare, val);
    
    	} else if (!strcasecmp(param, "queue-callswaiting")) {
    
    		ast_string_field_set(q, sound_calls, val);
    
    	} else if (!strcasecmp(param, "queue-quantity1")) {
    		ast_string_field_set(q, queue_quantity1, val);
    	} else if (!strcasecmp(param, "queue-quantity2")) {
    		ast_string_field_set(q, queue_quantity2, val);
    
    	} else if (!strcasecmp(param, "queue-holdtime")) {
    
    		ast_string_field_set(q, sound_holdtime, val);
    
    	} else if (!strcasecmp(param, "queue-minutes")) {
    
    		ast_string_field_set(q, sound_minutes, val);
    
    	} else if (!strcasecmp(param, "queue-minute")) {
    		ast_string_field_set(q, sound_minute, val);
    
    	} else if (!strcasecmp(param, "queue-seconds")) {
    
    		ast_string_field_set(q, sound_seconds, val);
    
    	} else if (!strcasecmp(param, "queue-thankyou")) {
    
    		ast_string_field_set(q, sound_thanks, val);
    
    	} else if (!strcasecmp(param, "queue-callerannounce")) {
    
    		ast_string_field_set(q, sound_callerannounce, val);
    
    	} else if (!strcasecmp(param, "queue-reporthold")) {
    
    		ast_string_field_set(q, sound_reporthold, val);
    
    	} else if (!strcasecmp(param, "announce-frequency")) {
    		q->announcefrequency = atoi(val);
    
    	} else if (!strcasecmp(param, "announce-to-first-user")) {
    		q->announce_to_first_user = ast_true(val);
    
    	} else if (!strcasecmp(param, "min-announce-frequency")) {
    		q->minannouncefrequency = atoi(val);
    
    		ast_debug(1, "%s=%s for queue '%s'\n", param, val, q->name);
    
    	} else if (!strcasecmp(param, "announce-round-seconds")) {
    		q->roundingseconds = atoi(val);
    
    		/* Rounding to any other values just doesn't make sense... */
    
    		if (!(q->roundingseconds == 0 || q->roundingseconds == 5 || q->roundingseconds == 10
    
    			|| q->roundingseconds == 15 || q->roundingseconds == 20 || q->roundingseconds == 30)) {
    
    			if (linenum >= 0) {
    				ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
    					"using 0 instead for queue '%s' at line %d of queues.conf\n",
    					val, param, q->name, linenum);
    			} else {
    				ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
    					"using 0 instead for queue '%s'\n", val, param, q->name);
    			}
    			q->roundingseconds=0;
    		}
    	} else if (!strcasecmp(param, "announce-holdtime")) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (!strcasecmp(val, "once")) {
    
    			q->announceholdtime = ANNOUNCEHOLDTIME_ONCE;
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else if (ast_true(val)) {
    
    			q->announceholdtime = ANNOUNCEHOLDTIME_ALWAYS;
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else {
    
    	} else if (!strcasecmp(param, "announce-position")) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (!strcasecmp(val, "limit")) {
    
    			q->announceposition = ANNOUNCEPOSITION_LIMIT;
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else if (!strcasecmp(val, "more")) {
    
    			q->announceposition = ANNOUNCEPOSITION_MORE_THAN;
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else if (ast_true(val)) {
    
    			q->announceposition = ANNOUNCEPOSITION_YES;
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else {
    
    			q->announceposition = ANNOUNCEPOSITION_NO;
    
    	} else if (!strcasecmp(param, "announce-position-limit")) {
    		q->announcepositionlimit = atoi(val);
    
    	} else if (!strcasecmp(param, "periodic-announce")) {
    
    			char *s, *buf = ast_strdupa(val);
    			unsigned int i = 0;
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (!q->sound_periodicannounce[i]) {
    
    					q->sound_periodicannounce[i] = ast_str_create(16);
    
    				ast_str_set(&q->sound_periodicannounce[i], 0, "%s", s);
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (i == MAX_PERIODIC_ANNOUNCEMENTS) {
    
    			ast_str_set(&q->sound_periodicannounce[0], 0, "%s", val);
    
    	} else if (!strcasecmp(param, "periodic-announce-frequency")) {
    		q->periodicannouncefrequency = atoi(val);
    
    	} else if (!strcasecmp(param, "relative-periodic-announce")) {
    		q->relativeperiodicannounce = ast_true(val);
    
    	} else if (!strcasecmp(param, "random-periodic-announce")) {
    		q->randomperiodicannounce = ast_true(val);
    
    	} else if (!strcasecmp(param, "retry")) {
    		q->retry = atoi(val);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->retry <= 0) {
    
    	} else if (!strcasecmp(param, "wrapuptime")) {
    		q->wrapuptime = atoi(val);
    
    David Vossel's avatar
     
    David Vossel committed
    	} else if (!strcasecmp(param, "penaltymemberslimit")) {
    
    		if ((sscanf(val, "%10d", &q->penaltymemberslimit) != 1)) {
    			q->penaltymemberslimit = 0;
    		}
    
    	} else if (!strcasecmp(param, "autofill")) {
    		q->autofill = ast_true(val);
    
    	} else if (!strcasecmp(param, "monitor-type")) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (!strcasecmp(val, "mixmonitor")) {
    
    	} else if (!strcasecmp(param, "autopause")) {
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	} else if (!strcasecmp(param, "autopausedelay")) {
    		q->autopausedelay = atoi(val);
    
    	} else if (!strcasecmp(param, "autopausebusy")) {
    		q->autopausebusy = ast_true(val);
    	} else if (!strcasecmp(param, "autopauseunavail")) {
    		q->autopauseunavail = ast_true(val);
    
    	} else if (!strcasecmp(param, "maxlen")) {
    		q->maxlen = atoi(val);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->maxlen < 0) {
    
    	} else if (!strcasecmp(param, "servicelevel")) {
    		q->servicelevel= atoi(val);
    	} else if (!strcasecmp(param, "strategy")) {
    
    		int strategy;
    
    		/* We are a static queue and already have set this, no need to do it again */
    		if (failunknown) {
    			return;
    		}
    		strategy = strat2int(val);
    		if (strategy < 0) {
    			ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
    				val, q->name);
    			q->strategy = QUEUE_STRATEGY_RINGALL;
    		}
    		if (strategy == q->strategy) {
    			return;
    		}
    		if (strategy == QUEUE_STRATEGY_LINEAR) {
    			ast_log(LOG_WARNING, "Changing to the linear strategy currently requires asterisk to be restarted.\n");
    			return;
    		}
    		q->strategy = strategy;
    
    	} else if (!strcasecmp(param, "joinempty")) {
    
    		parse_empty_options(val, &q->joinempty, 1);
    
    	} else if (!strcasecmp(param, "leavewhenempty")) {
    
    		parse_empty_options(val, &q->leavewhenempty, 0);
    
    	} else if (!strcasecmp(param, "reportholdtime")) {
    		q->reportholdtime = ast_true(val);
    	} else if (!strcasecmp(param, "memberdelay")) {
    		q->memberdelay = atoi(val);
    	} else if (!strcasecmp(param, "weight")) {
    		q->weight = atoi(val);
    	} else if (!strcasecmp(param, "timeoutrestart")) {
    		q->timeoutrestart = ast_true(val);
    
    	} else if (!strcasecmp(param, "defaultrule")) {
    		ast_string_field_set(q, defaultrule, val);
    
    	} else if (!strcasecmp(param, "timeoutpriority")) {
    		if (!strcasecmp(val, "conf")) {
    			q->timeoutpriority = TIMEOUT_PRIORITY_CONF;
    		} else {
    			q->timeoutpriority = TIMEOUT_PRIORITY_APP;
    		}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	} else if (failunknown) {
    
    		if (linenum >= 0) {
    			ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n",
    				q->name, param, linenum);
    		} else {
    			ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s\n", q->name, param);
    		}
    	}
    }
    
    
    /*! \internal
     * \brief If adding a single new member to a queue, use this function instead of ao2_linking.
     *        This adds round robin queue position data for a fresh member as well as links it.
     * \param queue Which queue the member is being added to
     * \param mem Which member is being added to the queue
     */
    static void member_add_to_queue(struct call_queue *queue, struct member *mem)
    {
    	ao2_lock(queue->members);
    	mem->queuepos = ao2_container_count(queue->members);
    	ao2_link(queue->members, mem);
    	ao2_unlock(queue->members);
    }
    
    /*! \internal
     * \brief If removing a single member from a queue, use this function instead of ao2_unlinking.
     *        This will perform round robin queue position reordering for the remaining members.
     * \param queue Which queue the member is being removed from
     * \param member Which member is being removed from the queue
     */
    static void member_remove_from_queue(struct call_queue *queue, struct member *mem)
    {
    	ao2_lock(queue->members);
    	queue_member_follower_removal(queue, mem);
    	ao2_unlink(queue->members, mem);
    	ao2_unlock(queue->members);
    }
    
    
    /*!
     * \brief Find rt member record to update otherwise create one.
     *
     * Search for member in queue, if found update penalty/paused state,
    
     * if no member exists create one flag it as a RT member and add to queue member list.
    
    static void rt_handle_member_record(struct call_queue *q, char *interface, struct ast_config *member_config)
    
    	struct member *m;
    	struct ao2_iterator mem_iter;
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	const char *config_val;
    
    	const char *rt_uniqueid = ast_variable_retrieve(member_config, interface, "uniqueid");
    	const char *membername = S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface);
    	const char *state_interface = S_OR(ast_variable_retrieve(member_config, interface, "state_interface"), interface);
    	const char *penalty_str = ast_variable_retrieve(member_config, interface, "penalty");
    	const char *paused_str = ast_variable_retrieve(member_config, interface, "paused");
    
    
    	if (ast_strlen_zero(rt_uniqueid)) {
    
    		ast_log(LOG_WARNING, "Realtime field uniqueid is empty for member %s\n", S_OR(membername, "NULL"));
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		if ((penalty < 0) && negative_penalty_invalid) {
    			return;
    		} else if (penalty < 0) {
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		}
    
    	if (paused_str) {
    		paused = atoi(paused_str);
    
    		if (paused < 0) {
    
    	if ((config_val = ast_variable_retrieve(member_config, interface, realtime_ringinuse_field))) {
    		if (ast_true(config_val)) {
    			ringinuse = 1;
    		} else if (ast_false(config_val)) {
    			ringinuse = 0;
    		} else {
    			ast_log(LOG_WARNING, "Invalid value of '%s' field for %s in queue '%s'\n", realtime_ringinuse_field, interface, q->name);
    		}
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	}
    
    	/* Find member by realtime uniqueid and update */
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    		if (!strcasecmp(m->rt_uniqueid, rt_uniqueid)) {
    			m->dead = 0;	/* Do not delete this one. */
    			ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid));
    
    			if (paused_str) {
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    				m->paused = paused;
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    			if (strcasecmp(state_interface, m->state_interface)) {
    				ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface));
    			}
    			m->penalty = penalty;
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    			found = 1;
    			ao2_ref(m, -1);
    			break;
    		}
    		ao2_ref(m, -1);
    	}
    
    	ao2_iterator_destroy(&mem_iter);
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	/* Create a new member */
    	if (!found) {
    
    		if ((m = create_queue_member(interface, membername, penalty, paused, state_interface, ringinuse))) {
    
    			m->realtime = 1;
    
    			ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid));
    
    				ast_queue_log(q->name, "REALTIME", m->interface, "ADDMEMBER", "%s", paused ? "PAUSED" : "");
    
    				ast_queue_log(q->name, "REALTIME", m->membername, "ADDMEMBER", "%s", paused ? "PAUSED" : "");
    
    			ao2_ref(m, -1);
    			m = NULL;
    
    /*! \brief Iterate through queue's member list and delete them */
    
    static void free_members(struct call_queue *q, int all)
    
    	struct member *cur;
    
    	struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
    
    	while ((cur = ao2_iterator_next(&mem_iter))) {
    
    		if (all || !cur->dynamic) {
    
    	ao2_iterator_destroy(&mem_iter);
    
    /*! \brief Free queue's member list then its string fields */
    
    	for (i = 0; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (q->sound_periodicannounce[i]) {
    
    	ao2_ref(q->members, -1);
    
    }
    
    static struct call_queue *alloc_queue(const char *queuename)
    {
    	struct call_queue *q;
    
    
    	if ((q = ao2_t_alloc(sizeof(*q), destroy_queue, "Allocate queue"))) {
    
    			queue_t_unref(q, "String field allocation failed");
    
    			return NULL;
    		}
    		ast_string_field_set(q, name, queuename);
    
    /*!
     * \brief Reload a single queue via realtime.
     *
     * Check for statically defined queue first, check if deleted RT queue,
     * check for new RT queue, if queue vars are not defined init them with defaults.
     * reload RT queue vars, set RT queue members dead and reload them, return finished queue.
    
     * \retval the queue,
    
     * \note Should be called with the "queues" container locked.
    
    static struct call_queue *find_queue_by_name_rt(const char *queuename, struct ast_variable *queue_vars, struct ast_config *member_config)
    
    		.name = queuename,
    
    	struct member *m;
    
    	struct ao2_iterator mem_iter;
    
    	char tmpbuf[64];	/* Must be longer than the longest queue param name. */
    
    	/* Static queues override realtime. */
    
    	if ((q = ao2_t_find(queues, &tmpq, OBJ_POINTER, "Check if static queue exists"))) {
    
    				queue_t_unref(q, "Queue is dead; can't return it");
    
    Olle Johansson's avatar
    Olle Johansson committed
    			ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name);
    			ao2_unlock(q);
    			return q;
    
    	} else if (!member_config) {
    
    		/* Not found in the list, and it's not realtime ... */
    		return NULL;
    
    	/* Check if queue is defined in realtime. */
    	if (!queue_vars) {
    		/* Delete queue from in-core list if it has been deleted in realtime. */
    		if (q) {
    
    Russell Bryant's avatar
    Russell Bryant committed
    			/*! \note Hmm, can't seem to distinguish a DB failure from a not
    
    			   found condition... So we might delete an in-core queue
    			   in case of DB failure. */
    
    			ast_debug(1, "Queue %s not found in realtime.\n", queuename);