Index: apps/app_queue.c =================================================================== --- apps/app_queue.c (revision 124241) +++ apps/app_queue.c (working copy) @@ -510,6 +510,8 @@ static struct ao2_container *queues; +static void copy_rules(struct queue_ent *qe, const char *rulename); +static void update_qe_rule(struct queue_ent *qe); static void update_realtime_members(struct call_queue *q); static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused); @@ -1674,7 +1676,7 @@ ast_config_destroy(member_config); } -static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason) +static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason, const char *overriding_rule) { struct call_queue *q; struct queue_ent *cur, *prev = NULL; @@ -1682,6 +1684,7 @@ int pos = 0; int inserted = 0; enum queue_member_status stat; + int exit = 0; if (!(q = load_realtime_queue(queuename))) return res; @@ -1689,50 +1692,63 @@ ao2_lock(queues); ao2_lock(q); + copy_rules(qe, S_OR(overriding_rule, q->defaultrule)); + qe->pr = AST_LIST_FIRST(&qe->qe_rules); + /* This is our one */ - 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 - * the queue. - * Take into account the priority of the calling user */ - inserted = 0; - prev = NULL; - cur = q->head; - while (cur) { - /* 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)) { + while (!exit) { + 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 + * the queue. + * Take into account the priority of the calling user */ + inserted = 0; + prev = NULL; + cur = q->head; + while (cur) { + /* 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); - inserted = 1; - } - cur->pos = ++pos; - prev = cur; - cur = cur->next; + 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 */ + 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 ); } - /* 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 */ - 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 ); + if (!exit && qe->pr) { + /* We failed to join the queue, but perhaps we can join if we move + * to the next defined penalty rule + */ + update_qe_rule(qe); + } else if (!qe->pr) { + exit = 1; + } } ao2_unlock(q); ao2_unlock(queues); @@ -2799,7 +2815,8 @@ /* This is the holding pen for callers 2 through maxlen */ for (;;) { - enum queue_member_status stat; + enum queue_member_status stat = QUEUE_NORMAL; + int exit = 0; if (is_our_turn(qe)) break; @@ -2810,16 +2827,31 @@ break; } - stat = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty); + /* If we are going to exit due to a leavewhenempty condition, we should + * actually attempt to keep the caller in the queue until we have + * exhausted all penalty rules. + */ + for (; !exit || qe->pr; update_qe_rule(qe)) { + + stat = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty); + + /* leave the queue if no agents, if enabled */ + if ((qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) || + ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) || + ((qe->parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_MEMBERS))) { + continue; + } else { + exit = 1; + } + } - /* leave the queue if no agents, if enabled */ if (qe->parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) { *reason = QUEUE_LEAVEEMPTY; ast_queue_log(qe->parent->name, qe->chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) time(NULL) - qe->start); leave_queue(qe); break; } - + /* leave the queue if no reachable agents, if enabled */ if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) { *reason = QUEUE_LEAVEUNAVAIL; @@ -4315,7 +4347,10 @@ { struct penalty_rule *pr_iter; struct rule_list *rl_iter; - const char *tmp = ast_strlen_zero(rulename) ? qe->parent->defaultrule : rulename; + const char *tmp = rulename; + if (ast_strlen_zero(tmp)) { + return; + } AST_LIST_LOCK(&rule_lists); AST_LIST_TRAVERSE(&rule_lists, rl_iter, list) { if (!strcasecmp(rl_iter->name, tmp)) @@ -4459,15 +4494,13 @@ qe.last_periodic_announce_time = time(NULL); qe.last_periodic_announce_sound = 0; qe.valid_digits = 0; - if (join_queue(args.queuename, &qe, &reason)) { + if (join_queue(args.queuename, &qe, &reason, args.rule)) { ast_log(LOG_WARNING, "Unable to join queue '%s'\n", args.queuename); set_queue_result(chan, reason); return 0; } ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""), S_OR(chan->cid.cid_num, "")); - copy_rules(&qe, args.rule); - qe.pr = AST_LIST_FIRST(&qe.qe_rules); check_turns: if (ringing) { ast_indicate(chan, AST_CONTROL_RINGING); @@ -4489,7 +4522,8 @@ /* they may dial a digit from the queue context; */ /* or, they may timeout. */ - enum queue_member_status stat; + enum queue_member_status stat = QUEUE_NORMAL; + int exit = 0; /* Leave if we have exceeded our queuetimeout */ if (qe.expire && (time(NULL) > qe.expire)) { @@ -4525,7 +4559,6 @@ goto stop; } - stat = get_member_status(qe.parent, qe.max_penalty, qe.min_penalty); /* exit after 'timeout' cycle if 'n' option enabled */ if (noption && tries >= qe.parent->membercount) { @@ -4537,6 +4570,18 @@ break; } + for (; !exit || qe.pr; update_qe_rule(&qe)) { + + stat = get_member_status(qe.parent, qe.max_penalty, qe.min_penalty); + + if ((qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) || + ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) || + ((qe.parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_MEMBERS))) { + continue; + } else { + exit = 1; + } + } /* leave the queue if no agents, if enabled */ if (qe.parent->leavewhenempty && (stat == QUEUE_NO_MEMBERS)) { record_abandoned(&qe);