Skip to content
Snippets Groups Projects
app_queue.c 170 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	} else if (!strcasecmp(param, "setqueuevar")) {
    		q->setqueuevar = ast_true(val);
    	} else if (!strcasecmp(param, "setqueueentryvar")) {
    		q->setqueueentryvar = ast_true(val);
    
    	} else if (!strcasecmp(param, "monitor-format")) {
    		ast_copy_string(q->monfmt, val, sizeof(q->monfmt));
    
    	} else if (!strcasecmp(param, "membermacro")) {
    
    		ast_string_field_set(q, membermacro, val);
    
    	} else if (!strcasecmp(param, "membergosub")) {
    
    		ast_string_field_set(q, membergosub, val);
    
    	} else if (!strcasecmp(param, "queue-youarenext")) {
    
    		ast_string_field_set(q, sound_next, val);
    
    	} else if (!strcasecmp(param, "queue-thereare")) {
    
    		ast_string_field_set(q, sound_thereare, val);
    
    	} else if (!strcasecmp(param, "queue-callswaiting")) {
    
    		ast_string_field_set(q, sound_calls, val);
    
    	} else if (!strcasecmp(param, "queue-holdtime")) {
    
    		ast_string_field_set(q, sound_holdtime, val);
    
    	} else if (!strcasecmp(param, "queue-minutes")) {
    
    		ast_string_field_set(q, sound_minutes, val);
    
    	} else if (!strcasecmp(param, "queue-seconds")) {
    
    		ast_string_field_set(q, sound_seconds, val);
    
    	} else if (!strcasecmp(param, "queue-lessthan")) {
    
    		ast_string_field_set(q, sound_lessthan, val);
    
    	} else if (!strcasecmp(param, "queue-thankyou")) {
    
    		ast_string_field_set(q, sound_thanks, val);
    
    	} else if (!strcasecmp(param, "queue-callerannounce")) {
    
    		ast_string_field_set(q, sound_callerannounce, val);
    
    	} else if (!strcasecmp(param, "queue-reporthold")) {
    
    		ast_string_field_set(q, sound_reporthold, val);
    
    	} else if (!strcasecmp(param, "announce-frequency")) {
    		q->announcefrequency = atoi(val);
    
    	} else if (!strcasecmp(param, "min-announce-frequency")) {
    		q->minannouncefrequency = atoi(val);
    
    		ast_debug(1, "%s=%s for queue '%s'\n", param, val, q->name);
    
    	} else if (!strcasecmp(param, "announce-round-seconds")) {
    		q->roundingseconds = atoi(val);
    
    		/* Rounding to any other values just doesn't make sense... */
    		if (!(q->roundingseconds == 0 || q->roundingseconds == 1 || q->roundingseconds == 5 || q->roundingseconds == 10
    			|| q->roundingseconds == 15 || q->roundingseconds == 20 || q->roundingseconds == 30)) {
    
    			if (linenum >= 0) {
    				ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
    					"using 0 instead for queue '%s' at line %d of queues.conf\n",
    					val, param, q->name, linenum);
    			} else {
    				ast_log(LOG_WARNING, "'%s' isn't a valid value for %s "
    					"using 0 instead for queue '%s'\n", val, param, q->name);
    			}
    			q->roundingseconds=0;
    		}
    	} else if (!strcasecmp(param, "announce-holdtime")) {
    		if (!strcasecmp(val, "once"))
    			q->announceholdtime = ANNOUNCEHOLDTIME_ONCE;
    		else if (ast_true(val))
    			q->announceholdtime = ANNOUNCEHOLDTIME_ALWAYS;
    		else
    			q->announceholdtime = 0;
    
    	} else if (!strcasecmp(param, "announce-position")) {
    		q->announceposition = ast_true(val);
    
    	} else if (!strcasecmp(param, "periodic-announce")) {
    
    			char *s, *buf = ast_strdupa(val);
    			unsigned int i = 0;
    
    
    				if (!q->sound_periodicannounce[i])
    					q->sound_periodicannounce[i] = ast_str_create(16);
    				ast_str_set(&q->sound_periodicannounce[i], 0, s);
    
    				if (i == MAX_PERIODIC_ANNOUNCEMENTS)
    					break;
    
    			ast_str_set(&q->sound_periodicannounce[0], 0, val);
    
    	} else if (!strcasecmp(param, "periodic-announce-frequency")) {
    		q->periodicannouncefrequency = atoi(val);
    
    	} else if (!strcasecmp(param, "retry")) {
    		q->retry = atoi(val);
    
    		if (q->retry <= 0)
    
    			q->retry = DEFAULT_RETRY;
    	} else if (!strcasecmp(param, "wrapuptime")) {
    		q->wrapuptime = atoi(val);
    
    	} else if (!strcasecmp(param, "autofill")) {
    		q->autofill = ast_true(val);
    
    	} else if (!strcasecmp(param, "monitor-type")) {
    		if (!strcasecmp(val, "mixmonitor"))
    			q->montype = 1;
    
    	} else if (!strcasecmp(param, "autopause")) {
    		q->autopause = ast_true(val);
    
    	} else if (!strcasecmp(param, "maxlen")) {
    		q->maxlen = atoi(val);
    		if (q->maxlen < 0)
    			q->maxlen = 0;
    	} else if (!strcasecmp(param, "servicelevel")) {
    		q->servicelevel= atoi(val);
    	} else if (!strcasecmp(param, "strategy")) {
    
    		/* We already have set this, no need to do it again */
    		return;
    
    	} else if (!strcasecmp(param, "joinempty")) {
    
    		if (!strcasecmp(val, "loose"))
    			q->joinempty = QUEUE_EMPTY_LOOSE;
    		else if (!strcasecmp(val, "strict"))
    
    			q->joinempty = QUEUE_EMPTY_STRICT;
    		else if (ast_true(val))
    			q->joinempty = QUEUE_EMPTY_NORMAL;
    		else
    			q->joinempty = 0;
    	} else if (!strcasecmp(param, "leavewhenempty")) {
    
    		if (!strcasecmp(val, "loose"))
    			q->leavewhenempty = QUEUE_EMPTY_LOOSE;
    		else if (!strcasecmp(val, "strict"))
    
    			q->leavewhenempty = QUEUE_EMPTY_STRICT;
    		else if (ast_true(val))
    			q->leavewhenempty = QUEUE_EMPTY_NORMAL;
    		else
    			q->leavewhenempty = 0;
    	} else if (!strcasecmp(param, "eventmemberstatus")) {
    		q->maskmemberstatus = !ast_true(val);
    	} else if (!strcasecmp(param, "eventwhencalled")) {
    
    		if (!strcasecmp(val, "vars")) {
    
    			q->eventwhencalled = QUEUE_EVENT_VARIABLES;
    		} else {
    
    			q->eventwhencalled = ast_true(val) ? 1 : 0;
    
    	} else if (!strcasecmp(param, "reportholdtime")) {
    		q->reportholdtime = ast_true(val);
    	} else if (!strcasecmp(param, "memberdelay")) {
    		q->memberdelay = atoi(val);
    	} else if (!strcasecmp(param, "weight")) {
    		q->weight = atoi(val);
    		if (q->weight)
    			use_weight++;
    		/* With Realtime queues, if the last queue using weights is deleted in realtime,
    		   we will not see any effect on use_weight until next reload. */
    	} else if (!strcasecmp(param, "timeoutrestart")) {
    		q->timeoutrestart = ast_true(val);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	} else if (failunknown) {
    
    		if (linenum >= 0) {
    			ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n",
    				q->name, param, linenum);
    		} else {
    			ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s\n", q->name, param);
    		}
    	}
    }
    
    
    static void rt_handle_member_record(struct call_queue *q, char *interface, const char *membername, const char *penalty_str, const char *paused_str)
    
    	struct member *m, tmpmem;
    
    	if (paused_str) {
    		paused = atoi(paused_str);
    		if (paused < 0)
    			paused = 0;
    	}
    
    
    	/* Find the member, or the place to put a new one. */
    
    	ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
    	m = ao2_find(q->members, &tmpmem, OBJ_POINTER);
    
    
    	/* Create a new one if not found, else update penalty */
    	if (!m) {
    
    		if ((m = create_queue_member(interface, membername, penalty, paused))) {
    
    			m->realtime = 1;
    
    			ao2_link(q->members, m);
    
    			ao2_ref(m, -1);
    			m = NULL;
    
    			q->membercount++;
    
    		}
    	} else {
    		m->dead = 0;	/* Do not delete this one. */
    
    		if (paused_str)
    			m->paused = paused;
    
    static void free_members(struct call_queue *q, int all)
    
    	struct member *cur;
    
    	struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
    
    	while ((cur = ao2_iterator_next(&mem_iter))) {
    
    		if (all || !cur->dynamic) {
    			ao2_unlink(q->members, cur);
    			remove_from_interfaces(cur->interface);
    
    			q->membercount--;
    
    	ast_debug(0, "Queue destructor called for queue '%s'!\n", q->name);
    
    	for (i = 0; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
    		if (q->sound_periodicannounce[i])
    			free(q->sound_periodicannounce[i]);
    	}
    
    	ao2_ref(q->members, -1);
    
    }
    
    static struct call_queue *alloc_queue(const char *queuename)
    {
    	struct call_queue *q;
    
    	if ((q = ao2_alloc(sizeof(*q), destroy_queue))) {
    
    		if (ast_string_field_init(q, 64)) {
    			free(q);
    			return NULL;
    		}
    		ast_string_field_set(q, name, queuename);
    
    Russell Bryant's avatar
    Russell Bryant committed
    /*!\brief Reload a single queue via realtime.
       \return Return the queue, or NULL if it doesn't exist.
    
       \note Should be called with the global qlock locked. */
    
    static struct call_queue *find_queue_by_name_rt(const char *queuename, struct ast_variable *queue_vars, struct ast_config *member_config)
    
    	struct call_queue *q, tmpq = {
    		.name = queuename,	
    	};
    
    	struct member *m;
    
    	struct ao2_iterator mem_iter;
    
    	char tmpbuf[64];	/* Must be longer than the longest queue param name. */
    
    	/* Static queues override realtime. */
    
    	if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
    		ao2_lock(q);
    
    				ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name);
    
    	} else if (!member_config)
    		/* Not found in the list, and it's not realtime ... */
    		return NULL;
    
    
    	/* 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_debug(1, "Queue %s not found in realtime.\n", queuename);
    
    
    			q->dead = 1;
    			/* Delete if unused (else will be deleted when last caller leaves). */
    
    		}
    		return NULL;
    	}
    
    	/* Create a new queue if an in-core entry does not exist yet. */
    	if (!q) {
    
    		init_queue(q);		/* Ensure defaults for all parameters not set explicitly. */
    
    	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;
    
    			while ((tmp = strchr(tmp, '_')))
    
    				*tmp++ = '-';
    		} else
    			tmp_name = v->name;
    		queue_set_param(q, tmp_name, v->value, -1, 0);
    	}
    
    
    	/* Temporarily set realtime members dead so we can detect deleted ones. 
    
    	 * Also set the membercount correctly for realtime*/
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    
    		q->membercount++;
    
    		if (m->realtime)
    
    	while ((interface = ast_category_browse(member_config, interface))) {
    		rt_handle_member_record(q, interface,
    
    			ast_variable_retrieve(member_config, interface, "membername"),
    
    			ast_variable_retrieve(member_config, interface, "penalty"),
    			ast_variable_retrieve(member_config, interface, "paused"));
    
    
    	/* Delete all realtime members that have been deleted in DB. */
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    
    			ao2_unlink(q->members, m);
    
    			q->membercount--;
    
    static struct call_queue *load_realtime_queue(const char *queuename)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct ast_variable *queue_vars;
    
    	struct ast_config *member_config = NULL;
    
    	struct call_queue *q = NULL, tmpq = {
    		.name = queuename,	
    	};
    
    
    	/* Find the queue in the in-core list first. */
    
    	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");
    
    				ast_variables_destroy(queue_vars);
    
    		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);
    
    	} else {
    		update_realtime_members(q);
    
    static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value)
    {
    	struct ast_variable *var;
    	int ret = -1;
    
    	if(!(var = ast_load_realtime("queue_members", "interface", mem->interface, "queue_name", queue_name, NULL))) 
    		return ret;
    	while (var) {
    		if(!strcmp(var->name, "uniqueid"))
    			break;
    		var = var->next;
    	}
    	if(var && !ast_strlen_zero(var->value)) {
    		if ((ast_update_realtime("queue_members", "uniqueid", var->value, field, value, NULL)) > -1)
    			ret = 0;
    	}
    	return ret;
    }
    
    
    static void update_realtime_members(struct call_queue *q)
    {
    	struct ast_config *member_config = NULL;
    
    	struct member *m;
    
    	char *interface = NULL;
    
    	struct ao2_iterator mem_iter;
    
    
    	member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , NULL);
    	if (!member_config) {
    		/*This queue doesn't have realtime members*/
    		ast_debug(3, "Queue %s has no realtime members defined. No need for update\n", q->name);
    		return;
    	}
    
    
    	/* Temporarily set realtime  members dead so we can detect deleted ones.*/ 
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    
    		if (m->realtime)
    
    	}
    
    	while ((interface = ast_category_browse(member_config, interface))) {
    		rt_handle_member_record(q, interface,
    			S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface),
    			ast_variable_retrieve(member_config, interface, "penalty"),
    			ast_variable_retrieve(member_config, interface, "paused"));
    	}
    
    	/* Delete all realtime members that have been deleted in DB. */
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((m = ao2_iterator_next(&mem_iter))) {
    
    			ao2_unlink(q->members, m);
    
    			remove_from_interfaces(m->interface);
    
    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)))
    
    	if (!q->joinempty && (stat == QUEUE_NO_MEMBERS))
    		*reason = QUEUE_JOINEMPTY;
    
    	else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS))
    		*reason = QUEUE_JOINUNAVAIL;
    	else if ((q->joinempty == QUEUE_EMPTY_LOOSE) && (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 );
    
    		ast_debug(1, "Queue '%s' Join, Channel '%s', Position '%d'\n", q->name, qe->chan->name, qe->pos );
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int play_file(struct ast_channel *chan, const 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)) {
    
    		qe->valid_digits = 1;
    
    		/* Return 1 on a successful goto */
    
    static int say_position(struct queue_ent *qe, int ringing)
    
    	int res = 0, avgholdmins, avgholdsecs;
    
    	/* Let minannouncefrequency seconds pass between the start of each position announcement */
    
    	time(&now);
    
    	if ((now - qe->last_pos) < qe->parent->minannouncefrequency)
    
    
    	/* 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))
    
    	if (ringing) {
    		ast_indicate(qe->chan,-1);
    	} else {
    		ast_moh_stop(qe->chan);
    	}
    
    	if (qe->parent->announceposition) {
    		/* Say we're next, if we are */
    		if (qe->pos == 1) {
    			res = play_file(qe->chan, qe->parent->sound_next);
    			if (res)
    				goto playout;
    			else
    				goto posout;
    		} else {
    			res = play_file(qe->chan, qe->parent->sound_thereare);
    			if (res)
    				goto playout;
    			res = ast_say_number(qe->chan, qe->pos, AST_DIGIT_ANY, qe->chan->language, (char *) NULL); /* Needs gender */
    			if (res)
    				goto playout;
    			res = play_file(qe->chan, qe->parent->sound_calls);
    			if (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;
    
    	ast_verb(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)) {
    
    		if (avgholdmins > 0) {
    
    				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);
    
    			res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, NULL);
    
    				goto playout;
    
    			res = play_file(qe->chan, qe->parent->sound_seconds);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    posout:
    
    	if (qe->parent->announceposition) {
    		ast_verb(3, "Told %s in %s their queue position (which was %d)\n",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			qe->chan->name, qe->parent->name, qe->pos);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    playout:
    
    	if ((res > 0 && !valid_exit(qe, res)) || res < 0)
    
    	/* 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 */
    
    	if (!res) {
                    if (ringing)
                            ast_indicate(qe->chan, AST_CONTROL_RINGING);
                    else
                            ast_moh_start(qe->chan, qe->moh, NULL);
    	}
    
    static void recalc_holdtime(struct queue_ent *qe, int newholdtime)
    
    
    	/* Calculate holdtime using 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 */
    
    
    	oldvalue = qe->parent->holdtime;
    
    	qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
    
    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);
    
    			ast_debug(1, "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;
    		}
    	}
    
    	ao2_unlock(q);
    
    	/*If the queue is a realtime queue, check to see if it's still defined in real time*/
    	if(q->realtime) {
    		if(!ast_load_realtime("queues", "name", q->name, NULL))
    			q->dead = 1;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		/* It's dead and nobody is in it, so kill it */
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    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;
    
    		if (oo->member)
    			ao2_ref(oo->member, -1);
    
    static int update_status(struct call_queue *q, struct member *member, int status)
    
    	struct ao2_iterator mem_iter;
    
    	/* Since a reload could have taken place, we have to traverse the list to
    		be sure it's still valid */
    
    	mem_iter = ao2_iterator_init(q->members, 0);
    	while ((cur = ao2_iterator_next(&mem_iter))) {
    
    		if (member != cur) {
    			ao2_ref(cur, -1);
    
    
    		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"
    
    				"MemberName: %s\r\n"
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				"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->membername, cur->dynamic ? "dynamic" : cur->realtime ? "realtime": "static",
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    				cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused);
    
    		ao2_ref(cur, -1);
    
    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 */
    
    	queue_iter = ao2_iterator_init(queues, 0);
    	while ((q = ao2_iterator_next(&queue_iter))) {
    		if (q == rq) { /* don't check myself, could deadlock */
    			queue_unref(q);
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    			continue;
    
    		if (q->count && q->members) {
    
    			if ((mem = ao2_find(q->members, member, OBJ_POINTER))) {
    
    				ast_debug(1, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
    
    				if (q->weight > rq->weight) {
    
    					ast_debug(1, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count);
    
    				ao2_ref(mem, -1);
    
    /*! \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)
    {
    
    	struct ast_str *buf = ast_str_alloca(len + 1);
    	char *tmp;
    
    	if (pbx_builtin_serialize_variables(chan, &buf)) {
    
    		int i, j;
    
    		/* convert "\n" to "\nVariable: " */
    		strcpy(vars, "Variable: ");
    
    
    		for (i = 0, j = 10; (i < len - 1) && (j < len - 1); i++, j++) {
    			vars[j] = tmp[i];
    
    			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 ((tmp->lastqueue && tmp->lastqueue->wrapuptime && (time(NULL) - tmp->lastcall < tmp->lastqueue->wrapuptime)) ||
    		(!tmp->lastqueue && qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime))) {
    		ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n", 
    				(tmp->lastqueue ? tmp->lastqueue->name : qe->parent->name), tmp->interface);
    
    		if (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)) {
    
    		ast_debug(1, "%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) {
    
    		ast_debug(1, "%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_debug(1, "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);
    
    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)
    
    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)
    
    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)
    
    	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 */
    
    		ast_debug(1, "ast call on peer returned %d\n", res);
    
    		ast_verb(3, "Couldn't call %s\n", tmp->interface);
    
    	} else if (qe->parent->eventwhencalled) {
    		char vars[2048];
    
    		manager_event(EVENT_FLAG_AGENT, "AgentCalled",