Skip to content
Snippets Groups Projects
app_queue.c 161 KiB
Newer Older
  • Learn to ignore specific revisions
  • {
    	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);
    
    		} else {
    			/* Ring just the best channel */
    
    			ast_debug(1, "Trying '%s' with metric %d\n", best->interface, best->metric);
    
    		if (best->chan) /* break out with result = 1 */
    			ret = 1;
    
    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++;
    		}
    
    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;
    }
    
    
    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 */
    
    	if ((now - qe->last_periodic_announce_time) < qe->parent->periodicannouncefrequency)
    		return 0;
    
    
    	/* Stop the music on hold so we can play our own file */
    
    	if (ringing)
    		ast_indicate(qe->chan,-1);
    	else
    		ast_moh_stop(qe->chan);
    
    	ast_verb(3, "Playing periodic announcement\n");
    
    	/* Check to make sure we have a sound file. If not, reset to the first sound file */
    
    Russell Bryant's avatar
    Russell Bryant committed
    	if (qe->last_periodic_announce_sound >= MAX_PERIODIC_ANNOUNCEMENTS || ast_strlen_zero(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound])) {
    
    		qe->last_periodic_announce_sound = 0;
    	}
    	
    
    	/* play the announcement */
    
    	res = play_file(qe->chan, qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]);
    
    
    	if ((res > 0 && !valid_exit(qe, res)) || res < 0)
    
    	/* Resume Music on Hold if the caller is going to stay in the queue */
    
    		if (ringing)
    			ast_indicate(qe->chan, AST_CONTROL_RINGING);
    		else
    			ast_moh_start(qe->chan, qe->moh, NULL);
    
    
    	/* update last_periodic_announce_time */
    	qe->last_periodic_announce_time = now;
    
    
    	/* Update the current periodic announcement to the next announcement */
    	qe->last_periodic_announce_sound++;
    	
    
    }
    
    static void record_abandoned(struct queue_ent *qe)
    {
    
    	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, qe->chan->uniqueid, 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)
    
    	ast_verb(3, "Nobody picked up in %d ms\n", rnatime);
    
    	ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime);
    	if (qe->parent->autopause) {
    
    		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);
    
    			ast_verb(3, "Failed to pause Queue Member %s in queue %s!\n", interface, qe->parent->name);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	return;
    }
    
    static struct callattempt *wait_for_answer(struct queue_ent *qe, struct callattempt *outgoing, int *to, char *digit, int prebusies, int caller_disconnect, int forwardsallowed)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int sentringing = 0;
    
    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
    
    	starttime = (long) time(NULL);
    
    	for (epollo = outgoing; epollo; epollo = epollo->q_next) {
    		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 (o->chan)
    						watchers[pos++] = o->chan;
    				}
    				numlines++;
    			}
    			if (pos > 1 /* found */ || !stillgoing /* nobody listening */ ||
    
    Tilghman Lesher's avatar
    Tilghman Lesher 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_log(LOG_NOTICE, "No one is answering queue '%s' (%d/%d/%d)\n", queue, numlines, numbusies, numnochan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    			*to = 0;
    			return NULL;
    		}
    		winner = ast_waitfor_n(watchers, pos, to);
    
    		for (o = outgoing; o; o = o->q_next) {
    
    			if (o->stillgoing && (o->chan) &&  (o->chan->_state == AST_STATE_UP)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				if (!peer) {
    
    					ast_verb(3, "%s answered %s\n", o->chan->name, in->name);
    
    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));
    
    
    				if (!ast_strlen_zero(o->chan->call_forward) && !forwardsallowed) {
    
    					ast_verb(3, "Forwarding %s to '%s' prevented.\n", in->name, o->chan->call_forward);
    
    					numnochan++;
    					do_hang(o);
    					winner = NULL;
    					continue;
    
    				} else if (!ast_strlen_zero(o->chan->call_forward)) {
    
    					ast_copy_string(tmpchan, o->chan->call_forward, sizeof(tmpchan));
    
    					if ((stuff = strchr(tmpchan, '/'))) {
    
    						tech = tmpchan;
    					} else {
    						snprintf(tmpchan, sizeof(tmpchan), "%s@%s", o->chan->call_forward, o->chan->context);
    						stuff = tmpchan;
    						tech = "Local";
    					}
    					/* Before processing channel, go ahead and check for forwarding */
    
    					ast_verb(3, "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, o->chan->name);
    
    					o->chan = ast_request(tech, in->nativeformats, stuff, &status);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					if (status != o->oldstatus)
    
    Mark Spencer's avatar
    Mark Spencer committed
    						update_dial_status(qe->parent, o->member, status);						
    
    					if (!o->chan) {
    						ast_log(LOG_NOTICE, "Unable to create local channel for call forward to '%s/%s'\n", tech, stuff);
    						o->stillgoing = 0;
    						numnochan++;
    					} else {
    
    						ast_channel_inherit_variables(in, o->chan);
    
    						o->chan->cid.cid_num = ast_strdup(in->cid.cid_num);
    
    
    						o->chan->cid.cid_name = ast_strdup(in->cid.cid_name);
    
    						ast_string_field_set(o->chan, accountcode, in->accountcode);
    
    						o->chan->cdrflags = in->cdrflags;
    
    						if (in->cid.cid_ani) {
    							if (o->chan->cid.cid_ani)
    
    							o->chan->cid.cid_ani = ast_strdup(in->cid.cid_ani);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    						if (o->chan->cid.cid_rdnis)
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    						o->chan->cid.cid_rdnis = ast_strdup(S_OR(in->macroexten, in->exten));
    
    						if (ast_call(o->chan, tmpchan, 0)) {
    							ast_log(LOG_NOTICE, "Failed to dial on local channel for call forward to '%s'\n", tmpchan);
    
    							numnochan++;
    						}
    					}
    					/* 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) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    						switch (f->subclass) {
    
    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", o->chan->name, in->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							}
    							break;
    						case AST_CONTROL_BUSY:
    
    							ast_verb(3, "%s is busy\n", o->chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							if (in->cdr)
    								ast_cdr_busy(in->cdr);
    
    							rna(endtime*1000, qe, on, membername);
    
    							if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
    
    									*to = orig;
    
    								ring_one(qe, outgoing, &numbusies);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							numbusies++;
    							break;
    						case AST_CONTROL_CONGESTION:
    
    							ast_verb(3, "%s is circuit-busy\n", o->chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							if (in->cdr)
    								ast_cdr_busy(in->cdr);
    
    							rna(endtime*1000, qe, on, membername);
    
    							if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
    
    									*to = orig;
    
    								ring_one(qe, outgoing, &numbusies);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							numbusies++;
    							break;
    						case AST_CONTROL_RINGING:
    
    							ast_verb(3, "%s is ringing\n", o->chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							if (!sentringing) {
    #if 0
    								ast_indicate(in, AST_CONTROL_RINGING);
    #endif								
    								sentringing++;
    							}
    							break;
    						case AST_CONTROL_OFFHOOK:
    							/* Ignore going off hook */
    							break;
    						default:
    
    							ast_debug(1, "Dunno what to do with control type %d\n", f->subclass);
    
    Mark Spencer's avatar
    Mark Spencer committed
    						}
    					}
    					ast_frfree(f);
    				} else {
    
    					endtime = (long) time(NULL) - starttime;
    
    					rna(endtime * 1000, qe, on, membername);
    
    					if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) {
    
    							*to = orig;
    
    						ring_one(qe, outgoing, &numbusies);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    			}
    		}
    		if (winner == in) {
    			f = ast_read(in);
    			if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
    				/* Got hung up */
    
    Mark Spencer's avatar
    Mark Spencer committed
    				return NULL;
    			}
    
    			if ((f->frametype == AST_FRAME_DTMF) && caller_disconnect && (f->subclass == '*')) {
    
    				ast_verb(3, "User hit %c to disconnect call.\n", f->subclass);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				return NULL;
    			}
    
    			if ((f->frametype == AST_FRAME_DTMF) && valid_exit(qe, f->subclass)) {
    
    				ast_verb(3, "User pressed digit: %c\n", f->subclass);
    
    				*to = 0;
    				*digit = f->subclass;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (!*to)
    
    			rna(orig, qe, on, membername);
    
    	for(epollo = outgoing; epollo; epollo = epollo->q_next) {
    		if(epollo->chan)
    			ast_poll_channel_del(in, epollo->chan);
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return peer;
    }
    
    
    static int is_our_turn(struct queue_ent *qe)
    {
    	struct queue_ent *ch;
    
    	struct member *cur;
    	int avl = 0;
    	int idx = 0;
    
    	if (!qe->parent->autofill) {
    		/* Atomically read the parent head -- does not need a lock */
    		ch = qe->parent->head;
    		/* If we are now at the top of the head, break out */
    
    			ast_debug(1, "It's our turn (%s).\n", qe->chan->name);
    
    			ast_debug(1, "It's not our turn (%s).\n", qe->chan->name);
    
    		/* This needs a lock. How many members are available to be served? */
    
    		if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) {
    
    			ast_debug(1, "Even though there are %d available members, the strategy is ringall so only the head call is allowed in\n", avl);
    
    			struct ao2_iterator mem_iter = ao2_iterator_init(qe->parent->members, 0);
    			while ((cur = ao2_iterator_next(&mem_iter))) {
    
    				switch (cur->status) {
    				case AST_DEVICE_NOT_INUSE:
    				case AST_DEVICE_UNKNOWN:
    
    					if (!cur->paused)
    						avl++;
    
    				ao2_ref(cur, -1);
    
    		ast_debug(1, "There are %d available members.\n", avl);
    
    	
    		while ((idx < avl) && (ch) && (ch != qe)) {
    			idx++;
    			ch = ch->next;			
    		}
    	
    		/* If the queue entry is within avl [the number of available members] calls from the top ... */
    		if (ch && idx < avl) {
    
    			ast_debug(1, "It's our turn (%s).\n", qe->chan->name);
    
    			ast_debug(1, "It's not our turn (%s).\n", qe->chan->name);
    
    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 (;;) {
    
    			break;
    
    		/* If we have timed out, break out */
    
    		if (qe->expire && (time(NULL) > qe->expire)) {
    			*reason = QUEUE_TIMEOUT;
    
    Mark Spencer's avatar
    Mark Spencer committed
    			break;
    
    		stat = get_member_status(qe->parent, qe->max_penalty);
    
    		/* leave the queue if no agents, if enabled */
    
    		if (qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) {
    			*reason = QUEUE_LEAVEEMPTY;
    
    			ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
    
    			leave_queue(qe);
    			break;
    		}
    
    		/* leave the queue if no reachable agents, if enabled */
    
    		if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) {
    			*reason = QUEUE_LEAVEUNAVAIL;
    
    			ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
    
    			leave_queue(qe);
    			break;
    		}
    		if ((qe->parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) {
    
    			ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
    
    		/* Make a position announcement, if enabled */
    
    		if (qe->parent->announcefrequency &&
    			(res = say_position(qe,ringing)))
    
    		/* Make a periodic announcement, if enabled */
    
    		if (qe->parent->periodicannouncefrequency &&
    			(res = say_periodic_announcement(qe,ringing)))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Wait a second before checking again */
    
    		if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) {
    			if (res > 0 && !valid_exit(qe, res))
    				res = 0;
    			else
    				break;
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl)
    
    	struct member *mem;
    	struct call_queue *qtmp;
    	struct ao2_iterator queue_iter;	
    	
    	if (shared_lastcall) {
    		queue_iter = ao2_iterator_init(queues, 0);
    		while ((qtmp = ao2_iterator_next(&queue_iter))) {
    			ao2_lock(qtmp);
    			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_ref(qtmp, -1);
    		}
    	} else {
    		ao2_lock(q);
    		time(&member->lastcall);
    		member->calls++;
    		member->lastqueue = q;
    		ao2_unlock(q);
    	}	
    
    	q->callscompleted++;
    
    	if (callcompletedinsl)
    		q->callscompletedinsl++;
    
    static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct queue_ent *qe, struct callattempt *tmp)
    
    	if (qe->max_penalty && (mem->penalty > qe->max_penalty))
    
    	switch (q->strategy) {
    	case QUEUE_STRATEGY_RINGALL:
    
    		/* Everyone equal, except for penalty */
    		tmp->metric = mem->penalty * 1000000;
    
    		break;
    
    	case QUEUE_STRATEGY_LINEAR:
    		if (pos < qe->linpos) {
    			tmp->metric = 1000 + pos;
    		} else {
    			if (pos > qe->linpos)
    				/* Indicate there is another priority */
    				qe->linwrapped = 1;
    			tmp->metric = pos;
    		}
    		tmp->metric += mem->penalty * 1000000;
    		break;
    
    	case QUEUE_STRATEGY_RRMEMORY:
    
    		if (pos < q->rrpos) {
    			tmp->metric = 1000 + pos;
    		} else {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				/* Indicate there is another priority */
    
    		tmp->metric += mem->penalty * 1000000;
    
    		tmp->metric += mem->penalty * 1000000;
    		break;
    	case QUEUE_STRATEGY_FEWESTCALLS:
    		tmp->metric = mem->calls;
    		tmp->metric += mem->penalty * 1000000;
    		break;
    	case QUEUE_STRATEGY_LEASTRECENT:
    		if (!mem->lastcall)
    			tmp->metric = 0;
    		else
    			tmp->metric = 1000000 - (time(NULL) - mem->lastcall);
    		tmp->metric += mem->penalty * 1000000;
    
    		break;
    	default:
    		ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy);
    		break;
    
    enum agent_complete_reason {
    	CALLER,
    	AGENT,
    	TRANSFER
    };
    
    static void send_agent_complete(const struct queue_ent *qe, const char *queuename,
    	const struct ast_channel *peer, const struct member *member, time_t callstart,
    	char *vars, size_t vars_len, enum agent_complete_reason rsn)
    {
    	const char *reason;
    
    	if (!qe->parent->eventwhencalled)
    		return;
    
    	switch (rsn) {
    	case CALLER:
    		reason = "caller";
    		break;
    	case AGENT:
    		reason = "agent";
    		break;
    	case TRANSFER:
    		reason = "transfer";
    		break;
    	}
    
    	manager_event(EVENT_FLAG_AGENT, "AgentComplete",
    		"Queue: %s\r\n"
    		"Uniqueid: %s\r\n"
    		"Channel: %s\r\n"
    		"Member: %s\r\n"
    		"MemberName: %s\r\n"
    		"HoldTime: %ld\r\n"
    		"TalkTime: %ld\r\n"
    		"Reason: %s\r\n"
    		"%s",
    		queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
    		(long)(callstart - qe->start), (long)(time(NULL) - callstart), reason,
    		qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, vars_len) : "");
    }
    
    
    static int try_calling(struct queue_ent *qe, const char *options, char *announceoverride, const char *url, int *tries, int *noption, const char *agi, const char *macro, const char *gosub, int ringing)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	struct member *cur;
    
    	struct callattempt *outgoing = NULL; /* the list of calls we are building */
    
    	char oldexten[AST_MAX_EXTENSION]="";
    
    	char oldcontext[AST_MAX_CONTEXT]="";
    
    	char queuename[256]="";
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_channel *peer;
    
    	struct member *member;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res = 0, bridge = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char *announce = NULL;
    
    	time_t callstart;
    
    	struct ast_bridge_config bridge_config;
    	char nondataquality = 1;
    
    	const char *monitorfilename;
    	const char *monitor_exec;
    	const char *monitor_options;
    	char tmpid[256], tmpid2[256];
    	char meid[1024], meid2[1024];
    	char mixmonargs[1512];
    	struct ast_app *mixmonapp = NULL;
    	char *p;
    
    	int callcompletedinsl;
    
    	struct ao2_iterator memi;
    
    	memset(&bridge_config, 0, sizeof(bridge_config));
    
    		
    	for (; options && *options; options++)
    		switch (*options) {
    		case 't':
    			ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_REDIRECT);
    			break;
    		case 'T':
    			ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_REDIRECT);
    			break;
    
    		case 'w':
    			ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_AUTOMON);
    			break;
    		case 'W':
    			ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_AUTOMON);
    			break;
    
    		case 'd':
    			nondataquality = 0;
    			break;
    		case 'h':
    			ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_DISCONNECT);
    			break;
    		case 'H':
    			ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT);
    			break;
    
                    case 'k':
                            ast_set_flag(&(bridge_config.features_callee), AST_FEATURE_PARKCALL);
                            break;
                    case 'K':
                            ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_PARKCALL);
                            break;
    
    			if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY || qe->parent->strategy == QUEUE_STRATEGY_LINEAR)
    
    				*tries = qe->parent->membercount;
    			*noption = 1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	/* Hold the lock while we setup the outgoing calls */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (use_weight)
    
    	ast_debug(1, "%s is trying to call a queue member.\n",
    
    	ast_copy_string(queuename, qe->parent->name, sizeof(queuename));
    
    	if (!ast_strlen_zero(qe->announce))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		announce = qe->announce;
    
    Kevin P. Fleming's avatar
    Kevin P. Fleming committed
    	if (!ast_strlen_zero(announceoverride))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		announce = announceoverride;
    
    	memi = ao2_iterator_init(qe->parent->members, 0);
    	while ((cur = ao2_iterator_next(&memi))) {
    
    		struct callattempt *tmp = ast_calloc(1, sizeof(*tmp));
    
    			ao2_ref(cur, -1);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			if (use_weight)
    
    Mark Spencer's avatar
    Mark Spencer committed
    			goto out;
    		}
    
    		tmp->stillgoing = -1;
    
    		tmp->member = cur;
    
    		tmp->lastcall = cur->lastcall;
    
    		ast_copy_string(tmp->interface, cur->interface, sizeof(tmp->interface));
    
    		/* Special case: If we ring everyone, go ahead and ring them, otherwise
    		   just calculate their metric for the appropriate strategy */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			/* Put them in the list of outgoing thingies...  We're ready now.
    
    			   XXX If we're forcibly removed, these outgoing calls won't get
    			   hung up XXX */
    
    			outgoing = tmp;		
    			/* If this line is up, don't try anybody else */
    			if (outgoing->chan && (outgoing->chan->_state == AST_STATE_UP))
    				break;
    		} else {
    
    			ao2_ref(cur, -1);
    			free(tmp);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if (qe->expire && (!qe->parent->timeout || (qe->expire - now) <= qe->parent->timeout))
    		to = (qe->expire - now) * 1000;
    	else
    		to = (qe->parent->timeout) ? qe->parent->timeout * 1000 : -1;
    
    	ring_one(qe, outgoing, &numbusies);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if (use_weight)
    
    	lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
    
    	if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) {
    
    		store_next_rr(qe, outgoing);
    	}
    	if (qe->parent->strategy == QUEUE_STRATEGY_LINEAR) {
    		store_next_lin(qe, outgoing);
    
    	peer = lpeer ? lpeer->chan : NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!peer) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			res = -1;
    
    			/* User exited by pressing a digit */
    
    		if (res == -1)
    			ast_debug(1, "%s: Nobody answered.\n", qe->chan->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Ah ha!  Someone answered within the desired timeframe.  Of course after this
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		   we will always return with -1 so that it is hung up properly after the
    
    Mark Spencer's avatar
    Mark Spencer committed
    		   conversation.  */
    
    		if (!strcmp(qe->chan->tech->type, "Zap"))
    
    			ast_channel_setoption(qe->chan, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
    
    		if (!strcmp(peer->tech->type, "Zap"))
    
    			ast_channel_setoption(peer, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0);
    
    		/* Update parameters for the queue */
    
    		time(&now);
    		recalc_holdtime(qe, (now - qe->start));
    
    		callcompletedinsl = ((now - qe->start) <= qe->parent->servicelevel);
    
    		member = lpeer->member;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		outgoing = NULL;
    
    		if (announce || qe->parent->reportholdtime || qe->parent->memberdelay) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			int res2;
    
    			res2 = ast_autoservice_start(qe->chan);
    
    				if (qe->parent->memberdelay) {
    					ast_log(LOG_NOTICE, "Delaying member connect for %d seconds\n", qe->parent->memberdelay);
    					res2 |= ast_safe_sleep(peer, qe->parent->memberdelay * 1000);
    				}
    				if (!res2 && announce) {
    
    					play_file(peer, announce);
    
    					if (!play_file(peer, qe->parent->sound_reporthold)) {
    						int holdtime;
    
    						time(&now);
    						holdtime = abs((now - qe->start) / 60);
    						if (holdtime < 2) {
    							play_file(peer, qe->parent->sound_lessthan);
    							ast_say_number(peer, 2, AST_DIGIT_ANY, peer->language, NULL);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    						} else
    
    							ast_say_number(peer, holdtime, AST_DIGIT_ANY, peer->language, NULL);
    						play_file(peer, qe->parent->sound_minutes);
    					}
    
    			res2 |= ast_autoservice_stop(qe->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				/* Agent must have hung up */
    
    				ast_log(LOG_WARNING, "Agent on %s hungup on the customer.\n", peer->name);
    
    				ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "AGENTDUMP", "%s", "");
    
    Olle Johansson's avatar
    Olle Johansson committed
    				record_abandoned(qe);
    
    				if (qe->parent->eventwhencalled)
    
    					manager_event(EVENT_FLAG_AGENT, "AgentDump",
    
    							"Queue: %s\r\n"
    							"Uniqueid: %s\r\n"
    							"Channel: %s\r\n"
    							"Member: %s\r\n"
    
    							"MemberName: %s\r\n"
    
    							queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername,
    
    							qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
    
    Mark Spencer's avatar
    Mark Spencer committed
    				ast_hangup(peer);
    
    				goto out;
    			} else if (res2) {
    				/* Caller must have hung up just before being connected*/
    				ast_log(LOG_NOTICE, "Caller was about to talk to agent on %s but the caller hungup.\n", peer->name);
    
    				ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "ABANDON", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start);
    
    				record_abandoned(qe);
    				ast_hangup(peer);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				return -1;
    			}
    		}
    
    		/* Stop music on hold */
    
    		if (ringing)
    			ast_indicate(qe->chan,-1);
    		else
    			ast_moh_stop(qe->chan);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* If appropriate, log that we have a destination channel */
    		if (qe->chan->cdr)
    			ast_cdr_setdestchan(qe->chan->cdr, peer->name);
    		/* Make sure channels are compatible */
    		res = ast_channel_make_compatible(qe->chan, peer);
    		if (res < 0) {
    
    			ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "SYSCOMPAT", "%s", "");
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", qe->chan->name, peer->name);
    
    			record_abandoned(qe);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_hangup(peer);
    			return -1;
    		}
    
    
    		/* Play announcement to the caller telling it's his turn if defined */
    		if (!ast_strlen_zero(qe->parent->sound_callerannounce)) {
    			if (play_file(qe->chan, qe->parent->sound_callerannounce))
    				ast_log(LOG_WARNING, "Announcement file '%s' is unavailable, continuing anyway...\n", qe->parent->sound_callerannounce);
    		}
    
    
    		/* if setinterfacevar is defined, make member variables available to the channel */
    		/* use  pbx_builtin_setvar to set a load of variables with one call */
    		if (qe->parent->setinterfacevar) {
    			snprintf(interfacevar,sizeof(interfacevar), "MEMBERINTERFACE=%s|MEMBERNAME=%s|MEMBERCALLS=%d|MEMBERLASTCALL=%ld|MEMBERPENALTY=%d|MEMBERDYNAMIC=%d|MEMBERREALTIME=%d",
    				member->interface, member->membername, member->calls, (long)member->lastcall, member->penalty, member->dynamic, member->realtime);
    		 	pbx_builtin_setvar(qe->chan, interfacevar);
    		}
    		
    		/* if setqueueentryvar is defined, make queue entry (i.e. the caller) variables available to the channel */
    		/* use  pbx_builtin_setvar to set a load of variables with one call */
    		if (qe->parent->setqueueentryvar) {
    			snprintf(interfacevar,sizeof(interfacevar), "QEHOLDTIME=%ld|QEORIGINALPOS=%d",
    				(long) time(NULL) - qe->start, qe->opos);
    			pbx_builtin_setvar(qe->chan, interfacevar);
    		}
    	
    		/* try to set queue variables if configured to do so*/
    		set_queue_variables(qe);
    
    		/* Begin Monitoring */
    		if (qe->parent->monfmt && *qe->parent->monfmt) {
    
    				ast_debug(1, "Starting Monitor as requested.\n");
    
    				monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME");
    				if (pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC") || pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC_ARGS"))
    					which = qe->chan;
    				else
    					which = peer;
    				if (monitorfilename)
    
    					ast_monitor_start(which, qe->parent->monfmt, monitorfilename, 1, X_REC_IN | X_REC_OUT);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				else if (qe->chan->cdr)
    
    					ast_monitor_start(which, qe->parent->monfmt, qe->chan->cdr->uniqueid, 1, X_REC_IN | X_REC_OUT);
    
    				else {
    					/* Last ditch effort -- no CDR, make up something */
    					snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
    
    					ast_monitor_start(which, qe->parent->monfmt, tmpid, 1, X_REC_IN | X_REC_OUT);
    
    				ast_debug(1, "Starting MixMonitor as requested.\n");
    
    				monitorfilename = pbx_builtin_getvar_helper(qe->chan, "MONITOR_FILENAME");
    				if (!monitorfilename) {
    					if (qe->chan->cdr)
    
    						ast_copy_string(tmpid, qe->chan->cdr->uniqueid, sizeof(tmpid));
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    					else
    
    						snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random());
    				} else {
    
    					const char *m = monitorfilename;
    					for (p = tmpid2; p < tmpid2 + sizeof(tmpid2) - 1; p++, m++) {
    						switch (*m) {
    						case '^':
    							if (*(m + 1) == '{')
    								*p = '$';
    							break;
    						case ',':
    							*p++ = '\\';
    							/* Fall through */
    						default:
    							*p = *m;
    
    					if (p == tmpid2 + sizeof(tmpid2))
    						tmpid2[sizeof(tmpid2) - 1] = '\0';
    
    
    					pbx_substitute_variables_helper(qe->chan, tmpid2, tmpid, sizeof(tmpid) - 1);
    				}
    
    				monitor_exec = pbx_builtin_getvar_helper(qe->chan, "MONITOR_EXEC");
    				monitor_options = pbx_builtin_getvar_helper(qe->chan, "MONITOR_OPTIONS");
    
    				if (monitor_exec) {
    
    					const char *m = monitor_exec;
    					for (p = meid2; p < meid2 + sizeof(meid2) - 1; p++, m++) {
    						switch (*m) {
    						case '^':
    							if (*(m + 1) == '{')
    								*p = '$';
    							break;
    						case ',':
    							*p++ = '\\';
    							/* Fall through */
    						default:
    							*p = *m;