Newer
Older
} 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);
Steve Murphy
committed
} 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);
Russell Bryant
committed
} 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);
Jason Parker
committed
/* 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")) {
Tilghman Lesher
committed
if (strchr(val, ',')) {
char *s, *buf = ast_strdupa(val);
unsigned int i = 0;
Tilghman Lesher
committed
while ((s = strsep(&buf, ",|"))) {
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);
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")) {
Mark Michelson
committed
/* 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")) {
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);
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)
{
int penalty = 0;
penalty = atoi(penalty_str);
penalty = 0;
}
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->dead = 0;
BJ Weschke
committed
add_to_interfaces(interface);
ao2_ref(m, -1);
m = NULL;
}
} else {
m->dead = 0; /* Do not delete this one. */
if (paused_str)
m->paused = paused;
m->penalty = penalty;
}
}
static void free_members(struct call_queue *q, int all)
Russell Bryant
committed
{
/* Free non-dynamic members */
struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
Russell Bryant
committed
while ((cur = ao2_iterator_next(&mem_iter))) {
if (all || !cur->dynamic) {
ao2_unlink(q->members, cur);
remove_from_interfaces(cur->interface);
Russell Bryant
committed
}
}
Mark Michelson
committed
static void destroy_queue(void *obj)
Russell Bryant
committed
{
Mark Michelson
committed
struct call_queue *q = obj;
int i;
Mark Michelson
committed
ast_debug(0, "Queue destructor called for queue '%s'!\n", q->name);
Russell Bryant
committed
free_members(q, 1);
ast_string_field_free_memory(q);
for (i = 0; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
if (q->sound_periodicannounce[i])
free(q->sound_periodicannounce[i]);
}
Mark Michelson
committed
}
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);
Mark Michelson
committed
}
return q;
Russell Bryant
committed
}
/*!\brief Reload a single queue via realtime.
\return Return the queue, or NULL if it doesn't exist.
Tilghman Lesher
committed
\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 ast_variable *v;
struct call_queue *q, tmpq = {
.name = queuename,
};
struct ao2_iterator mem_iter;
Joshua Colp
committed
char *interface = NULL;
const char *tmp_name;
char *tmp;
char tmpbuf[64]; /* Must be longer than the longest queue param name. */
/* Static queues override realtime. */
Mark Michelson
committed
if ((q = ao2_find(queues, &tmpq, OBJ_POINTER))) {
ao2_lock(q);
if (!q->realtime) {
if (q->dead) {
Mark Michelson
committed
ao2_unlock(q);
queue_unref(q);
return NULL;
} else {
ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name);
Mark Michelson
committed
ao2_unlock(q);
return q;
}
}
Mark Michelson
committed
queue_unref(q);
} 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) {
/*! \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). */
Mark Michelson
committed
ao2_unlink(queues, q);
ao2_unlock(q);
queue_unref(q);
}
return NULL;
}
/* Create a new queue if an in-core entry does not exist yet. */
if (!q) {
BJ Weschke
committed
if (!(q = alloc_queue(queuename)))
return NULL;
Mark Michelson
committed
ao2_lock(q);
clear_queue(q);
q->realtime = 1;
init_queue(q); /* Ensure defaults for all parameters not set explicitly. */
Mark Michelson
committed
ao2_link(queues, q);
}
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 = 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))) {
}
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))) {
if (m->dead) {
Mark Michelson
committed
ao2_unlock(q);
BJ Weschke
committed
remove_from_interfaces(m->interface);
Mark Michelson
committed
ao2_lock(q);
}
}
Mark Michelson
committed
ao2_unlock(q);
Tilghman Lesher
committed
return q;
}
static struct call_queue *load_realtime_queue(const char *queuename)
struct ast_variable *queue_vars;
struct ast_config *member_config = NULL;
struct call_queue *q = NULL, tmpq = {
.name = queuename,
};
Tilghman Lesher
committed
/* Find the queue in the in-core list first. */
Mark Michelson
committed
q = ao2_find(queues, &tmpq, OBJ_POINTER);
Tilghman Lesher
committed
Tilghman Lesher
committed
/*! \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);
Tilghman Lesher
committed
return NULL;
}
}
Mark Michelson
committed
ao2_lock(queues);
Tilghman Lesher
committed
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);
Mark Michelson
committed
ao2_unlock(queues);
Tilghman Lesher
committed
} else {
update_realtime_members(q);
Tilghman Lesher
committed
}
return 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 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;
}
Mark Michelson
committed
ao2_lock(q);
/* 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))) {
}
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))) {
Mark Michelson
committed
ao2_unlock(q);
remove_from_interfaces(m->interface);
Mark Michelson
committed
ao2_lock(q);
Mark Michelson
committed
ao2_unlock(q);
Tilghman Lesher
committed
static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason)
{
struct queue_ent *cur, *prev = NULL;
int res = -1;
int pos = 0;
int inserted = 0;
enum queue_member_status stat;
if (!(q = load_realtime_queue(queuename)))
Tilghman Lesher
committed
return res;
Mark Michelson
committed
ao2_lock(queues);
ao2_lock(q);
/* This is our one */
Kevin P. Fleming
committed
stat = get_member_status(q, qe->max_penalty);
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
* 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;
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 Michelson
committed
ao2_unlock(q);
ao2_unlock(queues);
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)
Kevin P. Fleming
committed
res = ast_waitstream(chan, AST_DIGIT_ANY);
ast_stopstream(chan);
return res;
}
Kevin P. Fleming
committed
static int valid_exit(struct queue_ent *qe, char digit)
{
Kevin P. Fleming
committed
int digitlen = strlen(qe->digits);
Kevin P. Fleming
committed
Kevin P. Fleming
committed
/* 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;
}
Kevin P. Fleming
committed
if (ast_strlen_zero(qe->context))
return 0;
Kevin P. Fleming
committed
/* 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 */
Kevin P. Fleming
committed
return 1;
}
Kevin P. Fleming
committed
return 0;
}
Steve Murphy
committed
static int say_position(struct queue_ent *qe, int ringing)
int res = 0, avgholdmins, avgholdsecs;
Russell Bryant
committed
/* Let minannouncefrequency seconds pass between the start of each position announcement */
Russell Bryant
committed
if ((now - qe->last_pos) < qe->parent->minannouncefrequency)
Kevin P. Fleming
committed
return 0;
/* 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))
Kevin P. Fleming
committed
return 0;
Steve Murphy
committed
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 */
Mark Spencer
committed
if ((avgholdmins+avgholdsecs) > 0 && (qe->parent->announceholdtime) &&
(!(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE) && qe->last_pos)) {
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_holdtime);
Kevin P. Fleming
committed
goto playout;
Mark Spencer
committed
if (avgholdmins < 2) {
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_lessthan);
Kevin P. Fleming
committed
goto playout;
res = ast_say_number(qe->chan, 2, AST_DIGIT_ANY, qe->chan->language, NULL);
Kevin P. Fleming
committed
goto playout;
} else {
res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, NULL);
Kevin P. Fleming
committed
goto playout;
}
res = play_file(qe->chan, qe->parent->sound_minutes);
Kevin P. Fleming
committed
goto playout;
Kevin P. Fleming
committed
if (avgholdsecs>0) {
res = ast_say_number(qe->chan, avgholdsecs, AST_DIGIT_ANY, qe->chan->language, NULL);
Kevin P. Fleming
committed
goto playout;
res = play_file(qe->chan, qe->parent->sound_seconds);
Kevin P. Fleming
committed
goto playout;
if (qe->parent->announceposition) {
ast_verb(3, "Told %s in %s their queue position (which was %d)\n",
Kevin P. Fleming
committed
res = play_file(qe->chan, qe->parent->sound_thanks);
if ((res > 0 && !valid_exit(qe, res)) || res < 0)
/* Don't restart music on hold if we're about to exit the caller from the queue */
Steve Murphy
committed
if (!res) {
if (ringing)
ast_indicate(qe->chan, AST_CONTROL_RINGING);
else
ast_moh_start(qe->chan, qe->moh, NULL);
}
Kevin P. Fleming
committed
return res;
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 */
Mark Michelson
committed
ao2_lock(qe->parent);
qe->parent->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
Mark Michelson
committed
ao2_unlock(qe->parent);
static void leave_queue(struct queue_ent *qe)
{
struct queue_ent *cur, *prev = NULL;
int pos = 0;
Mark Spencer
committed
Mark Michelson
committed
queue_ref(q);
ao2_lock(q);
for (cur = q->head; cur; cur = cur->next) {
/* 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 );
/* 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 Michelson
committed
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 Michelson
committed
if (q->dead) {
Mark Michelson
committed
ao2_unlink(queues, q);
queue_unref(q);
Mark Michelson
committed
queue_unref(q);
Mark Spencer
committed
/* Hang up a list of outgoing calls */
static void hangupcalls(struct callattempt *outgoing, struct ast_channel *exception)
struct callattempt *oo;
Mark Spencer
committed
if (outgoing->chan && (outgoing->chan != exception))
outgoing = outgoing->q_next;
if (oo->member)
ao2_ref(oo->member, -1);
Tilghman Lesher
committed
ast_free(oo);
static int update_status(struct call_queue *q, struct member *member, int status)
Mark Spencer
committed
{
struct member *cur;
struct ao2_iterator mem_iter;
Mark Spencer
committed
Mark Spencer
committed
/* Since a reload could have taken place, we have to traverse the list to
be sure it's still valid */
Mark Michelson
committed
ao2_lock(q);
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",
"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",
cur->penalty, cur->calls, (int)cur->lastcall, cur->status, cur->paused);
Mark Spencer
committed
}
Mark Spencer
committed
}
Mark Michelson
committed
ao2_unlock(q);
Mark Spencer
committed
return 0;
}
static int update_dial_status(struct 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 call_queue *rq, struct member *member)
Mark Spencer
committed
{
Mark Michelson
committed
struct ao2_iterator queue_iter;
/* &qlock and &rq->lock already set by try_calling()
* to solve deadlock */
Mark Michelson
committed
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);
Mark Michelson
committed
}
ao2_lock(q);
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);
Mark Michelson
committed
ao2_unlock(q);
if (found) {
queue_unref(q);
Mark Michelson
committed
}
queue_unref(q);
/*! \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: ");
tmp = buf->str;
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)
{
int res;
Mark Spencer
committed
int status;
char tech[256];
char *location;
/* on entry here, we know that tmp->chan == NULL */
Mark Michelson
committed
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;
(*busies)++;
Kevin P. Fleming
committed
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);
Kevin P. Fleming
committed
if (qe->chan->cdr)
ast_cdr_busy(qe->chan->cdr);
tmp->stillgoing = 0;
return 0;
}
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;
}
Kevin P. Fleming
committed
ast_copy_string(tech, tmp->interface, sizeof(tech));
if ((location = strchr(tech, '/')))
*location++ = '\0';
else
location = "";
/* Request the peer */
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;
update_dial_status(qe->parent, tmp->member, status);
Mark Michelson
committed
ao2_lock(qe->parent);
qe->parent->rrpos++;
Mark Michelson
committed
qe->linpos++;
Mark Michelson
committed
ao2_unlock(qe->parent);
Mark Michelson
committed
(*busies)++;
return 0;
update_dial_status(qe->parent, tmp->member, status);
Mark Spencer
committed
tmp->chan->appl = "AppQueue";
tmp->chan->data = "(Outgoing Line)";
tmp->chan->whentohangup = 0;
if (tmp->chan->cid.cid_num)
Tilghman Lesher
committed
ast_free(tmp->chan->cid.cid_num);
tmp->chan->cid.cid_num = ast_strdup(qe->chan->cid.cid_num);
if (tmp->chan->cid.cid_name)
Tilghman Lesher
committed
ast_free(tmp->chan->cid.cid_name);
tmp->chan->cid.cid_name = ast_strdup(qe->chan->cid.cid_name);
if (tmp->chan->cid.cid_ani)
Tilghman Lesher
committed
ast_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 */
ast_debug(1, "ast call on peer returned %d\n", res);
ast_verb(3, "Couldn't call %s\n", tmp->interface);
do_hang(tmp);
(*busies)++;
return 0;
} else if (qe->parent->eventwhencalled) {
char vars[2048];
manager_event(EVENT_FLAG_AGENT, "AgentCalled",
"Queue: %s\r\n"