Newer
Older
ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface));
Mark Michelson
committed
queue_iter = ao2_iterator_init(queues, 0);
while ((q = ao2_iterator_next(&queue_iter))) {
ao2_lock(q);
Mark Michelson
committed
mem_iter = ao2_iterator_init(q->members, 0);
while ((mem = ao2_iterator_next(&mem_iter))) {
if (!strcasecmp(mem->state_interface, interface)) {
ao2_ref(mem, -1);
ret = 1;
break;
}
Mark Michelson
committed
}
ao2_unlock(q);
queue_unref(q);
BJ Weschke
committed
}
return ret;
}
static int remove_from_interfaces(const char *interface)
BJ Weschke
committed
{
struct member_interface *curint;
BJ Weschke
committed
if (interface_exists_global(interface))
return 0;
BJ Weschke
committed
AST_LIST_LOCK(&interfaces);
AST_LIST_TRAVERSE_SAFE_BEGIN(&interfaces, curint, list) {
if (!strcasecmp(curint->interface, interface)) {
ast_debug(1, "Removing %s from the list of interfaces that make up all of our queue members.\n", interface);
Mark Michelson
committed
AST_LIST_REMOVE_CURRENT(list);
BJ Weschke
committed
}
}
AST_LIST_TRAVERSE_SAFE_END;
AST_LIST_UNLOCK(&interfaces);
BJ Weschke
committed
}
static void clear_and_free_interfaces(void)
{
struct member_interface *curint;
BJ Weschke
committed
AST_LIST_LOCK(&interfaces);
while ((curint = AST_LIST_REMOVE_HEAD(&interfaces, list)))
Tilghman Lesher
committed
ast_free(curint);
BJ Weschke
committed
AST_LIST_UNLOCK(&interfaces);
}
Mark Michelson
committed
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
/*Note: call this with the rule_lists locked */
static int insert_penaltychange (const char *list_name, const char *content, const int linenum)
{
char *timestr, *maxstr, *minstr, *contentdup;
struct penalty_rule *rule = NULL, *rule_iter;
struct rule_list *rl_iter;
int time, inserted = 0;
if (!(rule = ast_calloc(1, sizeof(*rule)))) {
ast_log(LOG_ERROR, "Cannot allocate memory for penaltychange rule at line %d!\n", linenum);
return -1;
}
contentdup = ast_strdupa(content);
if (!(maxstr = strchr(contentdup, ','))) {
ast_log(LOG_WARNING, "Improperly formatted penaltychange rule at line %d. Ignoring.\n", linenum);
ast_free(rule);
return -1;
}
*maxstr++ = '\0';
timestr = contentdup;
if ((time = atoi(timestr)) < 0) {
ast_log(LOG_WARNING, "Improper time parameter specified for penaltychange rule at line %d. Ignoring.\n", linenum);
ast_free(rule);
return -1;
}
rule->time = time;
if ((minstr = strchr(maxstr,',')))
*minstr++ = '\0';
/* The last check will evaluate true if either no penalty change is indicated for a given rule
* OR if a min penalty change is indicated but no max penalty change is */
if (*maxstr == '+' || *maxstr == '-' || *maxstr == '\0') {
rule->max_relative = 1;
}
rule->max_value = atoi(maxstr);
if (!ast_strlen_zero(minstr)) {
if (*minstr == '+' || *minstr == '-')
rule->min_relative = 1;
rule->min_value = atoi(minstr);
} else /*there was no minimum specified, so assume this means no change*/
rule->min_relative = 1;
/*We have the rule made, now we need to insert it where it belongs*/
AST_LIST_TRAVERSE(&rule_lists, rl_iter, list){
if (strcasecmp(rl_iter->name, list_name))
continue;
AST_LIST_TRAVERSE_SAFE_BEGIN(&rl_iter->rules, rule_iter, list) {
if (rule->time < rule_iter->time) {
AST_LIST_INSERT_BEFORE_CURRENT(rule, list);
inserted = 1;
break;
}
}
AST_LIST_TRAVERSE_SAFE_END;
if (!inserted) {
AST_LIST_INSERT_TAIL(&rl_iter->rules, rule, list);
}
}
return 0;
}
For error reporting, line number is passed for .conf static configuration.
For Realtime queues, linenum is -1.
The failunknown flag is set for config files (and static realtime) to show
errors for unknown parameters. It is cleared for dynamic realtime to allow
extra fields in the tables. */
static void queue_set_param(struct call_queue *q, const char *param, const char *val, int linenum, int failunknown)
{
Kevin P. Fleming
committed
if (!strcasecmp(param, "musicclass") ||
!strcasecmp(param, "music") || !strcasecmp(param, "musiconhold")) {
ast_string_field_set(q, moh, val);
} else if (!strcasecmp(param, "announce")) {
ast_string_field_set(q, announce, val);
} else if (!strcasecmp(param, "context")) {
ast_string_field_set(q, context, val);
} else if (!strcasecmp(param, "timeout")) {
q->timeout = atoi(val);
if (q->timeout < 0)
q->timeout = DEFAULT_TIMEOUT;
Kevin P. Fleming
committed
} else if (!strcasecmp(param, "ringinuse")) {
q->ringinuse = ast_true(val);
} else if (!strcasecmp(param, "setinterfacevar")) {
q->setinterfacevar = ast_true(val);
} 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);
Mark Michelson
committed
} else if (!strcasecmp(param, "queue-minute")) {
ast_string_field_set(q, sound_minute, val);
} else if (!strcasecmp(param, "queue-seconds")) {
ast_string_field_set(q, sound_seconds, 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... */
Mark Michelson
committed
if (!(q->roundingseconds == 0 || q->roundingseconds == 5 || q->roundingseconds == 10
Jason Parker
committed
|| 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);
Mark Michelson
committed
} else if (!strcasecmp(param, "defaultrule")) {
ast_string_field_set(q, defaultrule, 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);
}
}
}
Mark Michelson
committed
static void rt_handle_member_record(struct call_queue *q, char *interface, const char *membername, const char *penalty_str, const char *paused_str, const char* state_interface)
{
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) {
Mark Michelson
committed
if ((m = create_queue_member(interface, membername, penalty, paused, state_interface))) {
m->dead = 0;
add_to_interfaces(m->state_interface);
ao2_ref(m, -1);
m = NULL;
}
} else {
m->dead = 0; /* Do not delete this one. */
if (paused_str)
m->paused = paused;
Mark Michelson
committed
if (strcasecmp(state_interface, m->state_interface)) {
Mark Michelson
committed
remove_from_interfaces(m->state_interface);
ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface));
add_to_interfaces(m->state_interface);
}
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);
Mark Michelson
committed
remove_from_interfaces(cur->state_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) {
Mark Michelson
committed
struct ast_variable *tmpvar = NULL;
BJ Weschke
committed
if (!(q = alloc_queue(queuename)))
return NULL;
Mark Michelson
committed
ao2_lock(q);
clear_queue(q);
q->realtime = 1;
Mark Michelson
committed
/*Before we initialize the queue, we need to set the strategy, so that linear strategy
* will allocate the members properly
*/
for (tmpvar = queue_vars; tmpvar; tmpvar = tmpvar->next) {
Mark Michelson
committed
q->strategy = strat2int(tmpvar->value);
if (q->strategy < 0) {
ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
tmpvar->value, q->name);
q->strategy = QUEUE_STRATEGY_RINGALL;
}
break;
}
}
/* We traversed all variables and didn't find a strategy */
if (!tmpvar)
q->strategy = QUEUE_STRATEGY_RINGALL;
init_queue(q); /* Ensure defaults for all parameters not set explicitly. */
Mark Michelson
committed
ao2_link(queues, q);
Mark Michelson
committed
ast_variables_destroy(tmpvar);
}
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,
Mark Michelson
committed
S_OR(ast_variable_retrieve(member_config, interface, "membername"),interface),
ast_variable_retrieve(member_config, interface, "penalty"),
Mark Michelson
committed
ast_variable_retrieve(member_config, interface, "paused"),
Mark Michelson
committed
S_OR(ast_variable_retrieve(member_config, interface, "state_interface"),interface));
/* 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
remove_from_interfaces(m->state_interface);
}
}
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;
Mark Michelson
committed
if (!(var = ast_load_realtime("queue_members", "interface", mem->interface, "queue_name", queue_name, NULL)))
return ret;
while (var) {
Mark Michelson
committed
if (!strcmp(var->name, "uniqueid"))
break;
var = var->next;
}
Mark Michelson
committed
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;
if (!(member_config = ast_load_realtime_multientry("queue_members", "interface LIKE", "%", "queue_name", q->name , NULL))) {
/*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"),
Mark Michelson
committed
ast_variable_retrieve(member_config, interface, "paused"),
Mark Michelson
committed
S_OR(ast_variable_retrieve(member_config, interface, "state_interface"), interface));
}
/* 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
remove_from_interfaces(m->state_interface);
Mark Michelson
committed
ao2_unlock(q);
ast_config_destroy(member_config);
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 */
Mark Michelson
committed
stat = get_member_status(q, qe->max_penalty, qe->min_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, 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;
Mark Michelson
committed
ast_verb(3, "Hold time for %s is %d minute(s) %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 Michelson
committed
if (avgholdmins > 1) {
res = ast_say_number(qe->chan, avgholdmins, AST_DIGIT_ANY, qe->chan->language, NULL);
if (res)
goto playout;
Kevin P. Fleming
committed
Mark Michelson
committed
if (avgholdmins == 1) {
res = play_file(qe->chan, qe->parent->sound_minute);
Kevin P. Fleming
committed
goto playout;
} else {
Mark Michelson
committed
res = play_file(qe->chan, qe->parent->sound_minutes);
Kevin P. Fleming
committed
goto playout;
}
Mark Michelson
committed
if (avgholdsecs > 1) {
res = ast_say_number(qe->chan, avgholdmins > 1 ? avgholdsecs : avgholdmins * 60 + 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)
{
Mark Michelson
committed
struct penalty_rule *pr_iter;
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;
Mark Michelson
committed
/* Free penalty rules */
while ((pr_iter = AST_LIST_REMOVE_HEAD(&qe->qe_rules, list)))
ast_free(pr_iter);
/* 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*/
Mark Michelson
committed
if (q->realtime) {
if (!ast_load_realtime("queues", "name", q->name, NULL))
Mark Michelson
committed
q->dead = 1;
}
Mark Michelson
committed
if (q->dead) {
Mark Michelson
committed
ao2_unlink(queues, q);
/* unref the container's reference to the queue */
Mark Michelson
committed
queue_unref(q);
/* unref the explicit ref earlier in the function */
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()