Skip to content
Snippets Groups Projects
app_queue.c 331 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*! \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)
    {
    
    	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
    
    
    	set_queue_variables(qe->parent, qe->chan);
    
    	ao2_lock(qe->parent);
    
    	blob = ast_json_pack("{s: s, s: i, s: i}",
    			     "Queue", qe->parent->name,
    			     "Position", qe->pos,
    			     "OriginalPosition", qe->opos,
    			     "HoldTime", (int)(time(NULL) - qe->start));
    	ast_channel_publish_blob(qe->chan, queue_caller_abandon_type(), blob);
    
    	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, struct ast_channel *peer, char *interface, char *membername, int autopause)
    
    	RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
    
    
    	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);
    	}
    
    
    	blob = ast_json_pack("{s: s, s: s, s: s, s: i}",
    			     "Queue", qe->parent->name,
    			     "Interface", interface,
    			     "MemberName", membername,
    			     "RingTime", rnatime);
    	queue_publish_multi_channel_blob(qe->chan, peer, queue_agent_ringnoanswer_type(), blob);
    
    
    	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, int ringing)
    
    	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;
    
    	struct timeval start_time_tv = ast_tvnow();
    
    	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);
    
    	while ((*to = ast_remaining_ms(start_time_tv, orig)) && !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");
    
    				/* BUGBUG: We shouldn't have to set anything here, as each
    				 * individual dial attempt should have set that CDR to busy
    				 */
    
    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);
    
    				/* BUGBUG: We shouldn't have to set anything here, as each
    				 * individual dial attempt should have set that CDR to busy
    				 */
    
    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));
    				ast_copy_string(membername, o->member->membername, sizeof(membername));
    
    
    				/* Before processing channel, go ahead and check for forwarding */
    
    				if (!ast_strlen_zero(ast_channel_call_forward(o->chan)) && !forwardsallowed) {
    					ast_verb(3, "Forwarding %s to '%s' prevented.\n", inchan_name, ast_channel_call_forward(o->chan));
    
    					numnochan++;
    					do_hang(o);
    					winner = NULL;
    					continue;
    
    				} else if (!ast_strlen_zero(ast_channel_call_forward(o->chan))) {
    
    					ast_copy_string(tmpchan, ast_channel_call_forward(o->chan), sizeof(tmpchan));
    
    					if ((stuff = strchr(tmpchan, '/'))) {
    
    						snprintf(tmpchan, sizeof(tmpchan), "%s@%s", ast_channel_call_forward(o->chan), ast_channel_context(o->chan));
    
    					if (!strcasecmp(tech, "Local")) {
    						/*
    						 * Drop the connected line update block for local channels since
    						 * this is going to run dialplan and the user can change his
    						 * mind about what connected line information he wants to send.
    						 */
    						o->block_connected_update = 0;
    					}
    
    					ast_verb(3, "Now forwarding %s to '%s/%s' (thanks to %s)\n", inchan_name, tech, stuff, ochan_name);
    
    					o->chan = ast_request(tech, ast_channel_nativeformats(in), in, stuff, &status);
    
    						ast_log(LOG_NOTICE,
    							"Forwarding failed to create channel to dial '%s/%s'\n",
    							tech, stuff);
    
    						ast_channel_lock_both(o->chan, original);
    						ast_party_redirecting_copy(ast_channel_redirecting(o->chan),
    							ast_channel_redirecting(original));
    						ast_channel_unlock(o->chan);
    						ast_channel_unlock(original);
    
    						ast_channel_lock_both(o->chan, in);
    
    						ast_channel_inherit_variables(in, o->chan);
    
    						ast_channel_datastore_inherit(in, o->chan);
    
    						if (o->pending_connected_update) {
    							/*
    							 * Re-seed the callattempt's connected line information with
    							 * previously acquired connected line info from the queued
    							 * channel.  The previously acquired connected line info could
    							 * have been set through the CONNECTED_LINE dialplan function.
    							 */
    							o->pending_connected_update = 0;
    							ast_party_connected_line_copy(&o->connected, ast_channel_connected(in));
    						}
    
    
    						ast_channel_accountcode_set(o->chan, ast_channel_accountcode(in));
    
    						if (!ast_channel_redirecting(o->chan)->from.number.valid
    							|| ast_strlen_zero(ast_channel_redirecting(o->chan)->from.number.str)) {
    
    							/*
    							 * The call was not previously redirected so it is
    							 * now redirected from this number.
    							 */
    
    							ast_party_number_free(&ast_channel_redirecting(o->chan)->from.number);
    							ast_party_number_init(&ast_channel_redirecting(o->chan)->from.number);
    							ast_channel_redirecting(o->chan)->from.number.valid = 1;
    							ast_channel_redirecting(o->chan)->from.number.str =
    
    								ast_strdup(S_OR(ast_channel_macroexten(in), ast_channel_exten(in)));
    
    						ast_channel_dialed(o->chan)->transit_network_select = ast_channel_dialed(in)->transit_network_select;
    
    						o->dial_callerid_absent = !ast_channel_caller(o->chan)->id.number.valid
    							|| ast_strlen_zero(ast_channel_caller(o->chan)->id.number.str);
    						ast_connected_line_copy_from_caller(ast_channel_connected(o->chan),
    							ast_channel_caller(in));
    
    						ast_channel_unlock(in);
    
    						if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL
    							&& !o->block_connected_update) {
    							struct ast_party_redirecting redirecting;
    
    							/*
    							 * Redirecting updates to the caller make sense only on single
    							 * call at a time strategies.
    							 *
    							 * We must unlock o->chan before calling
    							 * ast_channel_redirecting_macro, because we put o->chan into
    							 * autoservice there.  That is pretty much a guaranteed
    							 * deadlock.  This is why the handling of o->chan's lock may
    							 * seem a bit unusual here.
    							 */
    							ast_party_redirecting_init(&redirecting);
    							ast_party_redirecting_copy(&redirecting, ast_channel_redirecting(o->chan));
    							ast_channel_unlock(o->chan);
    							if (ast_channel_redirecting_sub(o->chan, in, &redirecting, 0) &&
    								ast_channel_redirecting_macro(o->chan, in, &redirecting, 1, 0)) {
    								ast_channel_update_redirecting(in, &redirecting, NULL);
    							}
    							ast_party_redirecting_free(&redirecting);
    						} else {
    							ast_channel_unlock(o->chan);
    						}
    
    						if (ast_call(o->chan, stuff, 0)) {
    							ast_log(LOG_NOTICE, "Forwarding failed to dial '%s/%s'\n",
    								tech, stuff);
    
    					ast_channel_lock_both(qe->chan, o->chan);
    					ast_channel_publish_dial(qe->chan, o->chan, stuff, NULL);
    					ast_channel_unlock(o->chan);
    					ast_channel_unlock(qe->chan);
    
    					ast_channel_lock_both(qe->chan, original);
    
    Kinsey Moore's avatar
    Kinsey Moore committed
    					ast_channel_publish_dial_forward(qe->chan, original, NULL, "CANCEL",
    						ast_channel_call_forward(original));
    
    					ast_channel_unlock(original);
    					ast_channel_unlock(qe->chan);
    
    					/* Hangup the original channel now, in case we needed it */
    					ast_hangup(winner);
    					continue;
    				}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				f = ast_read(winner);
    				if (f) {
    					if (f->frametype == AST_FRAME_CONTROL) {
    
    						switch (f->subclass.integer) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    						case AST_CONTROL_ANSWER:
    
    Mark Spencer's avatar
    Mark Spencer committed
    							/* This is our guy if someone answered. */
    							if (!peer) {
    
    								ast_verb(3, "%s answered %s\n", ochan_name, inchan_name);
    
    								ast_channel_publish_dial(qe->chan, o->chan, on, "ANSWER");
    								publish_dial_end_event(qe->chan, outgoing, o->chan, "CANCEL");
    
    								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
    							}
    							break;
    						case AST_CONTROL_BUSY:
    
    							ast_channel_publish_dial(qe->chan, o->chan, on, "BUSY");
    
    							rna(endtime * 1000, qe, o->chan, on, membername, qe->parent->autopausebusy);
    
    							if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    								if (qe->parent->timeoutrestart) {
    
    									start_time_tv = ast_tvnow();
    
    								/* Have enough time for a queue member to answer? */
    
    								if (ast_remaining_ms(start_time_tv, orig) > 500) {
    
    									ring_one(qe, outgoing, &numbusies);
    									starttime = (long) time(NULL);
    								}
    
    Mark Spencer's avatar
    Mark Spencer committed
    							numbusies++;
    							break;
    						case AST_CONTROL_CONGESTION:
    
    							ast_verb(3, "%s is circuit-busy\n", ochan_name);
    
    							ast_channel_publish_dial(qe->chan, o->chan, on, "CONGESTION");
    
    							rna(endtime * 1000, qe, o->chan, on, membername, qe->parent->autopauseunavail);
    
    							if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    								if (qe->parent->timeoutrestart) {
    
    									start_time_tv = ast_tvnow();
    
    								if (ast_remaining_ms(start_time_tv, orig) > 500) {
    
    									ring_one(qe, outgoing, &numbusies);
    									starttime = (long) time(NULL);
    								}
    
    Mark Spencer's avatar
    Mark Spencer committed
    							numbusies++;
    							break;
    						case AST_CONTROL_RINGING:
    
    							ast_verb(3, "%s is ringing\n", ochan_name);
    
    
    							/* Start ring indication when the channel is ringing, if specified */
    							if (qe->ring_when_ringing) {
    								ast_moh_stop(qe->chan);
    								ast_indicate(qe->chan, AST_CONTROL_RINGING);
    							}
    
    Mark Spencer's avatar
    Mark Spencer committed
    							break;
    						case AST_CONTROL_OFFHOOK:
    							/* Ignore going off hook */
    							break;
    
    							if (o->block_connected_update) {
    
    								ast_verb(3, "Connected line update to %s prevented.\n", inchan_name);
    
    								break;
    							}
    							if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
    
    								struct ast_party_connected_line connected;
    
    								ast_verb(3, "%s connected line has changed. Saving it until answer for %s\n", ochan_name, inchan_name);
    								ast_party_connected_line_set_init(&connected, &o->connected);
    								ast_connected_line_parse_data(f->data.ptr, f->datalen, &connected);
    
    								ast_party_connected_line_set(&o->connected, &connected, NULL);
    
    								ast_party_connected_line_free(&connected);
    
    								break;
    							}
    
    							/*
    							 * Prevent using the CallerID from the outgoing channel since we
    							 * got a connected line update from it.
    							 */
    							o->dial_callerid_absent = 1;
    
    							if (ast_channel_connected_line_sub(o->chan, in, f, 1) &&
    								ast_channel_connected_line_macro(o->chan, in, f, 1, 1)) {
    								ast_indicate_data(in, AST_CONTROL_CONNECTED_LINE, f->data.ptr, f->datalen);
    
    						case AST_CONTROL_AOC:
    							{
    								struct ast_aoc_decoded *decoded = ast_aoc_decode(f->data.ptr, f->datalen, o->chan);
    								if (decoded && (ast_aoc_get_msg_type(decoded) == AST_AOC_S)) {
    									ast_aoc_destroy_decoded(o->aoc_s_rate_list);
    									o->aoc_s_rate_list = decoded;
    								} else {
    									ast_aoc_destroy_decoded(decoded);
    								}
    							}
    							break;
    
    							if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
    								/*
    								 * Redirecting updates to the caller make sense only on single
    								 * call at a time strategies.
    								 */
    								break;
    							}
    							if (o->block_connected_update) {
    								ast_verb(3, "Redirecting update to %s prevented\n",
    									inchan_name);
    								break;
    							}
    							ast_verb(3, "%s redirecting info has changed, passing it to %s\n",
    								ochan_name, inchan_name);
    							if (ast_channel_redirecting_sub(o->chan, in, f, 1) &&
    								ast_channel_redirecting_macro(o->chan, in, f, 1, 1)) {
    								ast_indicate_data(in, AST_CONTROL_REDIRECTING, f->data.ptr, f->datalen);
    
    						case AST_CONTROL_PVT_CAUSE_CODE:
    							ast_indicate_data(in, AST_CONTROL_PVT_CAUSE_CODE, f->data.ptr, f->datalen);
    							break;
    
    Mark Spencer's avatar
    Mark Spencer committed
    						default:
    
    							ast_debug(1, "Dunno what to do with control type %d\n", f->subclass.integer);
    
    Mark Spencer's avatar
    Mark Spencer committed
    						}
    					}
    					ast_frfree(f);
    
    				} else { /* ast_read() returned NULL */
    
    					endtime = (long) time(NULL) - starttime;
    
    					ast_channel_publish_dial(qe->chan, o->chan, on, "NOANSWER");
    
    					rna(endtime * 1000, qe, o->chan, on, membername, 1);
    
    					if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    						if (qe->parent->timeoutrestart) {
    
    							start_time_tv = ast_tvnow();
    
    						if (ast_remaining_ms(start_time_tv, orig) > 500) {
    
    							ring_one(qe, outgoing, &numbusies);
    							starttime = (long) time(NULL);
    						}
    
    
    		/* If we received an event from the caller, deal with it. */
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (winner == in) {
    			f = ast_read(in);
    
    			if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass.integer == AST_CONTROL_HANGUP))) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				/* Got hung up */
    
    				publish_dial_end_event(in, outgoing, NULL, "CANCEL");
    
    						ast_channel_hangupcause_set(in, f->data.uint32);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				return NULL;
    			}
    
    			if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass.integer == '*')) {
    				ast_verb(3, "User hit %c to disconnect call.\n", f->subclass.integer);
    
    				publish_dial_end_event(in, outgoing, NULL, "CANCEL");
    
    Mark Spencer's avatar
    Mark Spencer committed
    				return NULL;
    			}
    
    			if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass.integer)) {
    				ast_verb(3, "User pressed digit: %c\n", f->subclass.integer);
    
    				publish_dial_end_event(in, outgoing, NULL, "CANCEL");
    
    				*digit = f->subclass.integer;
    
    
    			/* Send the frame from the in channel to all outgoing channels. */
    			for (o = start; o; o = o->call_next) {
    				if (!o->stillgoing || !o->chan) {
    					/* This outgoing channel has died so don't send the frame to it. */
    					continue;
    				}
    				switch (f->frametype) {
    				case AST_FRAME_CONTROL:
    					switch (f->subclass.integer) {
    					case AST_CONTROL_CONNECTED_LINE:
    						if (ast_channel_connected_line_sub(in, o->chan, f, 1) &&
    							ast_channel_connected_line_macro(in, o->chan, f, 0, 1)) {
    							ast_indicate_data(o->chan, f->subclass.integer, f->data.ptr, f->datalen);
    						}
    						break;
    					case AST_CONTROL_REDIRECTING:
    						if (ast_channel_redirecting_sub(in, o->chan, f, 1) &&
    							ast_channel_redirecting_macro(in, o->chan, f, 0, 1)) {
    							ast_indicate_data(o->chan, f->subclass.integer, f->data.ptr, f->datalen);
    						}
    						break;
    					default:
    						/* We are not going to do anything with this frame. */
    						goto skip_frame;
    					}
    					break;
    				default:
    					/* We are not going to do anything with this frame. */
    					goto skip_frame;
    				}
    			}
    skip_frame:;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    	/* Make a position announcement, if enabled */
    
     	if (qe->parent->announcefrequency && qe->parent->announce_to_first_user) {
    
    		say_position(qe, ringing);
    	}
    
     	/* Make a periodic announcement, if enabled */
    
     	if (qe->parent->periodicannouncefrequency && qe->parent->announce_to_first_user) {
    
     		say_periodic_announcement(qe, ringing);
     	}
    
    	if (!*to) {
    		for (o = start; o; o = o->call_next) {
    
    			rna(orig, qe, o->chan, o->interface, o->member->membername, 1);
    
    		publish_dial_end_event(qe->chan, outgoing, NULL, "NOANSWER");
    
    	for (epollo = outgoing; epollo; epollo = epollo->q_next) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (epollo->chan) {
    
    			ast_poll_channel_del(in, epollo->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return peer;
    }
    
     * \brief Check if we should start attempting to call queue members.
    
     * A simple process, really. Count the number of members who are available
     * to take our call and then see if we are in a position in the queue at
     * which a member could accept our call.
     *
     * \param[in] qe The caller who wants to know if it is his turn
     * \retval 0 It is not our turn
     * \retval 1 It is our turn
    
    static int is_our_turn(struct queue_ent *qe)
    {
    	struct queue_ent *ch;
    	int res;
    
    	int avl;
    	int idx = 0;
    	/* This needs a lock. How many members are available to be served? */
    	ao2_lock(qe->parent);
    
    	avl = num_available_members(qe->parent);
    
    	ch = qe->parent->head;
    
    	ast_debug(1, "There %s %d available %s.\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member");
    
    	while ((idx < avl) && (ch) && (ch != qe)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (!ch->pending) {
    
    	/* If the queue entry is within avl [the number of available members] calls from the top ...
    
    	 * Autofill and position check added to support autofill=no (as only calls
    	 * from the front of the queue are valid when autofill is disabled)
    	 */
    	if (ch && idx < avl && (qe->parent->autofill || qe->pos == 1)) {
    
    		ast_debug(1, "It's our turn (%s).\n", ast_channel_name(qe->chan));
    
    		ast_debug(1, "It's not our turn (%s).\n", ast_channel_name(qe->chan));
    
    
    /*!
     * \brief update rules for queues
     *
     * Calculate min/max penalties making sure if relative they stay within bounds.
     * Update queues penalty and set dialplan vars, goto next list entry.
    */
    
    static void update_qe_rule(struct queue_ent *qe)
    {
    	int max_penalty = qe->pr->max_relative ? qe->max_penalty + qe->pr->max_value : qe->pr->max_value;
    	int min_penalty = qe->pr->min_relative ? qe->min_penalty + qe->pr->min_value : qe->pr->min_value;
    
    	char max_penalty_str[20], min_penalty_str[20];
    
    	/* a relative change to the penalty could put it below 0 */
    
    Olle Johansson's avatar
    Olle Johansson committed
    	if (max_penalty < 0) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	}
    	if (min_penalty < 0) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    	}
    	if (min_penalty > max_penalty) {
    
    	snprintf(max_penalty_str, sizeof(max_penalty_str), "%d", max_penalty);
    	snprintf(min_penalty_str, sizeof(min_penalty_str), "%d", min_penalty);
    
    	pbx_builtin_setvar_helper(qe->chan, "QUEUE_MAX_PENALTY", max_penalty_str);
    	pbx_builtin_setvar_helper(qe->chan, "QUEUE_MIN_PENALTY", min_penalty_str);
    	qe->max_penalty = max_penalty;
    	qe->min_penalty = min_penalty;
    
    	ast_debug(3, "Setting max penalty to %d and min penalty to %d for caller %s since %d seconds have elapsed\n", qe->max_penalty, qe->min_penalty, ast_channel_name(qe->chan), qe->pr->time);
    
    /*! \brief The waiting areas for callers who are not actively calling members
     *
     * This function is one large loop. This function will return if a caller
     * either exits the queue or it becomes that caller's turn to attempt calling
     * queue members. Inside the loop, we service the caller with periodic announcements,
     * holdtime announcements, etc. as configured in queues.conf
     *
    
     * \retval  0 if the caller's turn has arrived
     * \retval -1 if the caller should exit the queue.
    
    static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *reason)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	int res = 0;
    
    
    	/* This is the holding pen for callers 2 through maxlen */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	for (;;) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    		if (is_our_turn(qe)) {
    
    
    		/* If we have timed out, break out */
    
    		if (qe->expire && (time(NULL) >= qe->expire)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    
    		if (qe->parent->leavewhenempty) {
    
    			if ((status = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty, qe->parent->leavewhenempty))) {
    
    				*reason = QUEUE_LEAVEEMPTY;
    
    				ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
    
    		/* Make a position announcement, if enabled */
    
    Olle Johansson's avatar
    Olle Johansson committed
    			(res = say_position(qe,ringing))) {
    
    		/* If we have timed out, break out */
    
    		if (qe->expire && (time(NULL) >= qe->expire)) {
    
    			*reason = QUEUE_TIMEOUT;
    			break;
    		}
    
    
    		/* Make a periodic announcement, if enabled */
    
    		if (qe->parent->periodicannouncefrequency &&
    			(res = say_periodic_announcement(qe,ringing)))
    
    		/* see if we need to move to the next penalty level for this queue */
    
    		while (qe->pr && ((time(NULL) - qe->start) >= qe->pr->time)) {
    
    		/* If we have timed out, break out */
    
    		if (qe->expire && (time(NULL) >= qe->expire)) {
    
    			*reason = QUEUE_TIMEOUT;
    			break;
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Wait a second before checking again */
    
    		if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    			if (res > 0 && !valid_exit(qe, res)) {
    
    Olle Johansson's avatar
    Olle Johansson committed
    			} else {
    
    		/* If we have timed out, break out */
    
    		if (qe->expire && (time(NULL) >= qe->expire)) {
    
    			*reason = QUEUE_TIMEOUT;
    			break;
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    /*!
     * \brief update the queue status
     * \retval Always 0
    */
    
    static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl, int newtalktime)
    
    	struct ao2_iterator queue_iter;
    
    
    	if (shared_lastcall) {
    		queue_iter = ao2_iterator_init(queues, 0);
    
    		while ((qtmp = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) {
    
    			if ((mem = ao2_find(qtmp->members, member, OBJ_POINTER))) {
    
    				time(&mem->lastcall);
    				mem->calls++;
    				mem->lastqueue = q;
    				ao2_ref(mem, -1);
    			}
    			ao2_unlock(qtmp);
    
    		ao2_iterator_destroy(&queue_iter);