Skip to content
Snippets Groups Projects
app_queue.c 131 KiB
Newer Older
  • Learn to ignore specific revisions
  • 
    	/* Check if queue is defined in realtime. */
    	if (!queue_vars) {
    		/* Delete queue from in-core list if it has been deleted in realtime. */
    		if (q) {
    
    Russell Bryant's avatar
    Russell Bryant committed
    			/*! \note Hmm, can't seem to distinguish a DB failure from a not
    
    			   found condition... So we might delete an in-core queue
    			   in case of DB failure. */
    			ast_log(LOG_DEBUG, "Queue %s not found in realtime.\n", queuename);
    
    			q->dead = 1;
    			/* Delete if unused (else will be deleted when last caller leaves). */
    			if (!q->count) {
    				/* Delete. */
    
    				AST_LIST_REMOVE(&queues, q, list);
    
    			} else
    				ast_mutex_unlock(&q->lock);
    		}
    		return NULL;
    	}
    
    	/* Create a new queue if an in-core entry does not exist yet. */
    	if (!q) {
    
    			return NULL;
    		ast_mutex_lock(&q->lock);
    		clear_queue(q);
    		q->realtime = 1;
    
    		AST_LIST_INSERT_HEAD(&queues, q, list);
    
    	}
    	init_queue(q);		/* Ensure defaults for all parameters not set explicitly. */
    
    	memset(tmpbuf, 0, sizeof(tmpbuf));
    
    	for (v = queue_vars; v; v = v->next) {
    
    		/* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */
    
    		if ((tmp = strchr(v->name, '_'))) {
    
    			ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
    			tmp_name = tmpbuf;
    			tmp = tmp_name;
    
    			while ((tmp = strchr(tmp, '_')))
    
    				*tmp++ = '-';
    		} else
    			tmp_name = v->name;
    		queue_set_param(q, tmp_name, v->value, -1, 0);
    	}
    
    
    	if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN)
    		rr_dep_warning();
    
    
    	/* Temporarily set non-dynamic members dead so we can detect deleted ones. */
    
    	for (m = q->members; m; m = m->next) {
    
    		if (!m->dynamic)
    			m->dead = 1;
    
    	while ((interface = ast_category_browse(member_config, interface)))
    
    		rt_handle_member_record(q, interface, ast_variable_retrieve(member_config, interface, "penalty"));
    
    	/* Delete all realtime members that have been deleted in DB. */
    	m = q->members;
    	prev_m = NULL;
    	while (m) {
    		next_m = m->next;
    		if (m->dead) {
    			if (prev_m) {
    				prev_m->next = next_m;
    			} else {
    				q->members = next_m;
    			}
    
    static struct call_queue *load_realtime_queue(char *queuename)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct ast_variable *queue_vars;
    
    	struct ast_config *member_config = NULL;
    
    	struct call_queue *q;
    
    
    	/* Find the queue in the in-core list first. */
    
    	AST_LIST_LOCK(&queues);
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    	AST_LIST_UNLOCK(&queues);
    
    	if (!q || q->realtime) {
    
    		/*! \note Load from realtime before taking the global qlock, to avoid blocking all
    		   queue operations while waiting for the DB.
    
    		   This will be two separate database transactions, so we might
    		   see queue parameters as they were before another process
    		   changed the queue and member list as it was after the change.
    		   Thus we might see an empty member list when a queue is
    		   deleted. In practise, this is unlikely to cause a problem. */
    
    		queue_vars = ast_load_realtime("queues", "name", queuename, NULL);
    		if (queue_vars) {
    			member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", queuename, NULL);
    			if (!member_config) {
    				ast_log(LOG_ERROR, "no queue_members defined in your config (extconfig.conf).\n");
    				return NULL;
    			}
    		}
    
    
    		AST_LIST_LOCK(&queues);
    
    
    		q = find_queue_by_name_rt(queuename, queue_vars, member_config);
    		if (member_config)
    			ast_config_destroy(member_config);
    		if (queue_vars)
    			ast_variables_destroy(queue_vars);
    
    
    		AST_LIST_UNLOCK(&queues);
    
    	}
    	return q;
    }
    
    static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason)
    {
    
    	struct call_queue *q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct queue_ent *cur, *prev = NULL;
    	int res = -1;
    	int pos = 0;
    
    	if (!(q = load_realtime_queue(queuename)))
    
    	AST_LIST_LOCK(&queues);
    
    	if (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
    		*reason = QUEUE_JOINEMPTY;
    	else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS))
    		*reason = QUEUE_JOINUNAVAIL;
    	else if (q->maxlen && (q->count >= q->maxlen))
    		*reason = QUEUE_FULL;
    	else {
    		/* There's space for us, put us at the right position inside
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		 * the queue.
    
    		 * Take into account the priority of the calling user */
    		inserted = 0;
    		prev = NULL;
    		cur = q->head;
    
    			/* We have higher priority than the current user, enter
    			 * before him, after all the other users with priority
    			 * higher or equal to our priority. */
    			if ((!inserted) && (qe->prio > cur->prio)) {
    				insert_entry(q, prev, qe, &pos);
    				inserted = 1;
    			}
    			cur->pos = ++pos;
    			prev = cur;
    			cur = cur->next;
    		}
    		/* No luck, join at the end of the queue */
    		if (!inserted)
    			insert_entry(q, prev, qe, &pos);
    		ast_copy_string(qe->moh, q->moh, sizeof(qe->moh));
    		ast_copy_string(qe->announce, q->announce, sizeof(qe->announce));
    		ast_copy_string(qe->context, q->context, sizeof(qe->context));
    		q->count++;
    		res = 0;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		manager_event(EVENT_FLAG_CALL, "Join",
    			"Channel: %s\r\nCallerID: %s\r\nCallerIDName: %s\r\nQueue: %s\r\nPosition: %d\r\nCount: %d\r\nUniqueid: %s\r\n",
    			qe->chan->name,
    			S_OR(qe->chan->cid.cid_num, "unknown"), /* XXX somewhere else it is <unknown> */
    			S_OR(qe->chan->cid.cid_name, "unknown"),
    			q->name, qe->pos, q->count, qe->chan->uniqueid );
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	AST_LIST_UNLOCK(&queues);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int play_file(struct ast_channel *chan, char *filename)
    {
    	int res;
    
    	ast_stopstream(chan);
    	res = ast_streamfile(chan, filename, chan->language);
    	if (!res)
    
    	ast_stopstream(chan);
    
    	return res;
    }
    
    
    	/* Prevent possible buffer overflow */
    	if (digitlen < sizeof(qe->digits) - 2) {
    		qe->digits[digitlen] = digit;
    		qe->digits[digitlen + 1] = '\0';
    	} else {
    		qe->digits[0] = '\0';
    		return 0;
    	}
    
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	/* If there's no context to goto, short-circuit */
    
    
    	/* If the extension is bad, then reset the digits to blank */
    	if (!ast_canmatch_extension(qe->chan, qe->context, qe->digits, 1, qe->chan->cid.cid_num)) {
    		qe->digits[0] = '\0';
    		return 0;
    	}
    
    	/* We have an exact match */
    
    	if (!ast_goto_if_exists(qe->chan, qe->context, qe->digits, 1)) {
    		/* Return 1 on a successful goto */
    
    static int say_position(struct queue_ent *qe)
    {
    
    	int res = 0, avgholdmins, avgholdsecs;
    
    	time_t now;
    
    	/* Check to see if this is ludicrous -- if we just announced position, don't do it again*/
    	time(&now);
    
    	if ((now - qe->last_pos) < 15)
    
    
    	/* If either our position has changed, or we are over the freq timer, say position */
    
    	if ((qe->last_pos_said == qe->pos) && ((now - qe->last_pos) < qe->parent->announcefrequency))
    
    
    	ast_moh_stop(qe->chan);
    	/* Say we're next, if we are */
    	if (qe->pos == 1) {
    
    		res = play_file(qe->chan, qe->parent->sound_next);
    		if (res && valid_exit(qe, res))
    			goto playout;
    		else
    			goto posout;
    
    		if (res && valid_exit(qe, res))
    
    			goto playout;
    		res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */
    		if (res && valid_exit(qe, res))
    			goto playout;
    		res = play_file(qe->chan, qe->parent->sound_calls);
    		if (res && valid_exit(qe, res))
    			goto playout;
    
    	}
    	/* Round hold time to nearest minute */
    
    	avgholdmins = abs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60);
    
    
    	/* If they have specified a rounding then round the seconds as well */
    
    	if (qe->parent->roundingseconds) {
    		avgholdsecs = (abs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
    		avgholdsecs *= qe->parent->roundingseconds;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (option_verbose > 2)
    
    		ast_verbose(VERBOSE_PREFIX_3 "Hold time for %s is %d minutes %d seconds\n", qe->parent->name, avgholdmins, avgholdsecs);
    
    
    	/* If the hold time is >1 min, if it's enabled, and if it's not
    	   supposed to be only once and we have already said it, say it */
    
    	if ((avgholdmins+avgholdsecs) > 0 && (qe->parent->announceholdtime) &&
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		(!(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE) && qe->last_pos)) {
    
    		res = play_file(qe->chan, qe->parent->sound_holdtime);
    		if (res && valid_exit(qe, res))
    			goto playout;
    
    
    		if (avgholdmins > 0) {
    
    				res = play_file(qe->chan, qe->parent->sound_lessthan);
    				if (res && valid_exit(qe, res))
    					goto playout;
    
    
    				res = ast_say_number(qe->chan, 2, AST_DIGIT_ANY, qe->chan->language, NULL);
    
    				res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, NULL);
    
    				if (res && valid_exit(qe, res))
    					goto playout;
    			}
    			
    			res = play_file(qe->chan, qe->parent->sound_minutes);
    			if (res && valid_exit(qe, res))
    				goto playout;
    
    			res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, NULL);
    
    			if (res && valid_exit(qe, res))
    				goto playout;
    
    			res = play_file(qe->chan, qe->parent->sound_seconds);
    			if (res && valid_exit(qe, res))
    				goto playout;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    posout:
    
    	if (option_verbose > 2)
    		ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			qe->chan->name, qe->parent->name, qe->pos);
    
    	if (res && !valid_exit(qe, res))
    		res = 0;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    playout:
    
    	/* 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 */
    
    }
    
    static void recalc_holdtime(struct queue_ent *qe)
    {
    	int oldvalue, newvalue;
    
    	/* Calculate holdtime using a recursive boxcar filter */
    	/* Thanks to SRT for this contribution */
    	/* 2^2 (4) is the filter coefficient; a higher exponent would give old entries more weight */
    
    	newvalue = time(NULL) - qe->start;
    
    	ast_mutex_lock(&qe->parent->lock);
    	if (newvalue <= qe->parent->servicelevel)
    
    Olle Johansson's avatar
    Olle Johansson committed
    		qe->parent->callscompletedinsl++;
    
    	oldvalue = qe->parent->holdtime;
    	qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newvalue) >> 2;
    	ast_mutex_unlock(&qe->parent->lock);
    }
    
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void leave_queue(struct queue_ent *qe)
    {
    
    	struct call_queue *q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct queue_ent *cur, *prev = NULL;
    	int pos = 0;
    
    	if (!(q = qe->parent))
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	prev = NULL;
    
    	for (cur = q->head; cur; cur = cur->next) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (cur == qe) {
    			q->count--;
    
    
    			/* Take us out of the queue */
    			manager_event(EVENT_FLAG_CALL, "Leave",
    
    				"Channel: %s\r\nQueue: %s\r\nCount: %d\r\nUniqueid: %s\r\n",
    				qe->chan->name, q->name,  q->count, qe->chan->uniqueid);
    
    			if (option_debug)
    				ast_log(LOG_DEBUG, "Queue '%s' Leave, Channel '%s'\n", q->name, qe->chan->name );
    
    Mark Spencer's avatar
    Mark Spencer committed
    			/* Take us out of the queue */
    			if (prev)
    				prev->next = cur->next;
    			else
    				q->head = cur->next;
    		} else {
    
    			/* Renumber the people after us in the queue based on a new count */
    
    Mark Spencer's avatar
    Mark Spencer committed
    			cur->pos = ++pos;
    			prev = cur;
    		}
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* It's dead and nobody is in it, so kill it */
    
    		AST_LIST_LOCK(&queues);
    		AST_LIST_REMOVE(&queues, q, list);
    		AST_LIST_UNLOCK(&queues);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		destroy_queue(q);
    	}
    }
    
    
    static void hangupcalls(struct callattempt *outgoing, struct ast_channel *exception)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* Hangup any existing lines we have open */
    
    		if (outgoing->chan && (outgoing->chan != exception))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			ast_hangup(outgoing->chan);
    		oo = outgoing;
    
    		outgoing = outgoing->q_next;
    
    static int update_status(struct call_queue *q, struct member *member, int status)
    
    	/* Since a reload could have taken place, we have to traverse the list to
    		be sure it's still valid */
    	ast_mutex_lock(&q->lock);
    
    	for (cur = q->members; cur; cur = cur->next) {
    
    		if (member != cur)
    			continue;
    
    		cur->status = status;
    		if (!q->maskmemberstatus) {
    			manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				"Queue: %s\r\n"
    				"Location: %s\r\n"
    				"Membership: %s\r\n"
    				"Penalty: %d\r\n"
    				"CallsTaken: %d\r\n"
    				"LastCall: %d\r\n"
    				"Status: %d\r\n"
    				"Paused: %d\r\n",
    				q->name, cur->interface, cur->dynamic ? "dynamic" : "static",
    				cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused);
    
    static int update_dial_status(struct call_queue *q, struct member *member, int status)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	if (status == AST_CAUSE_BUSY)
    		status = AST_DEVICE_BUSY;
    	else if (status == AST_CAUSE_UNREGISTERED)
    		status = AST_DEVICE_UNAVAILABLE;
    	else if (status == AST_CAUSE_NOSUCHDRIVER)
    		status = AST_DEVICE_INVALID;
    	else
    		status = AST_DEVICE_UNKNOWN;
    	return update_status(q, member, status);
    }
    
    
    /* 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;
    
    	/* &qlock and &rq->lock already set by try_calling()
    	 * to solve deadlock */
    
    	AST_LIST_TRAVERSE(&queues, q, list) {
    
    		if (q == rq) /* don't check myself, could deadlock */
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			continue;
    
    		ast_mutex_lock(&q->lock);
    		if (q->count && q->members) {
    			for (mem = q->members; mem; mem = mem->next) {
    
    				if (strcmp(mem->interface, member->interface))
    					continue;
    
    				ast_log(LOG_DEBUG, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
    				if (q->weight > rq->weight) {
    					ast_log(LOG_DEBUG, "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);
    					found = 1;
    					break;
    
    				}
    			}
    		}
    		ast_mutex_unlock(&q->lock);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (found)
    
    /*! \brief common hangup actions */
    static void do_hang(struct callattempt *o)
    {
    	o->stillgoing = 0;
    	ast_hangup(o->chan);
    	o->chan = NULL;
    }
    
    
    static char *vars2manager(struct ast_channel *chan, char *vars, size_t len)
    {
    	char *tmp = alloca(len);
    
    	if (pbx_builtin_serialize_variables(chan, tmp, len)) {
    		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];
    
    			if (tmp[i + 1] == '\0')
    				break;
    			if (tmp[i] == '\n') {
    				vars[j] = '\r';
    				vars[++j] = '\n';
    
    				ast_copy_string(&(vars[j]), "Variable: ", len - j);
    				j += 9;
    			}
    		}
    		if (j > len - 1)
    			j = len - 1;
    		vars[j - 2] = '\r';
    		vars[j - 1] = '\n';
    		vars[j] = '\0';
    	} else {
    		/* there are no channel variables; leave it blank */
    		*vars = '\0';
    	}
    	return vars;
    }
    
    
    static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies)
    
    	char tech[256];
    	char *location;
    
    	/* on entry here, we know that tmp->chan == NULL */
    
    	if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) {
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "Wrapuptime not yet expired for %s\n", tmp->interface);
    
    		if (qe->chan->cdr)
    			ast_cdr_busy(qe->chan->cdr);
    		tmp->stillgoing = 0;
    
    	if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) {
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "%s in use, can't receive call\n", tmp->interface);
    		if (qe->chan->cdr)
    			ast_cdr_busy(qe->chan->cdr);
    		tmp->stillgoing = 0;
    		return 0;
    	}
    
    
    	if (tmp->member->paused) {
    		if (option_debug)
    			ast_log(LOG_DEBUG, "%s paused, can't receive call\n", tmp->interface);
    		if (qe->chan->cdr)
    			ast_cdr_busy(qe->chan->cdr);
    		tmp->stillgoing = 0;
    		return 0;
    	}
    
    	if (use_weight && compare_weight(qe->parent,tmp->member)) {
    		ast_log(LOG_DEBUG, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface);
    		if (qe->chan->cdr)
    			ast_cdr_busy(qe->chan->cdr);
    		tmp->stillgoing = 0;
    		(*busies)++;
    		return 0;
    	}
    
    	if ((location = strchr(tech, '/')))
    		*location++ = '\0';
    	else
    		location = "";
    
    
    	tmp->chan = ast_request(tech, qe->chan->nativeformats, location, &status);
    
    	if (!tmp->chan) {			/* If we can't, just go on to the next call */
    		if (qe->chan->cdr)
    			ast_cdr_busy(qe->chan->cdr);
    		tmp->stillgoing = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		update_dial_status(qe->parent, tmp->member, status);
    
    
    		ast_mutex_lock(&qe->parent->lock);
    		qe->parent->rrpos++;
    		ast_mutex_unlock(&qe->parent->lock);
    
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	} else if (status != tmp->oldstatus)
    
    Mark Spencer's avatar
    Mark Spencer committed
    		update_dial_status(qe->parent, tmp->member, status);
    
    	tmp->chan->appl = "AppQueue";
    	tmp->chan->data = "(Outgoing Line)";
    	tmp->chan->whentohangup = 0;
    
    	if (tmp->chan->cid.cid_num)
    		free(tmp->chan->cid.cid_num);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	tmp->chan->cid.cid_num = ast_strdup(qe->chan->cid.cid_num);
    
    	if (tmp->chan->cid.cid_name)
    		free(tmp->chan->cid.cid_name);
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    	tmp->chan->cid.cid_name = ast_strdup(qe->chan->cid.cid_name);
    
    	if (tmp->chan->cid.cid_ani)
    		free(tmp->chan->cid.cid_ani);
    
    	tmp->chan->cid.cid_ani = ast_strdup(qe->chan->cid.cid_ani);
    
    
    	/* Inherit specially named variables from parent channel */
    	ast_channel_inherit_variables(qe->chan, tmp->chan);
    
    
    	/* Presense of ADSI CPE on outgoing channel follows ours */
    	tmp->chan->adsicpe = qe->chan->adsicpe;
    
    	/* 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 */
    		if (option_debug)
    			ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res);
    		else if (option_verbose > 2)
    
    			ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->interface);
    
    	} else if (qe->parent->eventwhencalled) {
    		char vars[2048];
    
    		manager_event(EVENT_FLAG_AGENT, "AgentCalled",
    					"AgentCalled: %s\r\n"
    					"ChannelCalling: %s\r\n"
    					"CallerID: %s\r\n"
    					"CallerIDName: %s\r\n"
    					"Context: %s\r\n"
    					"Extension: %s\r\n"
    					"Priority: %d\r\n"
    					"%s",
    					tmp->interface, qe->chan->name,
    					tmp->chan->cid.cid_num ? tmp->chan->cid.cid_num : "unknown",
    					tmp->chan->cid.cid_name ? tmp->chan->cid.cid_name : "unknown",
    					qe->chan->context, qe->chan->exten, qe->chan->priority,
    					qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : "");
    
    			ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->interface);
    
    /*! \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 */
    
    	return best;
    }
    
    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) {
    			if (option_debug)
    				ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n");
    			break;
    		}
    
    		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) {
    					if (option_debug)
    						ast_log(LOG_DEBUG, "(Parallel) Trying '%s' with metric %d\n", cur->interface, cur->metric);
    					ring_entry(qe, cur, busies);
    
    		} else {
    			/* Ring just the best channel */
    			if (option_debug)
    				ast_log(LOG_DEBUG, "Trying '%s' with metric %d\n", best->interface, best->metric);
    			ring_entry(qe, best, busies);
    
    		if (best->chan) /* break out with result = 1 */
    			ret = 1;
    
    static int store_next(struct queue_ent *qe, struct callattempt *outgoing)
    
    	struct callattempt *best = find_best(outgoing);
    
    	if (best) {
    		/* Ring just the best channel */
    
    		if (option_debug)
    			ast_log(LOG_DEBUG, "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 background_file(struct queue_ent *qe, struct ast_channel *chan, char *filename)
    {
    	int res;
    
    	ast_stopstream(chan);
    	res = ast_streamfile(chan, filename, chan->language);
    
    	if (!res) {
    		/* Wait for a keypress */
    		res = ast_waitstream(chan, AST_DIGIT_ANY);
    
    		if (res < 0 || !valid_exit(qe, res))
    
    			res = 0;
    
    		/* Stop playback */
    		ast_stopstream(chan);
    	}
    	
    	return res;
    }
    
    static int say_periodic_announcement(struct queue_ent *qe)
    {
    	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 */
    	ast_moh_stop(qe->chan);
    
    	if (option_verbose > 2)
    		ast_verbose(VERBOSE_PREFIX_3 "Playing periodic announcement\n");
    
    
    	/* Check to make sure we have a sound file. If not, reset to the first sound file */
    	if (qe->last_periodic_announce_sound >= MAX_PERIODIC_ANNOUNCEMENTS || !strlen(qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound])) {
    		qe->last_periodic_announce_sound = 0;
    	}
    	
    
    	/* play the announcement */
    
    	res = background_file(qe, qe->chan, qe->parent->sound_periodicannounce[qe->last_periodic_announce_sound]);
    
    	/* Resume Music on Hold if the caller is going to stay in the queue */
    	if (!res)
    
    
    	/* 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)
    {
    	ast_mutex_lock(&qe->parent->lock);
    
    	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++;
    	ast_mutex_unlock(&qe->parent->lock);
    }
    
    
    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 *membername)
    {
    	if (option_verbose > 2)
    		ast_verbose( VERBOSE_PREFIX_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, membername, 1)) {
    			if (option_verbose > 2)
    				ast_verbose( VERBOSE_PREFIX_3 "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", membername, qe->parent->name);
    		} else {
    			if (option_verbose > 2)
    				ast_verbose( VERBOSE_PREFIX_3 "Failed to pause Queue Member %s in queue %s!\n", membername, 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)
    
    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;
    
    	starttime = (long) time(NULL);
    
    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)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				ast_log(LOG_DEBUG, "Everyone is busy at this time\n");
    			} 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) {
    					if (option_verbose > 2)
    						ast_verbose( VERBOSE_PREFIX_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));
    
    				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 */
    					if (option_verbose > 2)
    						ast_verbose(VERBOSE_PREFIX_3 "Now forwarding %s to '%s/%s' (thanks to %s)\n", in->name, tech, stuff, o->chan->name);
    					/* Setup parameters */
    
    					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);
    
    						if (o->chan->cid.cid_num)
    							free(o->chan->cid.cid_num);
    
    						o->chan->cid.cid_num = ast_strdup(in->cid.cid_num);
    
    
    						if (o->chan->cid.cid_name)
    							free(o->chan->cid.cid_name);
    
    						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)
    								free(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) {
    								if (option_verbose > 2)
    									ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
    
    Mark Spencer's avatar
    Mark Spencer committed
    							}
    							break;
    						case AST_CONTROL_BUSY:
    							if (option_verbose > 2)
    								ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
    							if (in->cdr)
    								ast_cdr_busy(in->cdr);
    
    							endtime = (long)time(NULL);
    							endtime -= starttime;
    							rna(endtime*1000, qe, on);
    
    							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:
    							if (option_verbose > 2)
    								ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
    							if (in->cdr)
    								ast_cdr_busy(in->cdr);
    
    							endtime = (long)time(NULL);
    							endtime -= starttime;
    							rna(endtime*1000, qe, on);
    
    							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:
    							if (option_verbose > 2)
    								ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
    							if (!sentringing) {
    #if 0