Skip to content
Snippets Groups Projects
app_queue.c 315 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			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;
    
    /*! \brief Caller leaving queue.
     * 
     * 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) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			q->count--;
    
    
    			/* Take us out of the queue */
    
    			/*** DOCUMENTATION
    			<managerEventInstance>
    				<synopsis>Raised when a channel leaves a Queue.</synopsis>
    				<syntax>
    					<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
    					<xi:include xpointer="xpointer(/docs/managerEvent[@name='Join']/managerEventInstance/syntax/parameter[@name='Count'])" />
    					<xi:include xpointer="xpointer(/docs/managerEvent[@name='Join']/managerEventInstance/syntax/parameter[@name='Position'])" />
    				</syntax>
    				<see-also>
    					<ref type="managerEvent">Join</ref>
    				</see-also>
    			</managerEventInstance>
    			***/
    
    			ast_manager_event(qe->chan, EVENT_FLAG_CALL, "Leave",
    
    				"Channel: %s\r\nQueue: %s\r\nCount: %d\r\nPosition: %d\r\nUniqueid: %s\r\n",
    
    				ast_channel_name(qe->chan), q->name,  q->count, qe->pos, ast_channel_uniqueid(qe->chan));
    
    			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);
    }
    
    
    /*! \brief Hang up a list of outgoing calls */
    
    static void hangupcalls(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);
    
    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))) {
    		switch (mem->status) {
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    			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:
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    					break;
    				}
    				/* else fall through */
    			case AST_DEVICE_NOT_INUSE:
    			case AST_DEVICE_UNKNOWN:
    				if (!mem->paused) {
    					avl++;
    				}
    
    				break;
    		}
    		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;
    }
    
    
    /*! \brief convert "\n" to "\nVariable: " ready for manager to use */
    
    static char *vars2manager(struct ast_channel *chan, char *vars, size_t len)
    {
    
    	struct ast_str *buf = ast_str_thread_get(&ast_str_thread_global_buf, len + 1);
    
    	if (pbx_builtin_serialize_variables(chan, &buf)) {
    
    		int i, j;
    
    		/* convert "\n" to "\nVariable: " */
    		strcpy(vars, "Variable: ");
    
    
    		for (i = 0, j = 10; (i < len - 1) && (j < len - 1); i++, j++) {
    			vars[j] = tmp[i];
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    			if (tmp[i + 1] == '\0') {
    
    				vars[j++] = '\r';
    				vars[j++] = '\n';
    
    
    				ast_copy_string(&(vars[j]), "Variable: ", len - j);
    				j += 9;
    			}
    		}
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (j > len - 3) {
    
    		vars[j++] = '\r';
    		vars[j++] = '\n';
    
    		vars[j] = '\0';
    	} else {
    		/* there are no channel variables; leave it blank */
    		*vars = '\0';
    	}
    	return vars;
    }
    
    
    /*! 
     * \brief Part 2 of ring_one
     *
     * 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;
    
    	enum ast_device_state newstate;
    
    	/* on entry here, we know that tmp->chan == NULL */
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	if (tmp->member->paused) {
    		ast_debug(1, "%s paused, can't receive call\n", tmp->interface);
    
    		if (ast_channel_cdr(qe->chan)) {
    			ast_cdr_busy(ast_channel_cdr(qe->chan));
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		}
    
    		tmp->stillgoing = 0;
    		return 0;
    	}
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    	if ((tmp->lastqueue && tmp->lastqueue->wrapuptime && (time(NULL) - tmp->lastcall < tmp->lastqueue->wrapuptime)) ||
    		(!tmp->lastqueue && qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime))) {
    		ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n",
    				(tmp->lastqueue ? tmp->lastqueue->name : qe->parent->name), tmp->interface);
    
    		if (ast_channel_cdr(qe->chan)) {
    			ast_cdr_busy(ast_channel_cdr(qe->chan));
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		}
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		(*busies)++;
    
    	if (!tmp->member->ringinuse) {
    
    		if (check_state_unknown && (tmp->member->status == AST_DEVICE_UNKNOWN)) {
    			newstate = ast_device_state(tmp->member->interface);
    			if (newstate != tmp->member->status) {
    				ast_log(LOG_WARNING, "Found a channel matching iterface %s while status was %s changed to %s\n",
    					tmp->member->interface, ast_devstate2str(tmp->member->status), ast_devstate2str(newstate));
    				ast_devstate_changed_literal(newstate, tmp->member->interface);
    			}
    		}
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		if ((tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) {
    			ast_debug(1, "%s in use, can't receive call\n", tmp->interface);
    
    			if (ast_channel_cdr(qe->chan)) {
    				ast_cdr_busy(ast_channel_cdr(qe->chan));
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    			}
    			tmp->stillgoing = 0;
    			return 0;
    		}
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    
    
    	if (use_weight && compare_weight(qe->parent,tmp->member)) {
    
    		ast_debug(1, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface);
    
    		if (ast_channel_cdr(qe->chan)) {
    			ast_cdr_busy(ast_channel_cdr(qe->chan));
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		}
    
    		tmp->stillgoing = 0;
    		(*busies)++;
    		return 0;
    	}
    
    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 */
    
    		if (ast_channel_cdr(qe->chan)) {
    			ast_cdr_busy(ast_channel_cdr(qe->chan));
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		}
    
    		tmp->stillgoing = 0;
    
    		update_status(qe->parent, tmp->member, get_queue_member_status(tmp->member));
    
    	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));
    
    	if (ast_cdr_isset_unanswered()) {
    		/* they want to see the unanswered dial attempts! */
    		/* set up the CDR fields on all the CDRs to give sensical information */
    
    		ast_cdr_setdestchan(ast_channel_cdr(tmp->chan), ast_channel_name(tmp->chan));
    		strcpy(ast_channel_cdr(tmp->chan)->clid, ast_channel_cdr(qe->chan)->clid);
    		strcpy(ast_channel_cdr(tmp->chan)->channel, ast_channel_cdr(qe->chan)->channel);
    		strcpy(ast_channel_cdr(tmp->chan)->src, ast_channel_cdr(qe->chan)->src);
    		strcpy(ast_channel_cdr(tmp->chan)->dst, ast_channel_exten(qe->chan));
    		strcpy(ast_channel_cdr(tmp->chan)->dcontext, ast_channel_context(qe->chan));
    		strcpy(ast_channel_cdr(tmp->chan)->lastapp, ast_channel_cdr(qe->chan)->lastapp);
    		strcpy(ast_channel_cdr(tmp->chan)->lastdata, ast_channel_cdr(qe->chan)->lastdata);
    		ast_channel_cdr(tmp->chan)->amaflags = ast_channel_cdr(qe->chan)->amaflags;
    		strcpy(ast_channel_cdr(tmp->chan)->accountcode, ast_channel_cdr(qe->chan)->accountcode);
    		strcpy(ast_channel_cdr(tmp->chan)->userfield, ast_channel_cdr(qe->chan)->userfield);
    
    	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_debug(1, "ast call on peer returned %d\n", res);
    
    		ast_verb(3, "Couldn't call %s\n", tmp->interface);
    
    		update_status(qe->parent, tmp->member, get_queue_member_status(tmp->member));
    
    	} else if (qe->parent->eventwhencalled) {
    		char vars[2048];
    
    
    		ast_channel_lock_both(tmp->chan, qe->chan);
    
    
    		/*** DOCUMENTATION
    		<managerEventInstance>
    			<synopsis>Raised when an Agent is notified of a member in the queue.</synopsis>
    			<syntax>
    				<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
    				<parameter name="AgentCalled">
    					<para>The agent's technology or location.</para>
    				</parameter>
    				<parameter name="AgentName">
    					<para>The name of the agent.</para>
    				</parameter>
    				<parameter name="Variable" required="no" multiple="yes">
    					<para>Optional channel variables from the ChannelCalling channel</para>
    				</parameter>
    			</syntax>
    			<see-also>
    				<ref type="managerEvent">AgentRingNoAnswer</ref>
    				<ref type="managerEvent">AgentComplete</ref>
    				<ref type="managerEvent">AgentConnect</ref>
    			</see-also>
    		</managerEventInstance>
    		***/
    
    		manager_event(EVENT_FLAG_AGENT, "AgentCalled",
    
    			"Queue: %s\r\n"
    			"AgentCalled: %s\r\n"
    			"AgentName: %s\r\n"
    			"ChannelCalling: %s\r\n"
    			"DestinationChannel: %s\r\n"
    			"CallerIDNum: %s\r\n"
    			"CallerIDName: %s\r\n"
    
    			"ConnectedLineNum: %s\r\n"
    			"ConnectedLineName: %s\r\n"
    
    			"Context: %s\r\n"
    			"Extension: %s\r\n"
    			"Priority: %d\r\n"
    			"Uniqueid: %s\r\n"
    			"%s",
    
    			qe->parent->name, tmp->interface, tmp->member->membername, ast_channel_name(qe->chan), ast_channel_name(tmp->chan),
    
    			S_COR(ast_channel_caller(qe->chan)->id.number.valid, ast_channel_caller(qe->chan)->id.number.str, "unknown"),
    			S_COR(ast_channel_caller(qe->chan)->id.name.valid, ast_channel_caller(qe->chan)->id.name.str, "unknown"),
    			S_COR(ast_channel_connected(qe->chan)->id.number.valid, ast_channel_connected(qe->chan)->id.number.str, "unknown"),
    			S_COR(ast_channel_connected(qe->chan)->id.name.valid, ast_channel_connected(qe->chan)->id.name.str, "unknown"),
    
    			ast_channel_context(qe->chan), ast_channel_exten(qe->chan), ast_channel_priority(qe->chan), ast_channel_uniqueid(qe->chan),
    
    			qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
    
    
    		ast_channel_unlock(tmp->chan);
    		ast_channel_unlock(qe->chan);
    
    
    		ast_verb(3, "Called %s\n", tmp->interface);
    
    	update_status(qe->parent, tmp->member, get_queue_member_status(tmp->member));
    
    /*! \brief find the entry with the best metric, or NULL */
    static struct callattempt *find_best(struct callattempt *outgoing)
    
    	struct callattempt *best = NULL, *cur;
    
    	for (cur = outgoing; cur; cur = cur->q_next) {
    		if (cur->stillgoing &&					/* Not already done */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			!cur->chan &&					/* Isn't already going */
    			(!best || cur->metric < best->metric)) {		/* We haven't found one yet, or it's better */
    
    /*! 
     * \brief Place a call to a queue member.
    
     *
     * Once metrics have been calculated for each member, this function is used
     * to place a call to the appropriate member (or members). The low-level
     * channel-handling and error detection is handled in ring_entry
     *
    
     * \retval 1 if a member was called successfully
     * \retval 0 otherwise
    
    static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *busies)
    {
    	int ret = 0;
    
    	while (ret == 0) {
    		struct callattempt *best = find_best(outgoing);
    		if (!best) {
    
    			ast_debug(1, "Nobody left to try ringing in queue\n");
    
    		if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
    
    			struct callattempt *cur;
    			/* Ring everyone who shares this best metric (for ringall) */
    			for (cur = outgoing; cur; cur = cur->q_next) {
    				if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) {
    
    					ast_debug(1, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric);
    
    					ret |= ring_entry(qe, cur, busies);
    
    		} else {
    			/* Ring just the best channel */
    
    			ast_debug(1, "Trying '%s' with metric %d\n", best->interface, best->metric);
    
    			ret = ring_entry(qe, best, busies);
    
    		
    		/* If we have timed out, break out */
    		if (qe->expire && (time(NULL) >= qe->expire)) {
    
    			ast_debug(1, "Queue timed out while ringing members.\n");
    
    /*! \brief Search for best metric and add to Round Robbin queue */
    
    static int store_next_rr(struct queue_ent *qe, struct callattempt *outgoing)
    
    	struct callattempt *best = find_best(outgoing);
    
    	if (best) {
    		/* Ring just the best channel */
    
    		ast_debug(1, "Next is '%s' with metric %d\n", best->interface, best->metric);
    
    		qe->parent->rrpos = best->metric % 1000;
    	} else {
    		/* Just increment rrpos */
    
    			/* No more channels, start over */
    			qe->parent->rrpos = 0;
    		} else {
    			/* Prioritize next entry */
    			qe->parent->rrpos++;
    		}
    
    /*! \brief Search for best metric and add to Linear queue */
    
    static int store_next_lin(struct queue_ent *qe, struct callattempt *outgoing)
    {
    	struct callattempt *best = find_best(outgoing);
    
    	if (best) {
    		/* Ring just the best channel */
    		ast_debug(1, "Next is '%s' with metric %d\n", best->interface, best->metric);
    		qe->linpos = best->metric % 1000;
    	} else {
    		/* Just increment rrpos */
    		if (qe->linwrapped) {
    			/* No more channels, start over */
    			qe->linpos = 0;
    		} else {
    			/* Prioritize next entry */
    			qe->linpos++;
    		}
    	}
    	qe->linwrapped = 0;
    
    	return 0;
    }
    
    
    /*! \brief Playback announcement to queued members if period has elapsed */
    
    static int say_periodic_announcement(struct queue_ent *qe, int ringing)
    
    {
    	int res = 0;
    	time_t now;
    
    	/* Get the current time */
    	time(&now);
    
    	/* Check to see if it is time to announce */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if ((now - qe->last_periodic_announce_time) < qe->parent->periodicannouncefrequency) {
    
    
    	/* Stop the music on hold so we can play our own file */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (ringing) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else {
    
    	ast_verb(3, "Playing periodic announcement\n");
    
    	if (qe->parent->randomperiodicannounce && qe->parent->numperiodicannounce) {
    
    		qe->last_periodic_announce_sound = ((unsigned long) ast_random()) % qe->parent->numperiodicannounce;
    	} else if (qe->last_periodic_announce_sound >= qe->parent->numperiodicannounce || 
    
    		ast_str_strlen(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]) == 0) {
    
    		qe->last_periodic_announce_sound = 0;
    	}
    	
    
    	/* play the announcement */
    
    	res = play_file(qe->chan, ast_str_buffer(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]));
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (res > 0 && !valid_exit(qe, res)) {
    
    	/* Resume Music on Hold if the caller is going to stay in the queue */
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (ringing) {
    
    			ast_indicate(qe->chan, AST_CONTROL_RINGING);
    
    Olle Johansson's avatar
    Olle Johansson committed
    		} else {
    
    			ast_moh_start(qe->chan, qe->moh, NULL);
    
    
    	/* update last_periodic_announce_time */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (qe->parent->relativeperiodicannounce) {
    
    		time(&qe->last_periodic_announce_time);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	} else {
    
    		qe->last_periodic_announce_time = now;
    
    	/* Update the current periodic announcement to the next announcement */
    
    	if (!qe->parent->randomperiodicannounce) {
    		qe->last_periodic_announce_sound++;
    	}
    
    /*! \brief Record that a caller gave up on waiting in queue */
    
    static void record_abandoned(struct queue_ent *qe)
    {
    
    	set_queue_variables(qe->parent, qe->chan);
    
    	ao2_lock(qe->parent);
    
    	/*** DOCUMENTATION
    	<managerEventInstance>
    		<synopsis>Raised when an caller abandons the queue.</synopsis>
    		<syntax>
    			<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
    			<xi:include xpointer="xpointer(/docs/managerEvent[@name='Join']/managerEventInstance/syntax/parameter[@name='Position'])" />
    			<parameter name="OriginalPosition">
    				<para>The channel's original position in the queue.</para>
    			</parameter>
    			<parameter name="HoldTime">
    				<para>The time the channel was in the queue, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
    			</parameter>
    		</syntax>
    	</managerEventInstance>
    	***/
    
    	manager_event(EVENT_FLAG_AGENT, "QueueCallerAbandon",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		"Queue: %s\r\n"
    		"Uniqueid: %s\r\n"
    		"Position: %d\r\n"
    		"OriginalPosition: %d\r\n"
    		"HoldTime: %d\r\n",
    
    		qe->parent->name, ast_channel_uniqueid(qe->chan), qe->pos, qe->opos, (int)(time(NULL) - qe->start));
    
    	qe->parent->callsabandoned++;
    
    BJ Weschke's avatar
    BJ Weschke committed
    /*! \brief RNA == Ring No Answer. Common code that is executed when we try a queue member and they don't answer. */
    
    static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername, int autopause)
    
    	ast_verb(3, "Nobody picked up in %d ms\n", rnatime);
    
    
    	/* Stop ringing, and resume MOH if specified */
    	if (qe->ring_when_ringing) {
    		ast_indicate(qe->chan, -1);
    		ast_moh_start(qe->chan, qe->moh, NULL);
    	}
    
    
    	if (qe->parent->eventwhencalled) {
    		char vars[2048];
    
    		/*** DOCUMENTATION
    		<managerEventInstance>
    			<synopsis>Raised when an agent is notified of a member in the queue and fails to answer.</synopsis>
    			<syntax>
    				<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='Queue'])" />
    				<xi:include xpointer="xpointer(/docs/managerEvent[@name='QueueMemberStatus']/managerEventInstance/syntax/parameter[@name='MemberName'])" />
    				<xi:include xpointer="xpointer(/docs/managerEvent[@name='AgentCalled']/managerEventInstance/syntax/parameter[@name='Variable'])" />
    				<parameter name="Member">
    					<para>The queue member's channel technology or location.</para>
    				</parameter>
    				<parameter name="RingTime">
    					<para>The time the agent was rung, expressed in seconds since 00:00, Jan 1, 1970 UTC.</para>
    				</parameter>
    			</syntax>
    			<see-also>
    				<ref type="managerEvent">AgentCalled</ref>
    			</see-also>
    		</managerEventInstance>
    		***/
    
    		manager_event(EVENT_FLAG_AGENT, "AgentRingNoAnswer",
    						"Queue: %s\r\n"
    						"Uniqueid: %s\r\n"
    						"Channel: %s\r\n"
    						"Member: %s\r\n"
    						"MemberName: %s\r\n"
    
    						"RingTime: %d\r\n"
    
    						ast_channel_uniqueid(qe->chan),
    
    						ast_channel_name(qe->chan),
    
    						rnatime,
    						qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
    	}
    
    	ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), membername, "RINGNOANSWER", "%d", rnatime);
    
    	if (qe->parent->autopause != QUEUE_AUTOPAUSE_OFF && autopause) {
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    		if (qe->parent->autopausedelay > 0) {
    			struct member *mem;
    			ao2_lock(qe->parent);
    			if ((mem = interface_exists(qe->parent, interface))) {
    				time_t idletime = time(&idletime)-mem->lastcall;
    				if ((mem->lastcall != 0) && (qe->parent->autopausedelay > idletime)) {
    					ao2_unlock(qe->parent);
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    					return;
    				}
    
    Gregory Nietsky's avatar
     
    Gregory Nietsky committed
    			}
    			ao2_unlock(qe->parent);
    		}
    
    		if (qe->parent->autopause == QUEUE_AUTOPAUSE_ON) {
    			if (!set_member_paused(qe->parent->name, interface, "Auto-Pause", 1)) {
    				ast_verb(3, "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n",
    					interface, qe->parent->name);
    			} else {
    				ast_verb(3, "Failed to pause Queue Member %s in queue %s!\n", interface, qe->parent->name);
    			}
    
    			/* If queue autopause is mode all, just don't send any queue to stop.
    			* the function will stop in all queues */
    			if (!set_member_paused("", interface, "Auto-Pause", 1)) {
    				ast_verb(3, "Auto-Pausing Queue Member %s in all queues since they failed to answer on queue %s.\n",
    						interface, qe->parent->name);
    			} else {
    					ast_verb(3, "Failed to pause Queue Member %s in all queues!\n", interface);
    			}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	return;
    }
    
    /*!
     * \brief Wait for a member to answer the call
    
     *
     * \param[in] qe the queue_ent corresponding to the caller in the queue
     * \param[in] outgoing the list of callattempts. Relevant ones will have their chan and stillgoing parameters non-zero
     * \param[in] to the amount of time (in milliseconds) to wait for a response
     * \param[out] digit if a user presses a digit to exit the queue, this is the digit the caller pressed
     * \param[in] prebusies number of busy members calculated prior to calling wait_for_answer
     * \param[in] caller_disconnect if the 'H' option is used when calling Queue(), this is used to detect if the caller pressed * to disconnect the call
     * \param[in] forwardsallowed used to detect if we should allow call forwarding, based on the 'i' option to Queue()
    
     *
     * \todo eventually all call forward logic should be intergerated into and replaced by ast_call_forward()
    
    static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed)
    
    	const char *queue = qe->parent->name;
    
    	struct callattempt *o, *start = NULL, *prev = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int orig = *to;
    	struct ast_frame *f;
    
    	struct callattempt *peer = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *winner;
    
    	char on[80] = "";
    	char membername[80] = "";
    
    	long endtime = 0;
    #ifdef HAVE_EPOLL
    	struct callattempt *epollo;
    #endif
    
    	struct ast_party_connected_line connected_caller;
    	char *inchan_name;
    
    
    	ast_party_connected_line_init(&connected_caller);
    
    
    	inchan_name = ast_strdupa(ast_channel_name(qe->chan));
    
    	starttime = (long) time(NULL);
    
    	for (epollo = outgoing; epollo; epollo = epollo->q_next) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (epollo->chan) {
    
    			ast_poll_channel_add(in, epollo->chan);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	while (*to && !peer) {
    
    		int numlines, retry, pos = 1;
    		struct ast_channel *watchers[AST_MAX_WATCHERS];
    		watchers[0] = in;
    
    
    		for (retry = 0; retry < 2; retry++) {
    			numlines = 0;
    			for (o = outgoing; o; o = o->q_next) { /* Keep track of important channels */
    				if (o->stillgoing) {	/* Keep track of important channels */
    					stillgoing = 1;
    
    						if (pos < AST_MAX_WATCHERS) {
    							watchers[pos++] = o->chan;
    						}
    
    Olle Johansson's avatar
    Olle Johansson committed
    						if (!start) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    						} else {
    
    							prev->call_next = o;
    
    				}
    				numlines++;
    			}
    			if (pos > 1 /* found */ || !stillgoing /* nobody listening */ ||
    
    Olle Johansson's avatar
    Olle Johansson committed
    				(qe->parent->strategy != QUEUE_STRATEGY_RINGALL) /* ring would not be delivered */) {
    
    			/* On "ringall" strategy we only move to the next penalty level
    			   when *all* ringing phones are done in the current penalty level */
    			ring_one(qe, outgoing, &numbusies);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    		if (pos == 1 /* not found */) {
    
    			if (numlines == (numbusies + numnochan)) {
    
    				ast_debug(1, "Everyone is busy at this time\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    			} else {
    
    				ast_debug(3, "No one is answering queue '%s' (%d numlines / %d busies / %d failed channels)\n", queue, numlines, numbusies, numnochan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    			*to = 0;
    			return NULL;
    		}
    
    
    		/* Poll for events from both the incoming channel as well as any outgoing channels */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		winner = ast_waitfor_n(watchers, pos, to);
    
    
    		/* Service all of the outgoing channels */
    
    		for (o = start; o; o = o->call_next) {
    
    			/* We go with a fixed buffer here instead of using ast_strdupa. Using
    
    			 * ast_strdupa in a loop like this one can cause a stack overflow
    			 */
    			char ochan_name[AST_CHANNEL_NAME];
    
    			if (o->chan) {
    				ast_channel_lock(o->chan);
    
    				ast_copy_string(ochan_name, ast_channel_name(o->chan), sizeof(ochan_name));
    
    			if (o->stillgoing && (o->chan) &&  (ast_channel_state(o->chan) == AST_STATE_UP)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (!peer) {
    
    					ast_verb(3, "%s answered %s\n", ochan_name, inchan_name);
    
    					if (!o->block_connected_update) {
    
    							if (ast_channel_connected_line_sub(o->chan, in, &o->connected, 0) &&
    								ast_channel_connected_line_macro(o->chan, in, &o->connected, 1, 0)) {
    
    								ast_channel_update_connected_line(in, &o->connected, NULL);
    
    						} else if (!o->dial_callerid_absent) {
    
    							ast_connected_line_copy_from_caller(&connected_caller, ast_channel_caller(o->chan));
    
    							ast_channel_unlock(o->chan);
    							connected_caller.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER;
    
    							if (ast_channel_connected_line_sub(o->chan, in, &connected_caller, 0) &&
    								ast_channel_connected_line_macro(o->chan, in, &connected_caller, 1, 0)) {
    								ast_channel_update_connected_line(in, &connected_caller, NULL);
    							}
    
    							ast_party_connected_line_free(&connected_caller);
    						}
    					}
    
    					if (o->aoc_s_rate_list) {
    						size_t encoded_size;
    						struct ast_aoc_encoded *encoded;
    						if ((encoded = ast_aoc_encode(o->aoc_s_rate_list, &encoded_size, o->chan))) {
    							ast_indicate_data(in, AST_CONTROL_AOC, encoded, encoded_size);
    							ast_aoc_destroy_encoded(encoded);
    						}
    					}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    			} else if (o->chan && (o->chan == winner)) {
    
    
    				ast_copy_string(on, o->member->interface, sizeof(on));