--- app_queue.c 2009-08-10 14:09:39.000000000 +0300 +++ app_queue_tikal.c 2009-08-10 14:09:39.000000000 +0300 @@ -119,7 +119,8 @@ QUEUE_STRATEGY_LEASTRECENT, QUEUE_STRATEGY_FEWESTCALLS, QUEUE_STRATEGY_RANDOM, - QUEUE_STRATEGY_RRMEMORY + QUEUE_STRATEGY_RRMEMORY, + QUEUE_STRATEGY_PENALTY_RINGALL }; static struct strategy { @@ -132,6 +133,7 @@ { QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" }, { QUEUE_STRATEGY_RANDOM, "random" }, { QUEUE_STRATEGY_RRMEMORY, "rrmemory" }, + { QUEUE_STRATEGY_PENALTY_RINGALL, "penalty_ringall" }, }; #define DEFAULT_RETRY 5 @@ -338,8 +340,11 @@ char announce[80]; /*!< Announcement to play for member when call is answered */ char context[AST_MAX_CONTEXT]; /*!< Context when user exits queue */ char digits[AST_MAX_EXTENSION]; /*!< Digits entered while in queue */ - int valid_digits; /*!< Digits entered correspond to valid extension. Exited */ + int valid_digits; /*!< Digits entered correspond to valid extension. Exited */ int pos; /*!< Where we are in the queue */ + int penaltypos; /*!< penalty ring all - position */ + int penalty_no_retry_wait; // skip retry if 1 - on penalties with no available members + int prioritized_attempt; //only set him best priority if attempt is 1 int prio; /*!< Our priority */ int last_pos_said; /*!< Last position we told the user */ time_t last_periodic_announce_time; /*!< The last time we played a periodic announcement */ @@ -439,6 +444,7 @@ int rrpos; /*!< Round Robin - position */ int memberdelay; /*!< Seconds to delay connecting member to caller */ int autofill; /*!< Ignore the head call status and ring an available agent */ + int agent_caller_priority; struct ao2_container *members; /*!< Head of the list of members */ /*! @@ -535,7 +541,8 @@ enum queue_member_status { QUEUE_NO_MEMBERS, QUEUE_NO_REACHABLE_MEMBERS, - QUEUE_NORMAL + QUEUE_NORMAL, + QUEUE_ALL_MEMBERS_BUSY // This means enter the queue but don't try dialing cause everyone is already talking }; /*! \brief Check if members are available @@ -552,24 +559,32 @@ ast_mutex_lock(&q->lock); mem_iter = ao2_iterator_init(q->members, 0); - while ((member = ao2_iterator_next(&mem_iter))) { + while ((member = ao2_iterator_next(&mem_iter))) + { if (max_penalty && (member->penalty > max_penalty)) { ao2_ref(member, -1); continue; } - if (member->paused) { + if (member->paused) + { ao2_ref(member, -1); continue; } - - switch (member->status) { + switch (member->status) + { + case AST_DEVICE_BUSY: + result = QUEUE_ALL_MEMBERS_BUSY; + ao2_ref(member, -1); + continue; case AST_DEVICE_INVALID: - /* nothing to do */ + if (result != QUEUE_ALL_MEMBERS_BUSY) + result = QUEUE_NO_REACHABLE_MEMBERS; ao2_ref(member, -1); break; case AST_DEVICE_UNAVAILABLE: - result = QUEUE_NO_REACHABLE_MEMBERS; + if (result != QUEUE_ALL_MEMBERS_BUSY) + result = QUEUE_NO_REACHABLE_MEMBERS; ao2_ref(member, -1); break; default: @@ -670,17 +685,55 @@ } AST_LIST_UNLOCK(&interfaces); - if (!curint) { - if (option_debug > 2) + if (!curint) + { /////////////// will only work with Crystal Call center! + // when any extension turns busy, check if it belongs to a logged-in agent and set its agent status as busy + // so the queue will not try to dial it. + // agent state should appears like this on asterisk db: + // CALLCENTER/EXTEN/'ext-num' 'agent-num'@online + // it should work independent, but I couldn't find other way to match agents-extensions from this functions. + char *db_family = "CALLCENTER/EXTEN"; + char *db_key = loc; + char *agent_num; + char full_agent[128]; + char queue_data[PM_MAX_LEN] = ""; + if(!ast_db_get(db_family, db_key, queue_data, PM_MAX_LEN)) + { + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: database result: %s\n", queue_data); + if (!queue_data) + return NULL; + if (strlen(queue_data) < 2) + return NULL; + if (!strstr(queue_data,"online")) + return NULL; + if (queue_data[0]=='@') + return NULL; + agent_num = strtok(queue_data,"@"); + if (agent_num == NULL) + return NULL; + strcpy(full_agent,"Agent/"); + strncat(full_agent,agent_num,10); + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: updating status for agent: %s to '%d' (%s)\n", full_agent, sc->state, devstate2str(sc->state)); + if (sc->state == AST_DEVICE_UNAVAILABLE || sc->state == AST_DEVICE_BUSY || sc->state == AST_DEVICE_NOT_INUSE || sc->state == AST_DEVICE_INVALID) + update_status(full_agent, sc->state); + else + update_status(full_agent, AST_DEVICE_BUSY); + return NULL; + //////////////up to here crystal call center modifications. + } + if (option_debug > 10) ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s) but we don't care because they're not a member of any queue.\n", technology, loc, sc->state, devstate2str(sc->state)); return NULL; } - + if (option_debug) ast_log(LOG_DEBUG, "Device '%s/%s' changed to state '%d' (%s)\n", technology, loc, sc->state, devstate2str(sc->state)); + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue : Device '%s/%s' changed to state '%d' (%s), sc->dev=%s\n", technology, loc, sc->state, devstate2str(sc->state),sc->dev); update_status(sc->dev, sc->state); - return NULL; } @@ -841,6 +894,7 @@ q->monfmt[0] = '\0'; q->periodicannouncefrequency = 0; q->reportholdtime = 0; + q->agent_caller_priority = 0; q->monjoin = 0; q->wrapuptime = 0; q->joinempty = 0; @@ -993,6 +1047,8 @@ } else if (!strcasecmp(param, "monitor-join")) { monjoin_dep_warning(); q->monjoin = ast_true(val); + } else if (!strcasecmp(param, "agent-caller-priority")) { + q->agent_caller_priority = ast_true(val); // for agent caller priority } else if (!strcasecmp(param, "monitor-format")) { ast_copy_string(q->monfmt, val, sizeof(q->monfmt)); } else if (!strcasecmp(param, "queue-youarenext")) { @@ -1455,19 +1511,25 @@ /* This is our one */ stat = get_member_status(q, qe->max_penalty); if (!q->joinempty && (stat == QUEUE_NO_MEMBERS)) + { *reason = QUEUE_JOINEMPTY; + } else if ((q->joinempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_MEMBERS)) + { *reason = QUEUE_JOINUNAVAIL; + } else if (q->maxlen && (q->count >= q->maxlen)) *reason = QUEUE_FULL; - else { + 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) { + 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. */ @@ -2026,7 +2088,8 @@ for (cur = outgoing; cur; cur = cur->q_next) { if (cur->stillgoing && /* Not already done */ !cur->chan && /* Isn't already going */ - (!best || cur->metric < best->metric)) { /* We haven't found one yet, or it's better */ + (!best || cur->metric < best->metric)) + { /* We haven't found one yet, or it's better */ best = cur; } } @@ -2046,13 +2109,42 @@ { int ret = 0; - while (ret == 0) { + while (ret == 0) + { struct callattempt *best = find_best(outgoing); if (!best) { if (option_debug) ast_log(LOG_DEBUG, "Nobody left to try ringing in queue\n"); break; } + + if (qe->parent->strategy == QUEUE_STRATEGY_PENALTY_RINGALL) + //work as ring all but consider penalties. + { + int skip_penalty = 1; + struct callattempt *cur; + /* Ring everyone who shares this best metric (for penalty_ringall) */ + for (cur = outgoing; cur; cur = cur->q_next) + if (cur->stillgoing && !cur->chan && cur->metric <= best->metric && cur->metric != 1000666) + skip_penalty = 0; // don't skip this penalty! + if (skip_penalty) + { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: no available user with this penatly, setting no_retry_wait to 1\n"); + qe->penalty_no_retry_wait = 1; + return 0; + } + qe->penalty_no_retry_wait = 0; + for (cur = outgoing; cur; cur = cur->q_next) + { + if (cur->stillgoing && !cur->chan && cur->metric <= best->metric) + { + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: Trying '%s' with metric %d\n", cur->interface, cur->metric); + ret |= ring_entry(qe, cur, busies); + } + } + } else if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) { struct callattempt *cur; /* Ring everyone who shares this best metric (for ringall) */ @@ -2162,7 +2254,8 @@ ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", rnatime); ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime); if (qe->parent->autopause && pause) { - if (!set_member_paused(qe->parent->name, interface, 1)) { +// if (!set_member_paused(qe->parent->name, interface, 1)) { + if (!set_member_paused(NULL, interface, 1)) { // set auto pause to pause all and not only RNA queue if (option_verbose > 2) ast_verbose( VERBOSE_PREFIX_3 "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", interface, qe->parent->name); } else { @@ -2203,14 +2296,18 @@ long endtime = 0; starttime = (long) time(NULL); - - while (*to && !peer) { - int numlines, retry, pos = 1; + while (*to && !peer) + { + int max_retry = 2, numlines, retry, pos = 1; struct ast_channel *watchers[AST_MAX_WATCHERS]; watchers[0] = in; start = NULL; - for (retry = 0; retry < 2; retry++) { + if (qe->parent->strategy == QUEUE_STRATEGY_PENALTY_RINGALL) + max_retry = 1; + + for (retry = 0; retry < max_retry; retry++) + { // chnage to one for no answer/ dnd to not effect ring all with penalties numlines = 0; for (o = outgoing; o; o = o->q_next) { /* Keep track of important channels */ if (o->stillgoing) { /* Keep track of important channels */ @@ -2227,7 +2324,7 @@ numlines++; } if (pos > 1 /* found */ || !stillgoing /* nobody listening */ || - (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) /* ring would not be delivered */) + (qe->parent->strategy != QUEUE_STRATEGY_RINGALL && qe->parent->strategy != QUEUE_STRATEGY_PENALTY_RINGALL) /* ring would not be delivered */) break; /* On "ringall" strategy we only move to the next penalty level when *all* ringing phones are done in the current penalty level */ @@ -2235,7 +2332,8 @@ /* and retry... */ } if (pos == 1 /* not found */) { - if (numlines == (numbusies + numnochan)) { + if (numlines == (numbusies + numnochan)) + { ast_log(LOG_DEBUG, "Everyone is busy at this time\n"); } else { ast_log(LOG_NOTICE, "No one is answering queue '%s' (%d/%d/%d)\n", queue, numlines, numbusies, numnochan); @@ -2339,12 +2437,16 @@ endtime = (long)time(NULL); endtime -= starttime; rna(endtime * 1000, qe, on, membername, 0); - if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { + if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL && qe->parent->strategy != QUEUE_STRATEGY_PENALTY_RINGALL) { if (qe->parent->timeoutrestart) *to = orig; ring_one(qe, outgoing, &numbusies); } numbusies++; + if (qe->parent->strategy == QUEUE_STRATEGY_PENALTY_RINGALL) + { + ast_waitfordigit(qe->chan, qe->parent->retry * 1000); // retry sleep + } break; case AST_CONTROL_CONGESTION: if (option_verbose > 2) @@ -2355,12 +2457,13 @@ endtime -= starttime; rna(endtime * 1000, qe, on, membername, 0); do_hang(o); - if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { + if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL && qe->parent->strategy != QUEUE_STRATEGY_PENALTY_RINGALL) { if (qe->parent->timeoutrestart) *to = orig; ring_one(qe, outgoing, &numbusies); } numbusies++; + qe->penalty_no_retry_wait=0; break; case AST_CONTROL_RINGING: if (option_verbose > 2) @@ -2378,7 +2481,8 @@ endtime = (long) time(NULL) - starttime; rna(endtime * 1000, qe, on, membername, 1); do_hang(o); - if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { + if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL && qe->parent->strategy != QUEUE_STRATEGY_PENALTY_RINGALL) + { if (qe->parent->timeoutrestart) *to = orig; ring_one(qe, outgoing, &numbusies); @@ -2417,7 +2521,6 @@ rna(orig, qe, o->interface, o->member->membername, 1); } } - return peer; } @@ -2434,37 +2537,73 @@ static int is_our_turn(struct queue_ent *qe) { struct queue_ent *ch; - int res; - int avl; + struct member *cur; + int avl = 0; int idx = 0; - /* This needs a lock. How many members are available to be served? */ - ast_mutex_lock(&qe->parent->lock); - - avl = num_available_members(qe->parent); - - ch = qe->parent->head; - - if (option_debug) { - ast_log(LOG_DEBUG, "There %s %d available %s.\n", avl != 1 ? "are" : "is", avl, avl != 1 ? "members" : "member"); - } - - while ((idx < avl) && (ch) && (ch != qe)) { - if (!ch->pending) - idx++; - ch = ch->next; - } + int res; - ast_mutex_unlock(&qe->parent->lock); + if (!qe->parent->autofill) { + /* Atomically read the parent head -- does not need a lock */ + ch = qe->parent->head; + /* If we are now at the top of the head, break out */ + if (ch == qe) { + if (option_debug) + ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name); + res = 1; + } else { + if (option_debug) + ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name); + res = 0; + } - /* If the queue entry is within avl [the number of available members] calls from the top ... */ - if (ch && idx < avl) { - if (option_debug) - ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name); - res = 1; } else { + /* This needs a lock. How many members are available to be served? */ + ast_mutex_lock(&qe->parent->lock); + + ch = qe->parent->head; + struct ao2_iterator mem_iter = ao2_iterator_init(qe->parent->members, 0); + while ((cur = ao2_iterator_next(&mem_iter))) { + switch (cur->status) { + case AST_DEVICE_INUSE: + if (!qe->parent->ringinuse) + break; + /* else fall through */ + case AST_DEVICE_NOT_INUSE: + case AST_DEVICE_UNKNOWN: + if (!cur->paused) + avl++; + break; + } + ao2_ref(cur, -1); + } + if (qe->parent->strategy == QUEUE_STRATEGY_RINGALL) { // ring all still check avl + 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"); + if (avl > 0) + avl = 1; + } + if (option_debug) - ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name); - res = 0; + ast_log(LOG_DEBUG, "There are %d available members.\n", avl); + + while ((idx < avl) && (ch) && (ch != qe)) { + if (!ch->pending) + idx++; + ch = ch->next; + } + + /* If the queue entry is within avl [the number of available members] calls from the top ... */ + if (ch && idx < avl) { + if (option_debug) + ast_log(LOG_DEBUG, "It's our turn (%s).\n", qe->chan->name); + res = 1; + } else { + if (option_debug) + ast_log(LOG_DEBUG, "It's not our turn (%s).\n", qe->chan->name); + res = 0; + } + + ast_mutex_unlock(&qe->parent->lock); } return res; @@ -2578,6 +2717,35 @@ return -1; switch (q->strategy) { + + case QUEUE_STRATEGY_PENALTY_RINGALL: + if (qe->penaltypos == -1) //-1 means this is a new call - reset everything + { + q->wrapped = 0; + qe->penaltypos = 0; + q->rrpos = 0; + } + if (!pos) + { + if (!q->wrapped) + { // No more channels, start over + qe->penaltypos = 0; + } else { // Prioritize next entry + qe->penaltypos++; + } + q->wrapped = 0; + } + if (qe->penaltypos == mem->penalty) + { + tmp->metric = 0; // ring this agent + } else + { + if (mem->penalty > qe->penaltypos) // Indicate there is another priority + q->wrapped = 1; + tmp->metric = 1000666; + } + break; + case QUEUE_STRATEGY_RINGALL: /* Everyone equal, except for penalty */ tmp->metric = mem->penalty * 1000000; @@ -2624,6 +2792,42 @@ ast_log(LOG_WARNING, "Can't calculate metric for unknown strategy %d\n", q->strategy); break; } + + if (qe->prioritized_attempt == 1) // we have super priority enabled + { + const char *prioritized_agent = pbx_builtin_getvar_helper(qe->chan, "prioritized_agent"); + if (prioritized_agent) + { + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: prioritizied agent = %s \n",prioritized_agent); + if (strstr(mem->interface,prioritized_agent)) + { + qe->prioritized_attempt=2; + tmp->metric = 0; + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: found the member !prioritizied agent = %s\n",prioritized_agent); + } + else + { + tmp->metric += 1; + } + } else + { + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: prioritizied agent is not set\n"); + qe->prioritized_attempt=0; + } + } else if (qe->prioritized_attempt == 2) + { + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: prioritizied agent was already found, inc metric of all others\n"); + tmp->metric += 1; + } + + + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: member: %s penalty: %d, metric:%d, pos is: %d, q->rrpos = %d,q->penaltypos = %d\n",mem->interface, mem->penalty, tmp->metric, pos, q->rrpos, qe->penaltypos); + return 0; } @@ -3291,9 +3495,19 @@ static int wait_a_bit(struct queue_ent *qe) { - /* Don't need to hold the lock while we setup the outgoing calls */ + if (qe->parent->strategy == QUEUE_STRATEGY_PENALTY_RINGALL) + //on penalty_ringall we need to check all priorities without holding, otherwise will take alog time to reach penalty 10 with it's the only one avail + { + if (option_verbose > 10) + ast_verbose(VERBOSE_PREFIX_3 "cc app_queue: wait_a_bit: no_retry_wait=%d\n", qe->penalty_no_retry_wait); + if(qe->penalty_no_retry_wait) + { + qe->penalty_no_retry_wait = 0; + return 0; + } + } + int retrywait = qe->parent->retry * 1000; - int res = ast_waitfordigit(qe->chan, retrywait); if (res > 0 && !valid_exit(qe, res)) res = 0; @@ -4006,8 +4220,14 @@ qe.last_periodic_announce_time = time(NULL); qe.last_periodic_announce_sound = 0; qe.valid_digits = 0; + qe.penaltypos = -1; // -1 means that queue is wrapped (=0) + qe.penalty_no_retry_wait = 1; + if (!join_queue(args.queuename, &qe, &reason)) { int makeannouncement = 0; + if (qe.parent->agent_caller_priority) + qe.prioritized_attempt = 1; //enabled + ast_queue_log(args.queuename, chan->uniqueid, "NONE", "ENTERQUEUE", "%s|%s", S_OR(args.url, ""), S_OR(chan->cid.cid_num, "")); @@ -4074,12 +4294,16 @@ break; } /* Try calling all queue members for 'timeout' seconds */ - res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi); + + stat = get_member_status(qe.parent, qe.max_penalty); + + if (stat == QUEUE_NORMAL) + { + res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi); + } if (res) goto stop; - - stat = get_member_status(qe.parent, qe.max_penalty); - + /* exit after 'timeout' cycle if 'n' option enabled */ if (noption && tries >= qe.parent->membercount) { if (option_verbose > 2)