Skip to content
Snippets Groups Projects
app_queue.c 331 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			q->dead = 1;
    			/* Delete if unused (else will be deleted when last caller leaves). */
    
    			queues_t_unlink(queues, q, "Unused; removing from container");
    
    			queue_t_unref(q, "Queue is dead; can't return it");
    
    		}
    		return NULL;
    	}
    
    	/* Create a new queue if an in-core entry does not exist yet. */
    	if (!q) {
    
    		if (!(q = alloc_queue(queuename))) {
    
    		/*Before we initialize the queue, we need to set the strategy, so that linear strategy
    		 * will allocate the members properly
    		 */
    		for (tmpvar = queue_vars; tmpvar; tmpvar = tmpvar->next) {
    
    Mark Michelson's avatar
    Mark Michelson committed
    			if (!strcasecmp(tmpvar->name, "strategy")) {
    
    				q->strategy = strat2int(tmpvar->value);
    				if (q->strategy < 0) {
    					ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
    					tmpvar->value, q->name);
    					q->strategy = QUEUE_STRATEGY_RINGALL;
    				}
    				break;
    			}
    		}
    		/* We traversed all variables and didn't find a strategy */
    
    		if (!tmpvar) {
    
    		queues_t_link(queues, q, "Add queue to container");
    
    	init_queue(q);		/* Ensure defaults for all parameters not set explicitly. */
    
    	for (v = queue_vars; v; v = v->next) {
    
    		/* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */
    
    			ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
    			tmp_name = tmpbuf;
    
    Olle Johansson's avatar
    Olle Johansson committed
    			while ((tmp = strchr(tmp, '_'))) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    			}
    		} else {
    
    		/* NULL values don't get returned from realtime; blank values should
    		 * still get set.  If someone doesn't want a value to be set, they
    		 * should set the realtime column to NULL, not blank. */
    		queue_set_param(q, tmp_name, v->value, -1, 0);
    
    	/* Temporarily set realtime members dead so we can detect deleted ones. */
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    
    		if (m->realtime) {
    
    	ao2_iterator_destroy(&mem_iter);
    
    	while ((interface = ast_category_browse(member_config, interface))) {
    
    		rt_handle_member_record(q, interface, member_config);
    
    
    	/* Delete all realtime members that have been deleted in DB. */
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    
    			if (ast_strlen_zero(m->membername) || !log_membername_as_agent) {
    				ast_queue_log(q->name, "REALTIME", m->interface, "REMOVEMEMBER", "%s", "");
    			} else {
    				ast_queue_log(q->name, "REALTIME", m->membername, "REMOVEMEMBER", "%s", "");
    			}
    
    	ao2_iterator_destroy(&mem_iter);
    
    /*!
     * note  */
    
    /*!
     * \internal
     * \brief Returns reference to the named queue. If the queue is realtime, it will load the queue as well.
     * \param queuename - name of the desired queue
     *
     * \retval the queue
     * \retval NULL if it doesn't exist
     */
    static struct call_queue *find_load_queue_rt_friendly(const char *queuename)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct ast_variable *queue_vars;
    
    	struct ast_config *member_config = NULL;
    
    	struct call_queue *q = NULL, tmpq = {
    
    		.name = queuename,
    
    	int prev_weight = 0;
    
    
    	/* Find the queue in the in-core list first. */
    
    	q = ao2_t_find(queues, &tmpq, OBJ_POINTER, "Look for queue in memory first");
    
    	if (!q || q->realtime) {
    
    		/*! \note Load from realtime before taking the "queues" container lock, to avoid blocking all
    
    		   queue operations while waiting for the DB.
    
    		   This will be two separate database transactions, so we might
    		   see queue parameters as they were before another process
    		   changed the queue and member list as it was after the change.
    		   Thus we might see an empty member list when a queue is
    		   deleted. In practise, this is unlikely to cause a problem. */
    
    
    		queue_vars = ast_load_realtime("queues", "name", queuename, SENTINEL);
    
    			member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, SENTINEL);
    
    				ast_debug(1, "No queue_members defined in config extconfig.conf\n");
    				member_config = ast_config_new();
    
    		if (q) {
    			prev_weight = q->weight ? 1 : 0;
    
    			queue_t_unref(q, "Need to find realtime queue");
    
    
    		q = find_queue_by_name_rt(queuename, queue_vars, member_config);
    
    		ast_config_destroy(member_config);
    		ast_variables_destroy(queue_vars);
    
    
    		/* update the use_weight value if the queue's has gained or lost a weight */
    
    		if (q) {
    			if (!q->weight && prev_weight) {
    				ast_atomic_fetchadd_int(&use_weight, -1);
    			}
    			if (q->weight && !prev_weight) {
    				ast_atomic_fetchadd_int(&use_weight, +1);
    			}
    
    		}
    		/* Other cases will end up with the proper value for use_weight */
    
    	} else {
    		update_realtime_members(q);
    
    static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value)
    {
    	int ret = -1;
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (ast_strlen_zero(mem->rt_uniqueid)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if ((ast_update_realtime("queue_members", "uniqueid", mem->rt_uniqueid, field, value, SENTINEL)) > 0) {
    
    static void update_realtime_members(struct call_queue *q)
    {
    	struct ast_config *member_config = NULL;
    
    	struct member *m;
    
    	char *interface = NULL;
    
    	struct ao2_iterator mem_iter;
    
    	if (!(member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , SENTINEL))) {
    
    		/* This queue doesn't have realtime members. If the queue still has any realtime
    		 * members in memory, they need to be removed.
    		 */
    		ao2_lock(q);
    		mem_iter = ao2_iterator_init(q->members, 0);
    		while ((m = ao2_iterator_next(&mem_iter))) {
    			if (m->realtime) {
    
    		ast_debug(3, "Queue %s has no realtime members defined. No need for update\n", q->name);
    
    
    	/* Temporarily set realtime  members dead so we can detect deleted ones.*/
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (m->realtime) {
    
    	ao2_iterator_destroy(&mem_iter);
    
    
    	while ((interface = ast_category_browse(member_config, interface))) {
    
    		rt_handle_member_record(q, interface, member_config);
    
    	}
    
    	/* Delete all realtime members that have been deleted in DB. */
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    
    			if (ast_strlen_zero(m->membername) || !log_membername_as_agent) {
    				ast_queue_log(q->name, "REALTIME", m->interface, "REMOVEMEMBER", "%s", "");
    			} else {
    				ast_queue_log(q->name, "REALTIME", m->membername, "REMOVEMEMBER", "%s", "");
    			}
    
    	ao2_iterator_destroy(&mem_iter);
    
    	ast_config_destroy(member_config);
    
    static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason, int position)
    
    	struct call_queue *q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct queue_ent *cur, *prev = NULL;
    	int res = -1;
    	int pos = 0;
    
    	if (!(q = find_load_queue_rt_friendly(queuename))) {
    
    	if (q->joinempty) {
    		int status = 0;
    		if ((status = get_member_status(q, qe->max_penalty, qe->min_penalty, q->joinempty))) {
    
    			*reason = QUEUE_JOINEMPTY;
    
    			queue_t_unref(q, "Done with realtime queue");
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (*reason == QUEUE_UNKNOWN && q->maxlen && (q->count >= q->maxlen)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else if (*reason == QUEUE_UNKNOWN) {
    
    		RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
    
    
    		/* There's space for us, put us at the right position inside
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		 * the queue.
    
    		 * Take into account the priority of the calling user */
    		inserted = 0;
    		prev = NULL;
    		cur = q->head;
    
    			/* We have higher priority than the current user, enter
    			 * before him, after all the other users with priority
    			 * higher or equal to our priority. */
    			if ((!inserted) && (qe->prio > cur->prio)) {
    				insert_entry(q, prev, qe, &pos);
    				inserted = 1;
    			}
    
    			/* <= is necessary for the position comparison because it may not be possible to enter
    			 * at our desired position since higher-priority callers may have taken the position we want
    			 */
    
    			if (!inserted && (qe->prio >= cur->prio) && position && (position <= pos + 1)) {
    
    				insert_entry(q, prev, qe, &pos);
    
    				/*pos is incremented inside insert_entry, so don't need to add 1 here*/
    				if (position < pos) {
    					ast_log(LOG_NOTICE, "Asked to be inserted at position %d but forced into position %d due to higher priority callers\n", position, pos);
    				}
    			}
    
    			cur->pos = ++pos;
    			prev = cur;
    			cur = cur->next;
    		}
    		/* No luck, join at the end of the queue */
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (!inserted) {
    
    		ast_copy_string(qe->moh, q->moh, sizeof(qe->moh));
    		ast_copy_string(qe->announce, q->announce, sizeof(qe->announce));
    		ast_copy_string(qe->context, q->context, sizeof(qe->context));
    		q->count++;
    
    		if (q->count == 1) {
    
    			ast_devstate_changed(AST_DEVICE_RINGING, AST_DEVSTATE_CACHABLE, "Queue:%s", q->name);
    
    
    		blob = ast_json_pack("{s: s, s: i, s: i}",
    				     "Queue", q->name,
    				     "Position", qe->pos,
    				     "Count", q->count);
    		ast_channel_publish_blob(qe->chan, queue_caller_join_type(), blob);
    
    		ast_debug(1, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, ast_channel_name(qe->chan), qe->pos );
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	queue_t_unref(q, "Done with realtime queue");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int play_file(struct ast_channel *chan, const char *filename)
    
    	if (ast_strlen_zero(filename)) {
    		return 0;
    	}
    
    
    	if (!ast_fileexists(filename, NULL, ast_channel_language(chan))) {
    
    	ast_stopstream(chan);
    
    	res = ast_streamfile(chan, filename, ast_channel_language(chan));
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (!res) {
    
    	ast_stopstream(chan);
    
    	return res;
    }
    
    
    /*!
     * \brief Check for valid exit from queue via goto
     * \retval 0 if failure
     * \retval 1 if successful
    */
    
    	/* Prevent possible buffer overflow */
    	if (digitlen < sizeof(qe->digits) - 2) {
    		qe->digits[digitlen] = digit;
    		qe->digits[digitlen + 1] = '\0';
    	} else {
    		qe->digits[0] = '\0';
    		return 0;
    	}
    
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	/* If there's no context to goto, short-circuit */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (ast_strlen_zero(qe->context)) {
    
    
    	/* If the extension is bad, then reset the digits to blank */
    
    	if (!ast_canmatch_extension(qe->chan, qe->context, qe->digits, 1,
    
    		S_COR(ast_channel_caller(qe->chan)->id.number.valid, ast_channel_caller(qe->chan)->id.number.str, NULL))) {
    
    		qe->digits[0] = '\0';
    		return 0;
    	}
    
    	/* We have an exact match */
    
    	if (!ast_goto_if_exists(qe->chan, qe->context, qe->digits, 1)) {
    
    		qe->valid_digits = 1;
    
    		/* Return 1 on a successful goto */
    
    static int say_position(struct queue_ent *qe, int ringing)
    
    	int res = 0, avgholdmins, avgholdsecs, announceposition = 0;
    
    	/* Let minannouncefrequency seconds pass between the start of each position announcement */
    
    	time(&now);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if ((now - qe->last_pos) < qe->parent->minannouncefrequency) {
    
    
    	/* If either our position has changed, or we are over the freq timer, say position */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if ((qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency)) {
    
    	if (ringing) {
    		ast_indicate(qe->chan,-1);
    	} else {
    		ast_moh_stop(qe->chan);
    	}
    
    
    	if (qe->parent->announceposition == ANNOUNCEPOSITION_YES ||
    		qe->parent->announceposition == ANNOUNCEPOSITION_MORE_THAN ||
    		(qe->parent->announceposition == ANNOUNCEPOSITION_LIMIT &&
    
    Olle Johansson's avatar
    Olle Johansson committed
    		qe->pos <= qe->parent->announcepositionlimit)) {
    
    		/* Say we're next, if we are */
    		if (qe->pos == 1) {
    			res = play_file(qe->chan, qe->parent->sound_next);
    
    Olle Johansson's avatar
    Olle Johansson committed
    			if (res) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    			}
    			goto posout;
    
    			if (qe->parent->announceposition == ANNOUNCEPOSITION_MORE_THAN && qe->pos > qe->parent->announcepositionlimit){
    				/* More than Case*/
    				res = play_file(qe->chan, qe->parent->queue_quantity1);
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (res) {
    
    				res = ast_say_number(qe->chan, qe->parent->announcepositionlimit, AST_DIGIT_ANY, ast_channel_language(qe->chan), NULL); /* Needs gender */
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (res) {
    
    			} else {
    				/* Normal Case */
    				res = play_file(qe->chan, qe->parent->sound_thereare);
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (res) {
    
    				res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, ast_channel_language(qe->chan), NULL); /* Needs gender */
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (res) {
    
    			}
    			if (qe->parent->announceposition == ANNOUNCEPOSITION_MORE_THAN && qe->pos > qe->parent->announcepositionlimit){
    				/* More than Case*/
    				res = play_file(qe->chan, qe->parent->queue_quantity2);
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (res) {
    
    			} else {
    				res = play_file(qe->chan, qe->parent->sound_calls);
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (res) {
    
    	}
    	/* Round hold time to nearest minute */
    
    	avgholdmins = abs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60);
    
    
    	/* If they have specified a rounding then round the seconds as well */
    
    	if (qe->parent->roundingseconds) {
    		avgholdsecs = (abs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
    		avgholdsecs *= qe->parent->roundingseconds;
    
    	ast_verb(3, "Hold time for %s is %d minute(s) %d seconds\n", qe->parent->name, avgholdmins, avgholdsecs);
    
    
    	/* If the hold time is >1 min, if it's enabled, and if it's not
    	   supposed to be only once and we have already said it, say it */
    
        if ((avgholdmins+avgholdsecs) > 0 && qe->parent->announceholdtime &&
            ((qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE && !qe->last_pos) ||
            !(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE))) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (res) {
    
    			res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, ast_channel_language(qe->chan), NULL);
    
    Olle Johansson's avatar
    Olle Johansson committed
    			if (res) {
    
    			if (avgholdmins == 1) {
    				res = play_file(qe->chan, qe->parent->sound_minute);
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (res) {
    
    				res = play_file(qe->chan, qe->parent->sound_minutes);
    
    Olle Johansson's avatar
    Olle Johansson committed
    				if (res) {
    
    			res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, ast_channel_language(qe->chan), NULL);
    
    Olle Johansson's avatar
    Olle Johansson committed
    			if (res) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    			if (res) {
    
    	} else if (qe->parent->announceholdtime && !qe->parent->announceposition) {
    		say_thanks = 0;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    posout:
    
    	if (qe->parent->announceposition) {
    		ast_verb(3, "Told %s in %s their queue position (which was %d)\n",
    
    			ast_channel_name(qe->chan), qe->parent->name, qe->pos);
    
    		res = play_file(qe->chan, qe->parent->sound_thanks);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    playout:
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if ((res > 0 && !valid_exit(qe, res))) {
    
    	/* Set our last_pos indicators */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	qe->last_pos = now;
    
    	qe->last_pos_said = qe->pos;
    
    	/* Don't restart music on hold if we're about to exit the caller from the queue */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (ringing) {
    			ast_indicate(qe->chan, AST_CONTROL_RINGING);
    		} else {
    			ast_moh_start(qe->chan, qe->moh, NULL);
    		}
    
    static void recalc_holdtime(struct queue_ent *qe, int newholdtime)
    
    	/* Calculate holdtime using an exponential average */
    
    	/* Thanks to SRT for this contribution */
    	/* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
    
    
    	oldvalue = qe->parent->holdtime;
    
    	qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
    
     * Search the queue to find the leaving client, if found remove from queue
     * create manager event, move others up the queue.
    */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void leave_queue(struct queue_ent *qe)
    {
    
    	struct call_queue *q;
    
    	struct queue_ent *current, *prev = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int pos = 0;
    
    	if (!(q = qe->parent)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return;
    
    	queue_t_ref(q, "Copy queue pointer from queue entry");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	prev = NULL;
    
    	for (current = q->head; current; current = current->next) {
    		if (current == qe) {
    
    			RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			q->count--;
    
    			if (!q->count) {
    
    				ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s", q->name);
    
    			blob = ast_json_pack("{s: s, s: i, s: i}",
    					     "Queue", q->name,
    					     "Position", qe->pos,
    					     "Count", q->count);
    			ast_channel_publish_blob(qe->chan, queue_caller_leave_type(), blob);
    
    			ast_debug(1, "Queue '%s' Leave, Channel '%s'\n", q->name, ast_channel_name(qe->chan));
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Take us out of the queue */
    
    Olle Johansson's avatar
    Olle Johansson committed
    			if (prev) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    			} else {
    
    Olle Johansson's avatar
    Olle Johansson committed
    			while ((pr_iter = AST_LIST_REMOVE_HEAD(&qe->qe_rules, list))) {
    
    			snprintf(posstr, sizeof(posstr), "%d", qe->pos);
    			pbx_builtin_setvar_helper(qe->chan, "QUEUEPOSITION", posstr);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else {
    
    			/* Renumber the people after us in the queue based on a new count */
    
    			current->pos = ++pos;
    			prev = current;
    
    	ao2_unlock(q);
    
    	/*If the queue is a realtime queue, check to see if it's still defined in real time*/
    
    		struct ast_variable *var;
    		if (!(var = ast_load_realtime("queues", "name", q->name, SENTINEL))) {
    
    		} else {
    			ast_variables_destroy(var);
    		}
    
    	if (q->dead) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* It's dead and nobody is in it, so kill it */
    
    		queues_t_unlink(queues, q, "Queue is now dead; remove it from the container");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	/* unref the explicit ref earlier in the function */
    
    /*!
     * \internal
     * \brief Destroy the given callattempt structure and free it.
     * \since 1.8
     *
     * \param doomed callattempt structure to destroy.
     *
     * \return Nothing
     */
    static void callattempt_free(struct callattempt *doomed)
    {
    	if (doomed->member) {
    		ao2_ref(doomed->member, -1);
    	}
    	ast_party_connected_line_free(&doomed->connected);
    	ast_free(doomed);
    }
    
    
    static void publish_dial_end_event(struct ast_channel *in, struct callattempt *outgoing, struct ast_channel *exception, const char *status)
    {
    	struct callattempt *cur;
    
    	for (cur = outgoing; cur; cur = cur->q_next) {
    
    		if (cur->chan && cur->chan != exception) {
    
    			ast_channel_publish_dial(in, cur->chan, NULL, status);
    
    /*! \brief Hang up a list of outgoing calls */
    
    static void hangupcalls(struct queue_ent *qe, struct callattempt *outgoing, struct ast_channel *exception, int cancel_answered_elsewhere)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    		/* If someone else answered the call we should indicate this in the CANCEL */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Hangup any existing lines we have open */
    
    		if (outgoing->chan && (outgoing->chan != exception)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    			if (exception || cancel_answered_elsewhere) {
    
    				ast_channel_hangupcause_set(outgoing->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
    
    			ast_channel_publish_dial(qe->chan, outgoing->chan, outgoing->interface, "CANCEL");
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_hangup(outgoing->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		oo = outgoing;
    
    		outgoing = outgoing->q_next;
    
    		ast_aoc_destroy_decoded(oo->aoc_s_rate_list);
    
    /*!
     * \brief Get the number of members available to accept a call.
     *
     * \note The queue passed in should be locked prior to this function call
     *
     * \param[in] q The queue for which we are couting the number of available members
     * \return Return the number of available members in queue q
     */
    static int num_available_members(struct call_queue *q)
    {
    	struct member *mem;
    	int avl = 0;
    	struct ao2_iterator mem_iter;
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((mem = ao2_iterator_next(&mem_iter))) {
    
    		ao2_ref(mem, -1);
    
    		/* If autofill is not enabled or if the queue's strategy is ringall, then
    		 * we really don't care about the number of available members so much as we
    		 * do that there is at least one available.
    		 *
    		 * In fact, we purposely will return from this function stating that only
    		 * one member is available if either of those conditions hold. That way,
    		 * functions which determine what action to take based on the number of available
    		 * members will operate properly. The reasoning is that even if multiple
    		 * members are available, only the head caller can actually be serviced.
    		 */
    		if ((!q->autofill || q->strategy == QUEUE_STRATEGY_RINGALL) && avl) {
    			break;
    		}
    	}
    
    	ao2_iterator_destroy(&mem_iter);
    
    
    	return avl;
    }
    
    /* traverse all defined queues which have calls waiting and contain this member
       return 0 if no other queue has precedence (higher weight) or 1 if found  */
    
    static int compare_weight(struct call_queue *rq, struct member *member)
    
    	struct call_queue *q;
    
    	struct member *mem;
    
    	int found = 0;
    
    	while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
    
    		if (q == rq) { /* don't check myself, could deadlock */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			continue;
    
    		if (q->count && q->members) {
    
    			if ((mem = ao2_find(q->members, member, OBJ_POINTER))) {
    
    				ast_debug(1, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
    
    				if (q->weight > rq->weight && q->count >= num_available_members(q)) {
    
    					ast_debug(1, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count);
    
    				ao2_ref(mem, -1);
    
    	ao2_iterator_destroy(&queue_iter);
    
    /*! \brief common hangup actions */
    static void do_hang(struct callattempt *o)
    {
    	o->stillgoing = 0;
    	ast_hangup(o->chan);
    	o->chan = NULL;
    }
    
    
    /*!
     * \internal
     * \brief Check if the member status is available.
     *
     * \param status Member status to check if available.
     *
     * \retval non-zero if the member status is available.
     */
    static int member_status_available(int status)
    {
    	return status == AST_DEVICE_NOT_INUSE || status == AST_DEVICE_UNKNOWN;
    }
    
    /*!
     * \internal
     * \brief Clear the member call pending flag.
     *
     * \param mem Queue member.
     *
     * \return Nothing
     */
    static void member_call_pending_clear(struct member *mem)
    {
    	ao2_lock(mem);
    	mem->call_pending = 0;
    	ao2_unlock(mem);
    }
    
    /*!
     * \internal
     * \brief Set the member call pending flag.
     *
     * \param mem Queue member.
     *
     * \retval non-zero if call pending flag was already set.
     */
    static int member_call_pending_set(struct member *mem)
    {
    	int old_pending;
    
    	ao2_lock(mem);
    	old_pending = mem->call_pending;
    	mem->call_pending = 1;
    	ao2_unlock(mem);
    
    	return old_pending;
    }
    
    /*!
     * \internal
     * \brief Determine if can ring a queue entry.
     *
     * \param qe Queue entry to check.
     * \param call Member call attempt.
     *
     * \retval non-zero if an entry can be called.
     */
    static int can_ring_entry(struct queue_ent *qe, struct callattempt *call)
    {
    	if (call->member->paused) {
    		ast_debug(1, "%s paused, can't receive call\n", call->interface);
    		return 0;
    	}
    
    	if (!call->member->ringinuse && !member_status_available(call->member->status)) {
    		ast_debug(1, "%s not available, can't receive call\n", call->interface);
    		return 0;
    	}
    
    	if ((call->lastqueue && call->lastqueue->wrapuptime && (time(NULL) - call->lastcall < call->lastqueue->wrapuptime))
    		|| (!call->lastqueue && qe->parent->wrapuptime && (time(NULL) - call->lastcall < qe->parent->wrapuptime))) {
    		ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n",
    			(call->lastqueue ? call->lastqueue->name : qe->parent->name),
    			call->interface);
    		return 0;
    	}
    
    	if (use_weight && compare_weight(qe->parent, call->member)) {
    		ast_debug(1, "Priority queue delaying call to %s:%s\n",
    			qe->parent->name, call->interface);
    		return 0;
    	}
    
    	if (!call->member->ringinuse) {
    		if (member_call_pending_set(call->member)) {
    			ast_debug(1, "%s has another call pending, can't receive call\n",
    				call->interface);
    			return 0;
    		}
    
    		/*
    		 * The queue member is available.  Get current status to be sure
    		 * because the device state and extension state callbacks may
    		 * not have updated the status yet.
    		 */
    		if (!member_status_available(get_queue_member_status(call->member))) {
    			ast_debug(1, "%s actually not available, can't receive call\n",
    				call->interface);
    			member_call_pending_clear(call->member);
    			return 0;
    		}
    	}
    
    	return 1;
    }
    
    
     * Does error checking before attempting to request a channel and call a member.
     * This function is only called from ring_one().
    
     * Failure can occur if:
     * - Agent on call
     * - Agent is paused
     * - Wrapup time not expired
     * - Priority by another queue
    
     * \retval 1 on success to reach a free agent
     * \retval 0 on failure to get agent.
    
    static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies)
    
    	char tech[256];
    	char *location;
    
    	const char *macrocontext, *macroexten;
    
    	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
    
    	/* on entry here, we know that tmp->chan == NULL */
    
    		tmp->stillgoing = 0;
    
    		return 0;
    	}
    
    	ast_assert(tmp->member->ringinuse || tmp->member->call_pending);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if ((location = strchr(tech, '/'))) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else {
    
    	tmp->chan = ast_request(tech, ast_channel_nativeformats(qe->chan), qe->chan, location, &status);
    
    	if (!tmp->chan) {			/* If we can't, just go on to the next call */
    
    		/* BUGBUG: Raise a BUSY dial end message here */
    
    	ast_channel_lock_both(tmp->chan, qe->chan);
    
    		ast_channel_hangupcause_set(tmp->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
    
    	ast_channel_appl_set(tmp->chan, "AppQueue");
    	ast_channel_data_set(tmp->chan, "(Outgoing Line)");
    
    	memset(ast_channel_whentohangup(tmp->chan), 0, sizeof(*ast_channel_whentohangup(tmp->chan)));
    
    
    	/* If the new channel has no callerid, try to guess what it should be */
    
    	if (!ast_channel_caller(tmp->chan)->id.number.valid) {
    		if (ast_channel_connected(qe->chan)->id.number.valid) {
    
    			struct ast_party_caller caller;
    
    
    			ast_party_caller_set_init(&caller, ast_channel_caller(tmp->chan));
    			caller.id = ast_channel_connected(qe->chan)->id;
    			caller.ani = ast_channel_connected(qe->chan)->ani;
    
    			ast_channel_set_caller_event(tmp->chan, &caller, NULL);
    
    		} else if (!ast_strlen_zero(ast_channel_dialed(qe->chan)->number.str)) {
    			ast_set_callerid(tmp->chan, ast_channel_dialed(qe->chan)->number.str, NULL, NULL);
    
    		} else if (!ast_strlen_zero(S_OR(ast_channel_macroexten(qe->chan), ast_channel_exten(qe->chan)))) {
    
    			ast_set_callerid(tmp->chan, S_OR(ast_channel_macroexten(qe->chan), ast_channel_exten(qe->chan)), NULL, NULL);
    
    	ast_party_redirecting_copy(ast_channel_redirecting(tmp->chan), ast_channel_redirecting(qe->chan));
    
    	ast_channel_dialed(tmp->chan)->transit_network_select = ast_channel_dialed(qe->chan)->transit_network_select;
    
    	ast_connected_line_copy_from_caller(ast_channel_connected(tmp->chan), ast_channel_caller(qe->chan));
    
    
    	/* Inherit specially named variables from parent channel */
    	ast_channel_inherit_variables(qe->chan, tmp->chan);
    
    	ast_channel_datastore_inherit(qe->chan, tmp->chan);
    
    	/* Presense of ADSI CPE on outgoing channel follows ours */
    
    	ast_channel_adsicpe_set(tmp->chan, ast_channel_adsicpe(qe->chan));
    
    	/* Inherit context and extension */
    
    	macrocontext = pbx_builtin_getvar_helper(qe->chan, "MACRO_CONTEXT");
    
    	ast_channel_dialcontext_set(tmp->chan, ast_strlen_zero(macrocontext) ? ast_channel_context(qe->chan) : macrocontext);
    
    	macroexten = pbx_builtin_getvar_helper(qe->chan, "MACRO_EXTEN");
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (!ast_strlen_zero(macroexten)) {
    
    		ast_channel_exten_set(tmp->chan, macroexten);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else {
    
    		ast_channel_exten_set(tmp->chan, ast_channel_exten(qe->chan));
    
    	ast_channel_unlock(tmp->chan);
    	ast_channel_unlock(qe->chan);
    
    
    	/* Place the call, but don't wait on the answer */
    	if ((res = ast_call(tmp->chan, location, 0))) {
    
    		/* Again, keep going even if there's an error */
    
    		ast_verb(3, "Couldn't call %s\n", tmp->interface);
    
    		member_call_pending_clear(tmp->member);
    		++*busies;
    
    	ast_channel_lock_both(tmp->chan, qe->chan);
    
    
    	blob = ast_json_pack("{s: s, s: s, s: s}",
    			     "Queue", qe->parent->name,
    			     "Interface", tmp->interface,
    			     "MemberName", tmp->member->membername);
    	queue_publish_multi_channel_blob(qe->chan, tmp->chan, queue_agent_called_type(), blob);
    
    	ast_channel_publish_dial(qe->chan, tmp->chan, tmp->interface, NULL);
    
    	ast_channel_unlock(tmp->chan);
    	ast_channel_unlock(qe->chan);
    
    	ast_verb(3, "Called %s\n", tmp->interface);
    
    	member_call_pending_clear(tmp->member);