Index: apps/app_queue.c =================================================================== --- apps/app_queue.c (revision 114901) +++ apps/app_queue.c (working copy) @@ -340,6 +340,7 @@ time_t expire; /*!< When this entry should expire (time out of queue) */ struct ast_channel *chan; /*!< Our channel */ struct queue_ent *next; /*!< The next queue entry */ + AST_LIST_ENTRY(queue_ent) list; }; struct member { @@ -433,10 +434,21 @@ * in, so this can not simply be replaced with ao2_container_count(). */ int membercount; - struct queue_ent *head; /*!< Head of the list of callers */ + int group_found; /*!< How to tell if a group has been specified for a queue */ + int group_strategy_found; /*!< How to tell if a queue's group specifies the strategy to use */ + struct queue_group *group; /*!< To which group does this queue belong */ AST_LIST_ENTRY(call_queue) list; /*!< Next call queue */ }; +struct queue_group { + AST_LIST_HEAD(, queue_ent) callers; + AST_LIST_ENTRY(queue_group) list; + char name[80]; + int unused; /*!< If a group is removed from configuration, this is how we detect it */ +}; + +static AST_LIST_HEAD_STATIC(groups, queue_group); + static AST_LIST_HEAD_STATIC(queues, call_queue); static int set_member_paused(const char *queuename, const char *interface, int paused); @@ -496,26 +508,6 @@ return -1; } -/*! \brief Insert the 'new' entry after the 'prev' entry of queue 'q' */ -static inline void insert_entry(struct call_queue *q, struct queue_ent *prev, struct queue_ent *new, int *pos) -{ - struct queue_ent *cur; - - if (!q || !new) - return; - if (prev) { - cur = prev->next; - prev->next = new; - } else { - cur = q->head; - q->head = new; - } - new->next = cur; - new->parent = q; - new->pos = ++(*pos); - new->opos = *pos; -} - enum queue_member_status { QUEUE_NO_MEMBERS, QUEUE_NO_REACHABLE_MEMBERS, @@ -802,6 +794,8 @@ { int i; + q->group_found = 0; + q->group_strategy_found = 0; q->dead = 0; q->retry = DEFAULT_RETRY; q->timeout = -1; @@ -941,6 +935,124 @@ AST_LIST_UNLOCK(&interfaces); } +static void parse_member(struct call_queue *q, char *data) +{ + char parse[80]; + char *interface; + char *membername = NULL; + int penalty; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(interface); + AST_APP_ARG(penalty); + AST_APP_ARG(membername); + ); + struct member *newm = NULL, *cur = NULL; + struct member tmpmem; + char *tmp; + + /* Add a new member */ + ast_copy_string(parse, data, sizeof(parse)); + + AST_NONSTANDARD_APP_ARGS(args, parse, ','); + + interface = args.interface; + if (!ast_strlen_zero(args.penalty)) { + tmp = args.penalty; + while (*tmp && *tmp < 33) tmp++; + penalty = atoi(tmp); + if (penalty < 0) { + penalty = 0; + } + } else + penalty = 0; + + if (!ast_strlen_zero(args.membername)) { + membername = args.membername; + while (*membername && *membername < 33) membername++; + } else + membername = interface; + + /* Find the old position in the list */ + ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); + cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK); + + newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0); + ao2_link(q->members, newm); + ao2_ref(newm, -1); + newm = NULL; + + if (cur) + ao2_ref(cur, -1); + else { + /* Add them to the master int list if necessary */ + add_to_interfaces(interface); + q->membercount++; + } +} +static void set_strategy(struct call_queue *q, const char *strategy) +{ + q->strategy = strat2int(strategy); + if (q->strategy < 0) { + ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n", + strategy, q->name); + q->strategy = QUEUE_STRATEGY_RINGALL; + } +} +static void parse_queuegroup(struct call_queue *q, const char *groupname) +{ + struct ast_config *groupcfg = NULL; + struct ast_variable *var = NULL, *realtimevar = NULL; + struct queue_group *group = NULL; + + /* As with other queue-related configurations, static trumps realtime. + * In other words, if we find the specified group in the config file, then + * we use it. We only look in realtime if we can't find the group in the config file + */ + if (!(groupcfg = ast_config_load("queuegroups.conf"))) { + if (!(realtimevar = ast_load_realtime("queue_groups", "name", groupname, NULL))) { + ast_log(LOG_WARNING, "Group %s was specified for queue %s but could not find definition. Ignoring!\n", groupname, q->name); + return; + } + } + + for (var = realtimevar ? realtimevar : ast_variable_browse(groupcfg, groupname); var; var = var->next) { + if (!strcasecmp(var->name, "member")) { + parse_member(q, var->value); + } else if (!strcasecmp(var->name, "strategy")) { + set_strategy(q, var->value); + q->group_strategy_found = 1; + } else { + ast_log(LOG_WARNING, "Invalid queuegroup option '%s' for queuegroup '%s'\n", var->name, groupname); + } + } + + if (groupcfg) { + ast_config_destroy(groupcfg); + } else { + ast_variables_destroy(realtimevar); + } + + AST_LIST_TRAVERSE(&groups, group, list) { + if (!strcasecmp(group->name, groupname)) { + group->unused = 0; + q->group = group; + q->group_found = 1; + return; + } + } + + /* This is a new group */ + if (!(group = ast_calloc(1, sizeof (*group)))) { + /* OH SHIT */ + } + ast_copy_string(group->name, groupname, sizeof(group->name)); + AST_LIST_HEAD_INIT(&group->callers); + group->unused = 0; + q->group = group; + q->group_found = 1; + AST_LIST_INSERT_HEAD(&groups, group, list); +} + /*! \brief Configure a queue parameter. \par For error reporting, line number is passed for .conf static configuration. @@ -1046,11 +1158,10 @@ } else if (!strcasecmp(param, "servicelevel")) { q->servicelevel= atoi(val); } else if (!strcasecmp(param, "strategy")) { - q->strategy = strat2int(val); - if (q->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 (!q->group_strategy_found){ + set_strategy(q, val); + } else { + ast_log(LOG_WARNING, "Ignoring strategy parameter for queue %s since its group specifies a strategy already\n", q->name); } } else if (!strcasecmp(param, "joinempty")) { if (!strcasecmp(val, "strict")) @@ -1086,6 +1197,12 @@ we will not see any effect on use_weight until next reload. */ } else if (!strcasecmp(param, "timeoutrestart")) { q->timeoutrestart = ast_true(val); + } else if (!strcasecmp(param, "group")) { + if (!q->group_found) { + parse_queuegroup(q, val); + } else { + ast_log(LOG_WARNING, "Ignoring group setting %s for queue %s since a group was already specified\n", val, q->name); + } } else if (failunknown) { if (linenum >= 0) { ast_log(LOG_WARNING, "Unknown keyword in queue '%s': %s at line %d of queues.conf\n", @@ -1406,7 +1523,7 @@ static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason) { struct call_queue *q; - struct queue_ent *cur, *prev = NULL; + struct queue_ent *cur; int res = -1; int pos = 0; int inserted = 0; @@ -1431,23 +1548,33 @@ * the queue. * Take into account the priority of the calling user */ inserted = 0; - prev = NULL; - cur = q->head; - while (cur) { + AST_LIST_LOCK(&q->group->callers); + AST_LIST_TRAVERSE_SAFE_BEGIN(&q->group->callers, cur, list) { /* 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); + AST_LIST_INSERT_BEFORE_CURRENT(&q->group->callers, qe, list); + if (option_debug > 2) + ast_log(LOG_DEBUG, "Inserted %s into group %s, address %p\n", qe->chan->name, q->group->name, q->group); + qe->parent = q; + qe->pos = ++pos; + qe->opos = pos; inserted = 1; } cur->pos = ++pos; - prev = cur; - cur = cur->next; } + AST_LIST_TRAVERSE_SAFE_END; /* No luck, join at the end of the queue */ - if (!inserted) - insert_entry(q, prev, qe, &pos); + if (!inserted) { + AST_LIST_INSERT_TAIL(&q->group->callers, qe, list); + if (option_debug > 2) + ast_log(LOG_DEBUG, "Inserted %s into group %s, address %p\n", qe->chan->name, q->group->name, q->group); + qe->parent = q; + qe->pos = ++pos; + qe->opos = pos; + } + AST_LIST_UNLOCK(&q->group->callers); 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)); @@ -1649,10 +1776,10 @@ ast_mutex_lock(&q->lock); prev = NULL; - for (cur = q->head; cur; cur = cur->next) { + AST_LIST_LOCK(&q->group->callers); + AST_LIST_TRAVERSE_SAFE_BEGIN(&q->group->callers, cur, list) { 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", @@ -1660,16 +1787,14 @@ if (option_debug) ast_log(LOG_DEBUG, "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; + AST_LIST_REMOVE_CURRENT(&q->group->callers, list); } else { /* Renumber the people after us in the queue based on a new count */ cur->pos = ++pos; - prev = cur; } } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_UNLOCK(&q->group->callers); ast_mutex_unlock(&q->lock); if (q->dead && !q->count) { @@ -2335,7 +2460,7 @@ if (!qe->parent->autofill) { /* Atomically read the parent head -- does not need a lock */ - ch = qe->parent->head; + ch = AST_LIST_FIRST(&qe->parent->group->callers); /* If we are now at the top of the head, break out */ if (ch == qe) { if (option_debug) @@ -2351,8 +2476,6 @@ /* This needs a lock. How many members are available to be served? */ ast_mutex_lock(&qe->parent->lock); - ch = qe->parent->head; - if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) { if (option_debug) ast_log(LOG_DEBUG, "Even though there may be multiple members available, the strategy is ringall so only the head call is allowed in\n"); @@ -2378,11 +2501,16 @@ if (option_debug) ast_log(LOG_DEBUG, "There are %d available members.\n", avl); - while ((idx < avl) && (ch) && (ch != qe)) { - if (!ch->pending) + AST_LIST_LOCK(&qe->parent->group->callers); + AST_LIST_TRAVERSE(&qe->parent->group->callers, ch, list) { + if (idx >= avl || ch == qe) { + break; + } + if (!ch->pending) { idx++; - ch = ch->next; + } } + AST_LIST_UNLOCK(&qe->parent->group->callers); /* If the queue entry is within avl [the number of available members] calls from the top ... */ if (ch && idx < avl) { @@ -4110,30 +4238,26 @@ .read = queue_function_queuememberlist, }; + + static int reload_queues(void) { struct call_queue *q; struct ast_config *cfg; - char *cat, *tmp; + struct member *cur; + char *cat; struct ast_variable *var; - struct member *cur, *newm; struct ao2_iterator mem_iter; + struct queue_group *group = NULL; int new; const char *general_val = NULL; - char parse[80]; - char *interface; - char *membername = NULL; - int penalty; - AST_DECLARE_APP_ARGS(args, - AST_APP_ARG(interface); - AST_APP_ARG(penalty); - AST_APP_ARG(membername); - ); + if (!(cfg = ast_config_load("queues.conf"))) { ast_log(LOG_NOTICE, "No call queueing config file (queues.conf), so no call queues\n"); return 0; } + AST_LIST_LOCK(&queues); use_weight=0; /* Mark all non-realtime queues as dead for the moment */ @@ -4143,6 +4267,10 @@ q->found = 0; } } + /* And mark the queue groups as unused */ + AST_LIST_TRAVERSE(&groups, group, list) { + group->unused = 1; + } /* Chug through config file */ cat = NULL; @@ -4195,51 +4323,28 @@ } for (var = ast_variable_browse(cfg, cat); var; var = var->next) { if (!strcasecmp(var->name, "member")) { - struct member tmpmem; - membername = NULL; - - /* Add a new member */ - ast_copy_string(parse, var->value, sizeof(parse)); - - AST_NONSTANDARD_APP_ARGS(args, parse, ','); - - interface = args.interface; - if (!ast_strlen_zero(args.penalty)) { - tmp = args.penalty; - while (*tmp && *tmp < 33) tmp++; - penalty = atoi(tmp); - if (penalty < 0) { - penalty = 0; - } - } else - penalty = 0; - - if (!ast_strlen_zero(args.membername)) { - membername = args.membername; - while (*membername && *membername < 33) membername++; - } - - /* Find the old position in the list */ - ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); - cur = ao2_find(q->members, &tmpmem, OBJ_POINTER | OBJ_UNLINK); - - newm = create_queue_member(interface, membername, penalty, cur ? cur->paused : 0); - ao2_link(q->members, newm); - ao2_ref(newm, -1); - newm = NULL; - - if (cur) - ao2_ref(cur, -1); - else { - /* Add them to the master int list if necessary */ - add_to_interfaces(interface); - q->membercount++; - } + parse_member(q, var->value); } else { queue_set_param(q, var->name, var->value, var->lineno, 1); } } + if (!q->group_found) { + /* No group was specified for this queue, so we need to + * figure out if the queue used to be part of a group and if + * so, was it a named one. + */ + if (!q->group || !ast_strlen_zero(q->group->name)) { + /* Either this is a new queue or the queue was in a group + * but now is not + */ + group = ast_calloc(1, sizeof(*group)); + *(group->name) = '\0'; + AST_LIST_HEAD_INIT(&group->callers); + q->group = group; + } + } + /* Free remaining members marked as delme */ mem_iter = ao2_iterator_init(q->members, 0); while ((cur = ao2_iterator_next(&mem_iter))) { @@ -4286,6 +4391,13 @@ } AST_LIST_TRAVERSE_SAFE_END; AST_LIST_UNLOCK(&queues); + AST_LIST_TRAVERSE_SAFE_BEGIN(&groups, group, list) { + if (group->unused) { + AST_LIST_REMOVE_CURRENT(&groups, list); + free(group); + } + } + AST_LIST_TRAVERSE_SAFE_END; return 1; } @@ -4394,13 +4506,14 @@ astman_append(s, " No Members%s", term); else ast_cli(fd, " No Members%s", term); - if (q->head) { + AST_LIST_LOCK(&q->group->callers); + if (!AST_LIST_EMPTY(&q->group->callers)) { pos = 1; if (s) astman_append(s, " Callers: %s", term); else ast_cli(fd, " Callers: %s", term); - for (qe = q->head; qe; qe = qe->next) { + AST_LIST_TRAVERSE(&q->group->callers, qe, list) { if (s) astman_append(s, " %d. %s (wait: %ld:%2.2ld, prio: %d)%s", pos++, qe->chan->name, (long) (now - qe->start) / 60, @@ -4414,6 +4527,7 @@ astman_append(s, " No Callers%s", term); else ast_cli(fd, " No Callers%s", term); + AST_LIST_UNLOCK(&q->group->callers); if (s) astman_append(s, "%s", term); else @@ -4534,7 +4648,8 @@ } /* List Queue Entries */ pos = 1; - for (qe = q->head; qe; qe = qe->next) { + AST_LIST_LOCK(&q->group->callers); + AST_LIST_TRAVERSE(&q->group->callers, qe, list) { astman_append(s, "Event: QueueEntry\r\n" "Queue: %s\r\n" "Position: %d\r\n" @@ -4549,6 +4664,7 @@ S_OR(qe->chan->cid.cid_name, "unknown"), (long) (now - qe->start), idText); } + AST_LIST_UNLOCK(&q->group->callers); } ast_mutex_unlock(&q->lock); } @@ -4829,6 +4945,140 @@ return NULL; } +static int handle_queue_group_show(int fd, int argc, char *argv[]) +{ + char *groupname = NULL; + struct queue_group *group_iter; + struct call_queue *queue_iter; + struct queue_ent *qe; + int pos; + time_t now; + char *max; + char max_buf[80]; + size_t max_left; + float sl = 0; + + time(&now); + + if (argc > 3) + groupname = argv[3]; + + AST_LIST_TRAVERSE(&groups, group_iter, list) { + if (ast_strlen_zero(groupname) || !strcasecmp(group_iter->name, groupname)) { + ast_cli(fd, "Groupname: %s\n", group_iter->name); + ast_cli(fd, "Queues:\n"); + AST_LIST_TRAVERSE(&queues, queue_iter, list) { + max_buf[0] = '\0'; + max = max_buf; + max_left = sizeof(max_buf); + sl = 0; + if (queue_iter->callscompleted > 0) + sl = 100 * ((float) queue_iter->callscompletedinsl / (float) queue_iter->callscompleted); + if (queue_iter->maxlen) + ast_build_string(&max, &max_left, "%d", queue_iter->maxlen); + else + ast_build_string(&max, &max_left, "unlimited"); + if (!strcasecmp(queue_iter->group->name, group_iter->name)) { + ast_cli(fd, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, S:%2.1f within %ds\n", + queue_iter->name, queue_iter->count, max_buf, int2strat(queue_iter->strategy), queue_iter->holdtime, queue_iter->weight, queue_iter->callscompleted, queue_iter->callsabandoned,sl,queue_iter->servicelevel); + } + } + AST_LIST_LOCK(&group_iter->callers); + if (!AST_LIST_EMPTY(&group_iter->callers)) { + pos = 1; + ast_cli(fd, " Callers: \n"); + AST_LIST_TRAVERSE(&group_iter->callers, qe, list) { + ast_cli(fd, " %d. %s (wait: %ld:%2.2ld, prio: %d)\n", pos++, + qe->chan->name, (long) (now - qe->start) / 60, + (long) (now - qe->start) % 60, qe->prio); + } + } else + ast_cli(fd, " No Callers\n"); + AST_LIST_UNLOCK(&group_iter->callers); + } + } + + return RESULT_SUCCESS; +} + +static char *complete_queue_group_show(const char *line, const char *word, int pos, int state) +{ + struct queue_group *group; + char *ret = NULL; + int which = 0; + int wordlen = strlen(word); + + if (pos != 3) + return NULL; + + AST_LIST_LOCK(&groups); + AST_LIST_TRAVERSE(&groups, group, list) { + if (!strncasecmp(word, group->name, wordlen) && ++which > state) { + ret = ast_strdup(group->name); + break; + } + } + AST_LIST_UNLOCK(&groups); + + return ret; +} + + +static int manager_queue_group_show(struct mansession *s, const struct message *m) +{ + const char *groupname = astman_get_header(m, "Group"); + struct queue_group *group_iter; + struct call_queue *queue_iter; + struct queue_ent *qe; + int pos; + time_t now; + char *max; + char max_buf[80]; + size_t max_left; + float sl = 0; + + time(&now); + + AST_LIST_TRAVERSE(&groups, group_iter, list) { + if (ast_strlen_zero(groupname) || !strcasecmp(group_iter->name, groupname)) { + astman_append(s, "Groupname: %s\r\n", group_iter->name); + astman_append(s, "Queues:\r\n"); + AST_LIST_TRAVERSE(&queues, queue_iter, list) { + max_buf[0] = '\0'; + max = max_buf; + max_left = sizeof(max_buf); + sl = 0; + if (queue_iter->callscompleted > 0) + sl = 100 * ((float) queue_iter->callscompletedinsl / (float) queue_iter->callscompleted); + if (queue_iter->maxlen) + ast_build_string(&max, &max_left, "%d", queue_iter->maxlen); + else + ast_build_string(&max, &max_left, "unlimited"); + + if (!strcasecmp(queue_iter->group->name, group_iter->name)) { + astman_append(s, "%-12.12s has %d calls (max %s) in '%s' strategy (%ds holdtime), W:%d, C:%d, A:%d, SL:%2.1f%% within %ds\r\n", + queue_iter->name, queue_iter->count, max_buf, int2strat(queue_iter->strategy), queue_iter->holdtime, queue_iter->weight, + queue_iter->callscompleted, queue_iter->callsabandoned,sl,queue_iter->servicelevel); + } + } + AST_LIST_LOCK(&group_iter->callers); + if (!AST_LIST_EMPTY(&group_iter->callers)) { + pos = 1; + astman_append(s, " Callers: \n"); + AST_LIST_TRAVERSE(&group_iter->callers, qe, list) { + astman_append(s, " %d. %s (wait: %ld:%2.2ld, prio: %d)\r\n", pos++, + qe->chan->name, (long) (now - qe->start) / 60, + (long) (now - qe->start) % 60, qe->prio); + } + } else + astman_append(s, " No Callers\n"); + AST_LIST_UNLOCK(&group_iter->callers); + } + } + + return 0; +} + static char queue_show_usage[] = "Usage: queue show\n" " Provides summary information on a specified queue.\n"; @@ -4839,6 +5089,11 @@ static char qrm_cmd_usage[] = "Usage: queue remove member from \n"; +static char qgs_cmd_usage[] = +"Usage: queue group show [groupname]\n" +" Shows information about group groupname. If no groupname is\n" +" specified, then all groups are shown\n"; + static struct ast_cli_entry cli_show_queue_deprecated = { { "show", "queue", NULL }, queue_show, NULL, @@ -4871,6 +5126,10 @@ { { "queue", "remove", "member", NULL }, handle_queue_remove_member, "Removes a channel from a specified queue", qrm_cmd_usage, complete_queue_remove_member, &cli_remove_queue_member_deprecated }, + + { { "queue", "group", "show", NULL }, + handle_queue_group_show, "Show status of specified queue group", + qgs_cmd_usage, complete_queue_group_show }, }; static int unload_module(void) @@ -4892,6 +5151,7 @@ res |= ast_manager_unregister("QueueAdd"); res |= ast_manager_unregister("QueueRemove"); res |= ast_manager_unregister("QueuePause"); + res |= ast_manager_unregister("QueueGroupShow"); res |= ast_unregister_application(app_aqm); res |= ast_unregister_application(app_rqm); res |= ast_unregister_application(app_pqm); @@ -4937,6 +5197,7 @@ res |= ast_manager_register("QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member, "Add interface to queue."); res |= ast_manager_register("QueueRemove", EVENT_FLAG_AGENT, manager_remove_queue_member, "Remove interface from queue."); res |= ast_manager_register("QueuePause", EVENT_FLAG_AGENT, manager_pause_queue_member, "Makes a queue member temporarily unavailable"); + res |= ast_manager_register("QueueGroupShow", 0, manager_queue_group_show, "Queue group status"); res |= ast_custom_function_register(&queueagentcount_function); res |= ast_custom_function_register(&queuemembercount_function); res |= ast_custom_function_register(&queuememberlist_function); Index: configs/queues.conf.sample =================================================================== --- configs/queues.conf.sample (revision 114901) +++ configs/queues.conf.sample (working copy) @@ -278,6 +278,12 @@ ; ; timeoutrestart = no ; +; If a group is specified for a queue, then that group will be searched for in +; queuegroups.conf. If a group is not found there, then it will be searched for in +; realtime. For more information regarding queue groups, see queuegroups.conf.sample +; +; group = group1 +; ; Each member of this call queue is listed on a separate line in ; the form technology/dialstring. "member" means a normal member of a ; queue. An optional penalty may be specified after a comma, such that