Skip to content
Snippets Groups Projects
app_queue.c 129 KiB
Newer Older
  • Learn to ignore specific revisions
  • 			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. */
    
    	v = queue_vars;
    	memset(tmpbuf, 0, sizeof(tmpbuf));
    	while(v) {
    		/* Convert to dashes `-' from underscores `_' as the latter are more SQL friendly. */
    		if((tmp = strchr(v->name, '_')) != NULL) {
    			ast_copy_string(tmpbuf, v->name, sizeof(tmpbuf));
    			tmp_name = tmpbuf;
    			tmp = tmp_name;
    			while((tmp = strchr(tmp, '_')) != NULL)
    				*tmp++ = '-';
    		} else
    			tmp_name = v->name;
    		queue_set_param(q, tmp_name, v->value, -1, 0);
    		v = v->next;
    	}
    
    
    	if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN)
    		rr_dep_warning();
    
    
    	/* Temporarily set non-dynamic members dead so we can detect deleted ones. */
    
    		if (!m->dynamic)
    			m->dead = 1;
    
    		m = m->next;
    	}
    
    	interface = ast_category_browse(member_config, NULL);
    	while (interface) {
    		rt_handle_member_record(q, interface, ast_variable_retrieve(member_config, interface, "penalty"));
    		interface = ast_category_browse(member_config, interface);
    	}
    
    	/* 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 ast_call_queue *load_realtime_queue(char *queuename)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct ast_variable *queue_vars = NULL;
    	struct ast_config *member_config = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_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 ast_call_queue *q;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct queue_ent *cur, *prev = NULL;
    	int res = -1;
    	int pos = 0;
    
    	q = load_realtime_queue(queuename);
    	if (!q)
    		return res;
    
    	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
    		 * the queue. 
    		 * Take into account the priority of the calling user */
    		inserted = 0;
    		prev = NULL;
    		cur = q->head;
    		while(cur) {
    			/* 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;
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    		/* XXX missing CalledIDnum ? */
    
    		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",
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    			      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)
    
    	else
    		res = 0;
    
    	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;
    	}
    
     	/* 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;
    	} else {
    		avgholdsecs=0;
    	}
    
    
    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) &&
    	    (!(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, (char *)NULL);
    				if (res && valid_exit(qe, res))
    					goto playout;
    			} else {
    				res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, (char*) 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;
    
    		if (avgholdsecs>0) {
    			res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, (char*) 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;
    
     posout:
    	if (option_verbose > 2)
    		ast_verbose(VERBOSE_PREFIX_3 "Told %s in %s their queue position (which was %d)\n",
    			    qe->chan->name, qe->parent->name, qe->pos);
    	res = play_file(qe->chan, qe->parent->sound_thanks);
    
    	if (res && !valid_exit(qe, res))
    		res = 0;
    
    	/* Set our last_pos indicators */
     	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 */
    
    		ast_moh_start(qe->chan, qe->moh);
    
    }
    
    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 ast_call_queue *q;
    	struct queue_ent *cur, *prev = NULL;
    	int pos = 0;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	q = qe->parent;
    	if (!q)
    		return;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	prev = NULL;
    	cur = q->head;
    	while(cur) {
    		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;
    		}
    		cur = cur->next;
    	}
    
    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
    	while(outgoing) {
    		/* 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;
    
    static int update_status(struct ast_call_queue *q, struct member *member, int status)
    {
    	struct member *cur;
    
    	/* 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 (!q->maskmemberstatus) {
    				manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
    					"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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int update_dial_status(struct ast_call_queue *q, struct member *member, int status)
    {
    	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 ast_call_queue *rq, struct member *member)
    {
    
    	struct ast_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 */
    
    			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)) {
    
    					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);
    		if (found) 
    			break;
    	}
    	return found;
    }
    
    
    /*! \brief common hangup actions */
    static void do_hang(struct callattempt *o)
    {
    	o->stillgoing = 0;
    	ast_hangup(o->chan);
    	o->chan = NULL;
    }
    
    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_INUSE)) {
    		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 0
    
    		ast_log(LOG_NOTICE, "Unable to create channel of type '%s' for Queue\n", cur->tech);
    
    #endif			
    		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);
    
    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 */
    
    	res = ast_call(tmp->chan, location, 0);
    
    	if (res) {
    		/* 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);
    
    			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",
    
    						tmp->interface, qe->chan->name,
    
    Luigi Rizzo's avatar
    Luigi Rizzo committed
    						S_OR(tmp->chan->cid.cid_num, "unknown"),
    						S_OR(tmp->chan->cid.cid_name, "unknown"),
    
    						qe->chan->context, qe->chan->exten, qe->chan->priority);
    		}
    
    			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 */
    			!cur->chan &&					/* Isn't already going */
    			(!best || cur->metric < best->metric)) {	/* We haven't found one yet, or it's better */
    				best = cur;
    
    	}
    	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);
    	} else {
    		res = 0;
    	}
    	
    	/*if (res) {
    		ast_log(LOG_WARNING, "ast_streamfile failed on %s \n", chan->name);
    		res = 0;
    	}*/
    
    	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)
    		ast_moh_start(qe->chan, qe->moh);
    
    
    	/* 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",
    	              "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);
    		}
    	}
     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;
    
    	long starttime = 0;
    	long endtime = 0;	
    
    	starttime = (long)time(NULL);
    
    Mark Spencer's avatar
    Mark Spencer 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 */ ||
    
    					 (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)) {
    					char tmpchan[256]="";
    					char *stuff;
    					char *tech;
    
    					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);
    					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 {
    						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);
    
    						}
    						if (o->chan->cid.cid_rdnis) 
    							free(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) {
    						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
    								ast_indicate(in, AST_CONTROL_RINGING);
    #endif								
    								sentringing++;
    							}
    							break;
    						case AST_CONTROL_OFFHOOK:
    							/* Ignore going off hook */
    							break;
    						default:
    							ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
    						}
    					}
    					ast_frfree(f);
    				} else {
    
    					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
    				}
    			}
    		}
    		if (winner == in) {
    			f = ast_read(in);
    #if 0
    			if (f && (f->frametype != AST_FRAME_VOICE))
    					printf("Frame type: %d, %d\n", f->frametype, f->subclass);
    			else if (!f || (f->frametype != AST_FRAME_VOICE))
    				printf("Hangup received on %s\n", in->name);