Newer
Older
Tilghman Lesher
committed
goto default_case;
Mark Spencer
committed
case AST_DEVICE_UNAVAILABLE:
if (conditions & QUEUE_EMPTY_UNAVAILABLE) {
ast_debug(4, "%s is unavailable because his device state is 'unavailable'\n", member->membername);
break;
}
Tilghman Lesher
committed
goto default_case;
case AST_DEVICE_INUSE:
if (conditions & QUEUE_EMPTY_INUSE) {
ast_debug(4, "%s is unavailable because his device state is 'inuse'\n", member->membername);
break;
}
Tilghman Lesher
committed
goto default_case;
case AST_DEVICE_RINGING:
if (conditions & QUEUE_EMPTY_RINGING) {
ast_debug(4, "%s is unavailable because his device state is 'ringing'\n", member->membername);
break;
}
case AST_DEVICE_UNKNOWN:
if (conditions & QUEUE_EMPTY_UNKNOWN) {
ast_debug(4, "%s is unavailable because his device state is 'unknown'\n", member->membername);
break;
}
Tilghman Lesher
committed
/* Fall-through */
Mark Spencer
committed
default:
Tilghman Lesher
committed
default_case:
if (member->paused && (conditions & QUEUE_EMPTY_PAUSED)) {
ast_debug(4, "%s is unavailable because he is paused'\n", member->membername);
break;
} else if ((conditions & QUEUE_EMPTY_WRAPUP) && member->lastcall && q->wrapuptime && (time(NULL) - q->wrapuptime < member->lastcall)) {
ast_debug(4, "%s is unavailable because it has only been %d seconds since his last call (wrapup time is %d)\n", member->membername, (int) (time(NULL) - member->lastcall), q->wrapuptime);
break;
} else {
ao2_iterator_destroy(&mem_iter);
ast_debug(4, "%s is available.\n", member->membername);
return 0;
}
break;
Mark Spencer
committed
}
}
ao2_iterator_destroy(&mem_iter);
Mark Michelson
committed
ao2_unlock(q);
Mark Spencer
committed
}
Mark Michelson
committed
/*! \brief set a member's status based on device state of that member's state_interface.
Mark Michelson
committed
* Lock interface list find sc, iterate through each queues queue_member list for member to
* update state inside queues
*/
static void update_status(struct call_queue *q, struct member *m, const int status)
Jason Parker
committed
queue_publish_member_blob(queue_member_status_type(), queue_member_blob_create(q, m));
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
/*!
* \internal \brief Determine if a queue member is available
* \retval 1 if the member is available
* \retval 0 if the member is not available
*/
static int is_member_available(struct member *mem)
{
int available = 0;
switch (mem->status) {
case AST_DEVICE_INVALID:
case AST_DEVICE_UNAVAILABLE:
break;
case AST_DEVICE_INUSE:
case AST_DEVICE_BUSY:
case AST_DEVICE_RINGING:
case AST_DEVICE_RINGINUSE:
case AST_DEVICE_ONHOLD:
if (!mem->ringinuse) {
break;
}
/* else fall through */
case AST_DEVICE_NOT_INUSE:
case AST_DEVICE_UNKNOWN:
if (!mem->paused) {
available = 1;
}
break;
}
return available;
}
/*! \brief set a member's status based on device state of that member's interface*/
static void device_state_cb(void *unused, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *msg)
struct ao2_iterator miter, qiter;
struct ast_device_state_message *dev_state;
struct member *m;
struct call_queue *q;
char interface[80], *slash_pos;
int found = 0; /* Found this member in any queue */
int found_member; /* Found this member in this queue */
int avail = 0; /* Found an available member in this queue */
if (ast_device_state_message_type() != stasis_message_type(msg)) {
return;
}
dev_state = stasis_message_data(msg);
if (dev_state->eid) {
/* ignore non-aggregate states */
return;
}
qiter = ao2_iterator_init(queues, 0);
Tilghman Lesher
committed
while ((q = ao2_t_iterator_next(&qiter, "Iterate over queues"))) {
avail = 0;
found_member = 0;
miter = ao2_iterator_init(q->members, 0);
for (; (m = ao2_iterator_next(&miter)); ao2_ref(m, -1)) {
if (!found_member) {
ast_copy_string(interface, m->state_interface, sizeof(interface));
if ((slash_pos = strchr(interface, '/'))) {
if (!strncasecmp(interface, "Local/", 6) && (slash_pos = strchr(slash_pos + 1, '/'))) {
*slash_pos = '\0';
}
}
if (!strcasecmp(interface, dev_state->device)) {
update_status(q, m, dev_state->state);
/* check every member until we find one NOT_INUSE */
if (!avail) {
avail = is_member_available(m);
}
if (avail && found_member) {
/* early exit as we've found an available member and the member of interest */
ao2_ref(m, -1);
break;
}
}
if (found_member) {
found = 1;
if (avail) {
ast_devstate_changed(AST_DEVICE_NOT_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s_avail", q->name);
ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s_avail", q->name);
ao2_iterator_destroy(&miter);
Tilghman Lesher
committed
queue_t_unref(q, "Done with iterator");
ao2_iterator_destroy(&qiter);
ast_debug(1, "Device '%s' changed to state '%d' (%s)\n",
dev_state->device,
dev_state->state,
ast_devstate2str(dev_state->state));
ast_debug(3, "Device '%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n",
dev_state->device,
dev_state->state,
ast_devstate2str(dev_state->state));
Russell Bryant
committed
}
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
/*! \brief Helper function which converts from extension state to device state values */
static int extensionstate2devicestate(int state)
{
switch (state) {
case AST_EXTENSION_NOT_INUSE:
state = AST_DEVICE_NOT_INUSE;
break;
case AST_EXTENSION_INUSE:
state = AST_DEVICE_INUSE;
break;
case AST_EXTENSION_BUSY:
state = AST_DEVICE_BUSY;
break;
case AST_EXTENSION_RINGING:
state = AST_DEVICE_RINGING;
break;
case AST_EXTENSION_ONHOLD:
state = AST_DEVICE_ONHOLD;
break;
case AST_EXTENSION_UNAVAILABLE:
state = AST_DEVICE_UNAVAILABLE;
break;
case AST_EXTENSION_REMOVED:
case AST_EXTENSION_DEACTIVATED:
default:
state = AST_DEVICE_INVALID;
break;
}
return state;
}
static int extension_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
{
struct ao2_iterator miter, qiter;
struct member *m;
struct call_queue *q;
int state = info->exten_state;
int found = 0, device_state = extensionstate2devicestate(state);
/* only interested in extension state updates involving device states */
if (info->reason != AST_HINT_UPDATE_DEVICE) {
return 0;
}
qiter = ao2_iterator_init(queues, 0);
Tilghman Lesher
committed
while ((q = ao2_t_iterator_next(&qiter, "Iterate through queues"))) {
ao2_lock(q);
miter = ao2_iterator_init(q->members, 0);
for (; (m = ao2_iterator_next(&miter)); ao2_ref(m, -1)) {
if (!strcmp(m->state_context, context) && !strcmp(m->state_exten, exten)) {
update_status(q, m, device_state);
ao2_ref(m, -1);
found = 1;
break;
}
}
ao2_iterator_destroy(&miter);
ao2_unlock(q);
Tilghman Lesher
committed
queue_t_unref(q, "Done with iterator");
}
ao2_iterator_destroy(&qiter);
if (found) {
ast_debug(1, "Extension '%s@%s' changed to state '%d' (%s)\n", exten, context, device_state, ast_devstate2str(device_state));
} else {
ast_debug(3, "Extension '%s@%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n",
exten, context, device_state, ast_devstate2str(device_state));
}
return 0;
}
/*! \brief Return the current state of a member */
static int get_queue_member_status(struct member *cur)
{
return ast_strlen_zero(cur->state_exten) ? ast_device_state(cur->state_interface) : extensionstate2devicestate(ast_extension_state(NULL, cur->state_context, cur->state_exten));
}
/*! \brief allocate space for new queue member and set fields based on parameters passed */
static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused, const char *state_interface, int ringinuse)
{
struct member *cur;
if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
cur->ringinuse = ringinuse;
cur->penalty = penalty;
cur->paused = paused;
ast_copy_string(cur->interface, interface, sizeof(cur->interface));
if (!ast_strlen_zero(state_interface)) {
Mark Michelson
committed
ast_copy_string(cur->state_interface, state_interface, sizeof(cur->state_interface));
Mark Michelson
committed
ast_copy_string(cur->state_interface, interface, sizeof(cur->state_interface));
}
if (!ast_strlen_zero(membername)) {
ast_copy_string(cur->membername, membername, sizeof(cur->membername));
ast_copy_string(cur->membername, interface, sizeof(cur->membername));
}
if (!strchr(cur->interface, '/')) {
ast_log(LOG_WARNING, "No location at interface '%s'\n", interface);
if (!strncmp(cur->state_interface, "hint:", 5)) {
char *tmp = ast_strdupa(cur->state_interface), *context = tmp;
char *exten = strsep(&context, "@") + 5;
ast_copy_string(cur->state_exten, exten, sizeof(cur->state_exten));
ast_copy_string(cur->state_context, S_OR(context, "default"), sizeof(cur->state_context));
}
cur->status = get_queue_member_status(cur);
}
return cur;
}
static int compress_char(const char c)
{
}
static int member_hash_fn(const void *obj, const int flags)
{
const struct member *mem = obj;
const char *interface = (flags & OBJ_KEY) ? obj : mem->interface;
const char *chname = strchr(interface, '/');
if (!chname) {
chname = interface;
}
for (i = 0; i < 5 && chname[i]; i++) {
ret += compress_char(chname[i]) << (i * 6);
static int member_cmp_fn(void *obj1, void *obj2, int flags)
struct member *mem1 = obj1;
struct member *mem2 = obj2;
const char *interface = (flags & OBJ_KEY) ? obj2 : mem2->interface;
return strcasecmp(mem1->interface, interface) ? 0 : CMP_MATCH | CMP_STOP;
Mark Michelson
committed
* \brief Initialize Queue default values.
* \note the queue's lock must be held before executing this function
*/
static void init_queue(struct call_queue *q)
{
Mark Michelson
committed
struct penalty_rule *pr_iter;
q->dead = 0;
q->retry = DEFAULT_RETRY;
q->timeout = DEFAULT_TIMEOUT;
q->maxlen = 0;
q->announcefrequency = 0;
Russell Bryant
committed
q->minannouncefrequency = DEFAULT_MIN_ANNOUNCE_FREQUENCY;
q->announceholdtime = 1;
q->announcepositionlimit = 10; /* Default 10 positions */
q->announceposition = ANNOUNCEPOSITION_YES; /* Default yes */
q->roundingseconds = 0; /* Default - don't announce seconds */
q->servicelevel = 0;
Kevin P. Fleming
committed
q->ringinuse = 1;
q->announce_to_first_user = 0;
q->setinterfacevar = 0;
q->setqueuevar = 0;
q->setqueueentryvar = 0;
q->autofill = autofill_default;
q->montype = montype_default;
q->monfmt[0] = '\0';
q->reportholdtime = 0;
q->wrapuptime = 0;
q->joinempty = 0;
q->leavewhenempty = 0;
q->memberdelay = 0;
q->weight = 0;
q->timeoutrestart = 0;
q->periodicannouncefrequency = 0;
q->randomperiodicannounce = 0;
q->numperiodicannounce = 0;
Mark Michelson
committed
q->autopause = QUEUE_AUTOPAUSE_OFF;
q->timeoutpriority = TIMEOUT_PRIORITY_APP;
Mark Michelson
committed
if (!q->members) {
if (q->strategy == QUEUE_STRATEGY_LINEAR || q->strategy == QUEUE_STRATEGY_RRORDERED) {
Mark Michelson
committed
/* linear strategy depends on order, so we have to place all members in a single bucket */
q->members = ao2_container_alloc(1, member_hash_fn, member_cmp_fn);
Mark Michelson
committed
q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
Mark Michelson
committed
}
ast_string_field_set(q, sound_next, "queue-youarenext");
ast_string_field_set(q, sound_thereare, "queue-thereare");
ast_string_field_set(q, sound_calls, "queue-callswaiting");
ast_string_field_set(q, queue_quantity1, "queue-quantity1");
ast_string_field_set(q, queue_quantity2, "queue-quantity2");
ast_string_field_set(q, sound_holdtime, "queue-holdtime");
ast_string_field_set(q, sound_minutes, "queue-minutes");
Mark Michelson
committed
ast_string_field_set(q, sound_minute, "queue-minute");
ast_string_field_set(q, sound_seconds, "queue-seconds");
ast_string_field_set(q, sound_thanks, "queue-thankyou");
ast_string_field_set(q, sound_reporthold, "queue-reporthold");
if (!q->sound_periodicannounce[0]) {
q->sound_periodicannounce[0] = ast_str_create(32);
}
if (q->sound_periodicannounce[0]) {
ast_str_set(&q->sound_periodicannounce[0], 0, "queue-periodic-announce");
for (i = 1; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
ast_str_set(&q->sound_periodicannounce[i], 0, "%s", "");
}
Mark Michelson
committed
while ((pr_iter = AST_LIST_REMOVE_HEAD(&q->rules,list))) {
Mark Michelson
committed
ast_free(pr_iter);
/* On restart assume no members are available.
* The queue_avail hint is a boolean state to indicate whether a member is available or not.
*
* This seems counter intuitive, but is required to light a BLF
* AST_DEVICE_INUSE indicates no members are available.
* AST_DEVICE_NOT_INUSE indicates a member is available.
*/
ast_devstate_changed(AST_DEVICE_INUSE, AST_DEVSTATE_CACHABLE, "Queue:%s_avail", q->name);
}
static void clear_queue(struct call_queue *q)
{
q->holdtime = 0;
q->callscompleted = 0;
q->callsabandoned = 0;
q->callscompletedinsl = 0;
q->talktime = 0;
if (q->members) {
struct member *mem;
struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0);
while ((mem = ao2_iterator_next(&mem_iter))) {
mem->calls = 0;
mem->lastcall = 0;
ao2_ref(mem, -1);
}
ao2_iterator_destroy(&mem_iter);
}
Mark Michelson
committed
* \brief Change queue penalty by adding rule.
*
* Check rule for errors with time or fomatting, see if rule is relative to rest
Mark Michelson
committed
* of queue, iterate list of rules to find correct insertion point, insert and return.
* \retval -1 on failure
* \retval 0 on success
* \note Call this with the rule_lists locked
Mark Michelson
committed
*/
static int insert_penaltychange(const char *list_name, const char *content, const int linenum)
Mark Michelson
committed
{
char *timestr, *maxstr, *minstr, *contentdup;
struct penalty_rule *rule = NULL, *rule_iter;
struct rule_list *rl_iter;
int penaltychangetime, inserted = 0;
Mark Michelson
committed
if (!(rule = ast_calloc(1, sizeof(*rule)))) {
return -1;
}
contentdup = ast_strdupa(content);
Mark Michelson
committed
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 ((penaltychangetime = atoi(timestr)) < 0) {
Mark Michelson
committed
ast_log(LOG_WARNING, "Improper time parameter specified for penaltychange rule at line %d. Ignoring.\n", linenum);
ast_free(rule);
return -1;
}
rule->time = penaltychangetime;
Mark Michelson
committed
Mark Michelson
committed
*minstr++ = '\0';
Mark Michelson
committed
/* 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)) {
Mark Michelson
committed
rule->min_relative = 1;
Mark Michelson
committed
rule->min_value = atoi(minstr);
} else { /*there was no minimum specified, so assume this means no change*/
Mark Michelson
committed
rule->min_relative = 1;
Mark Michelson
committed
/*We have the rule made, now we need to insert it where it belongs*/
AST_LIST_TRAVERSE(&rule_lists, rl_iter, list){
Mark Michelson
committed
continue;
Mark Michelson
committed
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;
Mark Michelson
committed
if (!inserted) {
AST_LIST_INSERT_TAIL(&rl_iter->rules, rule, list);
Mark Michelson
committed
}
Mark Michelson
committed
}
if (!inserted) {
ast_log(LOG_WARNING, "Unknown rule list name %s; ignoring.\n", list_name);
ast_free(rule);
return -1;
}
Mark Michelson
committed
return 0;
}
static void parse_empty_options(const char *value, enum empty_conditions *empty, int joinempty)
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
{
char *value_copy = ast_strdupa(value);
char *option = NULL;
while ((option = strsep(&value_copy, ","))) {
if (!strcasecmp(option, "paused")) {
*empty |= QUEUE_EMPTY_PAUSED;
} else if (!strcasecmp(option, "penalty")) {
*empty |= QUEUE_EMPTY_PENALTY;
} else if (!strcasecmp(option, "inuse")) {
*empty |= QUEUE_EMPTY_INUSE;
} else if (!strcasecmp(option, "ringing")) {
*empty |= QUEUE_EMPTY_RINGING;
} else if (!strcasecmp(option, "invalid")) {
*empty |= QUEUE_EMPTY_INVALID;
} else if (!strcasecmp(option, "wrapup")) {
*empty |= QUEUE_EMPTY_WRAPUP;
} else if (!strcasecmp(option, "unavailable")) {
*empty |= QUEUE_EMPTY_UNAVAILABLE;
} else if (!strcasecmp(option, "unknown")) {
*empty |= QUEUE_EMPTY_UNKNOWN;
} else if (!strcasecmp(option, "loose")) {
*empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID);
} else if (!strcasecmp(option, "strict")) {
*empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID | QUEUE_EMPTY_PAUSED | QUEUE_EMPTY_UNAVAILABLE);
} else if ((ast_false(option) && joinempty) || (ast_true(option) && !joinempty)) {
*empty = (QUEUE_EMPTY_PENALTY | QUEUE_EMPTY_INVALID | QUEUE_EMPTY_PAUSED);
} else if ((ast_false(option) && !joinempty) || (ast_true(option) && joinempty)) {
} else {
ast_log(LOG_WARNING, "Unknown option %s for '%s'\n", option, joinempty ? "joinempty" : "leavewhenempty");
}
}
}
Mark Michelson
committed
* 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.
* \note For error reporting, line number is passed for .conf static configuration,
* for Realtime queues, linenum is -1.
*/
static void queue_set_param(struct call_queue *q, const char *param, const char *val, int linenum, int failunknown)
{
if (!strcasecmp(param, "musicclass") ||
Kevin P. Fleming
committed
!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);
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-quantity1")) {
ast_string_field_set(q, queue_quantity1, val);
} else if (!strcasecmp(param, "queue-quantity2")) {
ast_string_field_set(q, queue_quantity2, 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);
} else if (!strcasecmp(param, "announce-to-first-user")) {
q->announce_to_first_user = ast_true(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")) {
q->announceholdtime = ANNOUNCEHOLDTIME_ONCE;
q->announceholdtime = ANNOUNCEHOLDTIME_ALWAYS;
q->announceholdtime = 0;
} else if (!strcasecmp(param, "announce-position")) {
q->announceposition = ANNOUNCEPOSITION_LIMIT;
q->announceposition = ANNOUNCEPOSITION_MORE_THAN;
q->announceposition = ANNOUNCEPOSITION_YES;
q->announceposition = ANNOUNCEPOSITION_NO;
} else if (!strcasecmp(param, "announce-position-limit")) {
q->announcepositionlimit = atoi(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, ",|"))) {
q->sound_periodicannounce[i] = ast_str_create(16);
ast_str_set(&q->sound_periodicannounce[i], 0, "%s", s);
q->numperiodicannounce = i;
ast_str_set(&q->sound_periodicannounce[0], 0, "%s", val);
q->numperiodicannounce = 1;
} else if (!strcasecmp(param, "periodic-announce-frequency")) {
q->periodicannouncefrequency = atoi(val);
Matthew Nicholson
committed
} else if (!strcasecmp(param, "relative-periodic-announce")) {
q->relativeperiodicannounce = ast_true(val);
} else if (!strcasecmp(param, "random-periodic-announce")) {
q->randomperiodicannounce = ast_true(val);
} else if (!strcasecmp(param, "retry")) {
q->retry = atoi(val);
q->retry = DEFAULT_RETRY;
} else if (!strcasecmp(param, "wrapuptime")) {
q->wrapuptime = atoi(val);
if ((sscanf(val, "%10d", &q->penaltymemberslimit) != 1)) {
q->penaltymemberslimit = 0;
}
} else if (!strcasecmp(param, "autofill")) {
q->autofill = ast_true(val);
} else if (!strcasecmp(param, "monitor-type")) {
q->montype = 1;
} else if (!strcasecmp(param, "autopause")) {
Mark Michelson
committed
q->autopause = autopause2int(val);
} else if (!strcasecmp(param, "autopausedelay")) {
q->autopausedelay = atoi(val);
} else if (!strcasecmp(param, "autopausebusy")) {
q->autopausebusy = ast_true(val);
} else if (!strcasecmp(param, "autopauseunavail")) {
q->autopauseunavail = ast_true(val);
} else if (!strcasecmp(param, "maxlen")) {
q->maxlen = atoi(val);
q->maxlen = 0;
} else if (!strcasecmp(param, "servicelevel")) {
q->servicelevel= atoi(val);
} else if (!strcasecmp(param, "strategy")) {
Terry Wilson
committed
int strategy;
/* We are a static queue and already have set this, no need to do it again */
if (failunknown) {
return;
}
strategy = strat2int(val);
if (strategy < 0) {
ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
val, q->name);
q->strategy = QUEUE_STRATEGY_RINGALL;
}
if (strategy == q->strategy) {
return;
}
if (strategy == QUEUE_STRATEGY_LINEAR) {
ast_log(LOG_WARNING, "Changing to the linear strategy currently requires asterisk to be restarted.\n");
return;
}
q->strategy = strategy;
} else if (!strcasecmp(param, "joinempty")) {
parse_empty_options(val, &q->joinempty, 1);
} else if (!strcasecmp(param, "leavewhenempty")) {
parse_empty_options(val, &q->leavewhenempty, 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);
} 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);
} else if (!strcasecmp(param, "timeoutpriority")) {
if (!strcasecmp(val, "conf")) {
q->timeoutpriority = TIMEOUT_PRIORITY_CONF;
} else {
q->timeoutpriority = TIMEOUT_PRIORITY_APP;
}
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);
}
}
}
Jonathan Rose
committed
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
/*! \internal
* \brief If adding a single new member to a queue, use this function instead of ao2_linking.
* This adds round robin queue position data for a fresh member as well as links it.
* \param queue Which queue the member is being added to
* \param mem Which member is being added to the queue
*/
static void member_add_to_queue(struct call_queue *queue, struct member *mem)
{
ao2_lock(queue->members);
mem->queuepos = ao2_container_count(queue->members);
ao2_link(queue->members, mem);
ao2_unlock(queue->members);
}
/*! \internal
* \brief If removing a single member from a queue, use this function instead of ao2_unlinking.
* This will perform round robin queue position reordering for the remaining members.
* \param queue Which queue the member is being removed from
* \param member Which member is being removed from the queue
*/
static void member_remove_from_queue(struct call_queue *queue, struct member *mem)
{
ao2_lock(queue->members);
queue_member_follower_removal(queue, mem);
ao2_unlink(queue->members, mem);
ao2_unlock(queue->members);
}
Mark Michelson
committed
/*!
* \brief Find rt member record to update otherwise create one.
*
* Search for member in queue, if found update penalty/paused state,
* if no member exists create one flag it as a RT member and add to queue member list.
Mark Michelson
committed
*/
static void rt_handle_member_record(struct call_queue *q, char *interface, struct ast_config *member_config)
{
struct member *m;
struct ao2_iterator mem_iter;
int penalty = 0;
int found = 0;
int ringinuse = q->ringinuse;
const char *rt_uniqueid = ast_variable_retrieve(member_config, interface, "uniqueid");
const char *membername = S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface);
const char *state_interface = S_OR(ast_variable_retrieve(member_config, interface, "state_interface"), interface);
const char *penalty_str = ast_variable_retrieve(member_config, interface, "penalty");
const char *paused_str = ast_variable_retrieve(member_config, interface, "paused");
if (ast_strlen_zero(rt_uniqueid)) {
ast_log(LOG_WARNING, "Realtime field uniqueid is empty for member %s\n", S_OR(membername, "NULL"));
return;
}
penalty = atoi(penalty_str);
if ((penalty < 0) && negative_penalty_invalid) {
return;
} else if (penalty < 0) {
penalty = 0;
}
if (paused_str) {
paused = atoi(paused_str);
if ((config_val = ast_variable_retrieve(member_config, interface, realtime_ringinuse_field))) {
if (ast_true(config_val)) {
ringinuse = 1;
} else if (ast_false(config_val)) {
ringinuse = 0;
} else {
ast_log(LOG_WARNING, "Invalid value of '%s' field for %s in queue '%s'\n", realtime_ringinuse_field, interface, q->name);
}
}
/* Find member by realtime uniqueid and update */
mem_iter = ao2_iterator_init(q->members, 0);
while ((m = ao2_iterator_next(&mem_iter))) {
if (!strcasecmp(m->rt_uniqueid, rt_uniqueid)) {
m->dead = 0; /* Do not delete this one. */
ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid));
if (strcasecmp(state_interface, m->state_interface)) {
ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface));
}
m->penalty = penalty;
m->ringinuse = ringinuse;
found = 1;
ao2_ref(m, -1);
break;
}
ao2_ref(m, -1);
}
ao2_iterator_destroy(&mem_iter);
if ((m = create_queue_member(interface, membername, penalty, paused, state_interface, ringinuse))) {
m->dead = 0;
ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid));
Kinsey Moore
committed
if (!log_membername_as_agent) {
ast_queue_log(q->name, "REALTIME", m->interface, "ADDMEMBER", "%s", paused ? "PAUSED" : "");
Kinsey Moore
committed
} else {
ast_queue_log(q->name, "REALTIME", m->membername, "ADDMEMBER", "%s", paused ? "PAUSED" : "");
Kinsey Moore
committed
}
Jonathan Rose
committed
member_add_to_queue(q, m);
ao2_ref(m, -1);
m = NULL;
}
}
}
Mark Michelson
committed
/*! \brief Iterate through queue's member list and delete them */
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))) {
Jonathan Rose
committed
member_remove_from_queue(q, cur);
Russell Bryant
committed
}
ao2_iterator_destroy(&mem_iter);
Russell Bryant
committed
}
Mark Michelson
committed
/*! \brief Free queue's member list then its string fields */
Mark Michelson
committed
static void destroy_queue(void *obj)
Russell Bryant
committed
{
Mark Michelson
committed
struct call_queue *q = obj;
int i;
Russell Bryant
committed
free_members(q, 1);
ast_string_field_free_memory(q);
for (i = 0; i < MAX_PERIODIC_ANNOUNCEMENTS; i++) {
free(q->sound_periodicannounce[i]);
}
Mark Michelson
committed
}
static struct call_queue *alloc_queue(const char *queuename)
{
struct call_queue *q;
Tilghman Lesher
committed
if ((q = ao2_t_alloc(sizeof(*q), destroy_queue, "Allocate queue"))) {
if (ast_string_field_init(q, 64)) {
Tilghman Lesher
committed
queue_t_unref(q, "String field allocation failed");
return NULL;
}
ast_string_field_set(q, name, queuename);
Mark Michelson
committed
}
return q;
Russell Bryant
committed
}
Mark Michelson
committed
/*!
* \brief Reload a single queue via realtime.
*
* Check for statically defined queue first, check if deleted RT queue,
* check for new RT queue, if queue vars are not defined init them with defaults.
* reload RT queue vars, set RT queue members dead and reload them, return finished queue.
Mark Michelson
committed
* \retval NULL if it doesn't exist.
* \note Should be called with the "queues" container locked.
Mark Michelson
committed
*/
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 = {
};
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. */
Tilghman Lesher
committed
if ((q = ao2_t_find(queues, &tmpq, OBJ_POINTER, "Check if static queue exists"))) {
Mark Michelson
committed
ao2_lock(q);
if (!q->realtime) {
if (q->dead) {
Mark Michelson
committed
ao2_unlock(q);
Tilghman Lesher
committed
queue_t_unref(q, "Queue is dead; can't return it");
return NULL;
ast_log(LOG_WARNING, "Static queue '%s' already exists. Not loading from realtime\n", q->name);
ao2_unlock(q);
return q;
}
/* 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);