Index: app_queue.c =================================================================== --- app_queue.c (revision 180005) +++ app_queue.c (working copy) @@ -156,29 +156,19 @@ "any of the join options cause the caller to not enter the queue.\n" "The option string may contain zero or more of the following characters:\n" " 'd' -- data-quality (modem) call (minimum delay).\n" -" 'h' -- allow callee to hang up by hitting '*', or whatver disconnect sequence\n" -" defined in the featuremap section in features.conf.\n" -" 'H' -- allow caller to hang up by hitting '*', or whatever disconnect sequence\n" -" defined in the featuremap section in features.conf.\n" +" 'h' -- allow callee to hang up by hitting *.\n" +" 'H' -- allow caller to hang up by hitting *.\n" " 'n' -- no retries on the timeout; will exit this application and \n" " go to the next step.\n" " 'i' -- ignore call forward requests from queue members and do nothing\n" " when they are requested.\n" " 'r' -- ring instead of playing MOH\n" -" 't' -- allow the called user transfer the calling user by pressing '#' or\n" -" whatever blindxfer sequence defined in the featuremap section in\n" -" features.conf\n" -" 'T' -- to allow the calling user to transfer the call by pressing '#' or\n" -" whatever blindxfer sequence defined in the featuremap section in\n" -" features.conf\n" +" 't' -- allow the called user transfer the calling user\n" +" 'T' -- to allow the calling user to transfer the call.\n" " 'w' -- allow the called user to write the conversation to disk via Monitor\n" -" by pressing the automon sequence defined in the featuremap section in\n" -" features.conf\n" " 'W' -- allow the calling user to write the conversation to disk via Monitor\n" -" by pressing the automon sequence defined in the featuremap section in\n" -" features.conf\n" " In addition to transferring the call, a call may be parked and then picked\n" -"up by another user, by transferring to the parking lot extension. See features.conf.\n" +"up by another user.\n" " The optional URL will be sent to the called party if the channel supports\n" "it.\n" " The optional AGI parameter will setup an AGI script to be executed on the \n" @@ -265,6 +255,15 @@ "Allows you to write your own events into the queue log\n" "Example: QueueLog(101|${UNIQUEID}|${AGENT}|WENTONBREAK|600)\n"; +static char *app_ulc = "UpdateLastCall" ; +static char *app_ulc_synopsis = "Dynamically removes queue members" ; +static char *app_ulc_descrip = +" UpdateLastCall(interface):\n" +"Update the last call timestamp of member 'interface'\n" +"Only works if shared_lastcall is set to yes\n" +"Example: UpdateLastCall(techsupport|SIP/3000)\n" +""; + /*! \brief Persistent Members astdb family */ static const char *pm_family = "Queue/PersistentMembers"; /* The maximum length of each persistent member queue database entry */ @@ -282,6 +281,9 @@ /*! \brief queues.conf [general] option */ static int montype_default = 0; +/*! \brief queues.conf [general] option */ +static int shared_lastcall = 0; + enum queue_result { QUEUE_UNKNOWN = 0, QUEUE_TIMEOUT = 1, @@ -325,6 +327,7 @@ int metric; int oldstatus; time_t lastcall; + struct call_queue *lastqueue; struct member *member; }; @@ -362,6 +365,7 @@ int status; /*!< Status of queue member */ int paused; /*!< Are we paused (not accepting calls)? */ time_t lastcall; /*!< When last successful call was hungup */ + struct call_queue *lastqueue; /*!< Last queue we received a call */ unsigned int dead:1; /*!< Used to detect members deleted in realtime */ unsigned int delme:1; /*!< Flag to delete entry on reload */ }; @@ -376,6 +380,7 @@ /* values used in multi-bit flags in call_queue */ #define QUEUE_EMPTY_NORMAL 1 #define QUEUE_EMPTY_STRICT 2 +#define QUEUE_EMPTY_LOOSE 3 #define ANNOUNCEHOLDTIME_ALWAYS 1 #define ANNOUNCEHOLDTIME_ONCE 2 #define QUEUE_EVENT_VARIABLES 3 @@ -404,7 +409,7 @@ int announcefrequency; /*!< How often to announce their position */ int periodicannouncefrequency; /*!< How often to play periodic announcement */ int roundingseconds; /*!< How many seconds do we round to? */ - int holdtime; /*!< Current avg holdtime, based on an exponential average */ + int holdtime; /*!< Current avg holdtime, based on recursive boxcar filter */ int callscompleted; /*!< Number of queue calls completed */ int callsabandoned; /*!< Number of queue calls abandoned */ int servicelevel; /*!< seconds setting for servicelevel*/ @@ -451,8 +456,6 @@ static int set_member_paused(const char *queuename, const char *interface, int paused); -static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); - static void rr_dep_warning(void) { static unsigned int warned = 0; @@ -531,6 +534,7 @@ enum queue_member_status { QUEUE_NO_MEMBERS, QUEUE_NO_REACHABLE_MEMBERS, + QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS, QUEUE_NORMAL }; @@ -554,24 +558,25 @@ continue; } - if (member->paused) { - ao2_ref(member, -1); - continue; - } - switch (member->status) { case AST_DEVICE_INVALID: /* nothing to do */ ao2_ref(member, -1); break; case AST_DEVICE_UNAVAILABLE: - result = QUEUE_NO_REACHABLE_MEMBERS; + if (result != QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS) + result = QUEUE_NO_REACHABLE_MEMBERS; ao2_ref(member, -1); break; default: - ast_mutex_unlock(&q->lock); - ao2_ref(member, -1); - return QUEUE_NORMAL; + if (member->paused) { + result = QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS; + } else { + ast_mutex_unlock(&q->lock); + ao2_ref(member, -1); + return QUEUE_NORMAL; + } + break; } } @@ -807,7 +812,7 @@ static int member_cmp_fn(void *obj1, void *obj2, int flags) { struct member *mem1 = obj1, *mem2 = obj2; - return strcmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH | CMP_STOP; + return strcmp(mem1->interface, mem2->interface) ? 0 : CMP_MATCH; } static void init_queue(struct call_queue *q) @@ -1065,14 +1070,18 @@ q->strategy = QUEUE_STRATEGY_RINGALL; } } else if (!strcasecmp(param, "joinempty")) { - if (!strcasecmp(val, "strict")) + 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, "strict")) + 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; @@ -1239,9 +1248,9 @@ ast_mutex_lock(&q->lock); clear_queue(q); q->realtime = 1; + init_queue(q); /* Ensure defaults for all parameters not set explicitly. */ AST_LIST_INSERT_HEAD(&queues, q, list); } - init_queue(q); /* Ensure defaults for all parameters not set explicitly. */ memset(tmpbuf, 0, sizeof(tmpbuf)); for (v = queue_vars; v; v = v->next) { @@ -1254,11 +1263,7 @@ *tmp++ = '-'; } else tmp_name = v->name; - - if (!ast_strlen_zero(v->value)) { - /* Don't want to try to set the option if the value is empty */ - queue_set_param(q, tmp_name, v->value, -1, 0); - } + queue_set_param(q, tmp_name, v->value, -1, 0); } if (q->strategy == QUEUE_STRATEGY_ROUNDROBIN) @@ -1301,12 +1306,11 @@ static int update_realtime_member_field(struct member *mem, const char *queue_name, const char *field, const char *value) { - struct ast_variable *var, *save; + struct ast_variable *var; int ret = -1; if (!(var = ast_load_realtime("queue_members", "interface", mem->interface, "queue_name", queue_name, NULL))) return ret; - save = var; while (var) { if (!strcmp(var->name, "uniqueid")) break; @@ -1316,7 +1320,6 @@ if ((ast_update_realtime("queue_members", "uniqueid", var->value, field, value, NULL)) > -1) ret = 0; } - ast_variables_destroy(save); return ret; } @@ -1436,8 +1439,10 @@ 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)) + 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 { @@ -1579,9 +1584,8 @@ /* 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 */ - if ((avgholdmins+avgholdsecs) > 0 && qe->parent->announceholdtime && - ((qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE && !qe->last_pos) || - !(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE))) { + if ((avgholdmins+avgholdsecs) > 0 && (qe->parent->announceholdtime) && + (!(qe->parent->announceholdtime == ANNOUNCEHOLDTIME_ONCE) && qe->last_pos)) { res = play_file(qe->chan, qe->parent->sound_holdtime); if (res) goto playout; @@ -1642,7 +1646,7 @@ { int oldvalue; - /* Calculate holdtime using an exponential average */ + /* 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 */ @@ -1713,7 +1717,53 @@ } } +/*! \brief Calculate the number of availabe queue members + * + * A member is available if it's not inuse (ringing). If the strategy set to + * QUEUE_STRATEGY_RINGALL, then return 1. + * + * Note: Duplicated code from is_our_turn(). + * + * By GC. 08/08/2008 + */ +static int get_available_members(struct call_queue *q) +{ + struct member *mem; + int avl = 0; + /* This needs a lock. How many members are available to be served? */ + ast_mutex_lock(&q->lock); + + if (q->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"); + avl = 1; + } else { + struct ao2_iterator mem_iter = ao2_iterator_init(q->members, 0); + while ((mem = ao2_iterator_next(&mem_iter))) { + switch (mem->status) { + case AST_DEVICE_INUSE: + if (!q->ringinuse) + break; + /* else fall through */ + case AST_DEVICE_NOT_INUSE: + case AST_DEVICE_UNKNOWN: + if (!mem->paused) + avl++; + break; + } + ao2_ref(mem, -1); + } + } + + if (option_debug) + ast_log(LOG_DEBUG, "There are %d available members.\n", avl); + + ast_mutex_unlock(&q->lock); + + return avl; +} + /* 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) @@ -1731,7 +1781,9 @@ if (q->count && q->members) { if ((mem = ao2_find(q->members, member, OBJ_POINTER))) { ast_log(LOG_DEBUG, "Found matching member %s in queue '%s'\n", mem->interface, q->name); - if (q->weight > rq->weight) { + // changed by GC, 07/08/2008, use autofill logic to be able to send low priority call to agents while all high-priority calls are presented + //if ( q->weight > rq->weight) { + if ( (q->weight > rq->weight) && ( q->count >= get_available_members(q) ) ) { ast_log(LOG_DEBUG, "Queue '%s' (weight %d, calls %d) is preferred over '%s' (weight %d, calls %d)\n", q->name, q->weight, q->count, rq->name, rq->weight, rq->count); found = 1; } @@ -1776,10 +1828,10 @@ j += 9; } } - if (j > len - 3) - j = len - 3; - vars[j++] = '\r'; - vars[j++] = '\n'; + if (j > len - 1) + j = len - 1; + vars[j - 2] = '\r'; + vars[j - 1] = '\n'; vars[j] = '\0'; } else { /* there are no channel variables; leave it blank */ @@ -1799,12 +1851,15 @@ int status; char tech[256]; char *location; - const char *macrocontext, *macroexten; /* on entry here, we know that tmp->chan == NULL */ - if (qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime)) { - if (option_debug) - ast_log(LOG_DEBUG, "Wrapuptime not yet expired for %s\n", tmp->interface); + if ((tmp->lastqueue && tmp->lastqueue->wrapuptime && (time(NULL) - tmp->lastcall < tmp->lastqueue->wrapuptime)) || + (!tmp->lastqueue && qe->parent->wrapuptime && (time(NULL) - tmp->lastcall < qe->parent->wrapuptime))) { + if (option_verbose > 2) + ast_verbose(VERBOSE_PREFIX_3 "Wrapuptime not yet expired on queue %s for %s\n", (tmp->lastqueue ? tmp->lastqueue->name : qe->parent->name), tmp->interface); + else + ast_log(LOG_DEBUG, "Wrapuptime not yet expired on queue %s for %s\n", (tmp->lastqueue ? tmp->lastqueue->name : qe->parent->name), tmp->interface); + if (qe->chan->cdr) ast_cdr_busy(qe->chan->cdr); tmp->stillgoing = 0; @@ -1881,18 +1936,14 @@ tmp->chan->adsicpe = qe->chan->adsicpe; /* Inherit context and extension */ - ast_channel_lock(qe->chan); - macrocontext = pbx_builtin_getvar_helper(qe->chan, "MACRO_CONTEXT"); - if (!ast_strlen_zero(macrocontext)) - ast_copy_string(tmp->chan->dialcontext, macrocontext, sizeof(tmp->chan->dialcontext)); + if (!ast_strlen_zero(qe->chan->macrocontext)) + ast_copy_string(tmp->chan->dialcontext, qe->chan->macrocontext, sizeof(tmp->chan->dialcontext)); else ast_copy_string(tmp->chan->dialcontext, qe->chan->context, sizeof(tmp->chan->dialcontext)); - macroexten = pbx_builtin_getvar_helper(qe->chan, "MACRO_EXTEN"); - if (!ast_strlen_zero(macroexten)) - ast_copy_string(tmp->chan->exten, macroexten, sizeof(tmp->chan->exten)); + if (!ast_strlen_zero(qe->chan->macroexten)) + ast_copy_string(tmp->chan->exten, qe->chan->macroexten, sizeof(tmp->chan->exten)); else ast_copy_string(tmp->chan->exten, qe->chan->exten, sizeof(tmp->chan->exten)); - ast_channel_unlock(qe->chan); /* Place the call, but don't wait on the answer */ if ((res = ast_call(tmp->chan, location, 0))) { @@ -1903,7 +1954,6 @@ ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", tmp->interface); do_hang(tmp); (*busies)++; - update_status(tmp->member->interface, ast_device_state(tmp->member->interface)); return 0; } else if (qe->parent->eventwhencalled) { char vars[2048]; @@ -1927,7 +1977,6 @@ ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", tmp->interface); } - update_status(tmp->member->interface, ast_device_state(tmp->member->interface)); return 1; } @@ -2069,12 +2118,12 @@ } /*! \brief RNA == Ring No Answer. Common code that is executed when we try a queue member and they don't answer. */ -static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername, int pause) +static void rna(int rnatime, struct queue_ent *qe, char *interface, char *membername) { if (option_verbose > 2) 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 (qe->parent->autopause) { if (!set_member_paused(qe->parent->name, interface, 1)) { 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); @@ -2251,7 +2300,7 @@ do_hang(o); endtime = (long)time(NULL); endtime -= starttime; - rna(endtime * 1000, qe, on, membername, 0); + rna(endtime*1000, qe, on, membername); if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { if (qe->parent->timeoutrestart) *to = orig; @@ -2266,7 +2315,7 @@ ast_cdr_busy(in->cdr); endtime = (long)time(NULL); endtime -= starttime; - rna(endtime * 1000, qe, on, membername, 0); + rna(endtime*1000, qe, on, membername); do_hang(o); if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { if (qe->parent->timeoutrestart) @@ -2277,7 +2326,8 @@ break; case AST_CONTROL_RINGING: if (option_verbose > 2) - ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name); + // ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name); + ast_verbose( VERBOSE_PREFIX_3 "app_queue: %s is ringing\n", o->chan->name); break; case AST_CONTROL_OFFHOOK: /* Ignore going off hook */ @@ -2289,7 +2339,7 @@ ast_frfree(f); } else { endtime = (long) time(NULL) - starttime; - rna(endtime * 1000, qe, on, membername, 1); + rna(endtime * 1000, qe, on, membername); do_hang(o); if (qe->parent->strategy != QUEUE_STRATEGY_RINGALL) { if (qe->parent->timeoutrestart) @@ -2327,7 +2377,7 @@ } if (!*to) { for (o = start; o; o = o->call_next) - rna(orig, qe, o->interface, o->member->membername, 1); + rna(orig, qe, o->interface, o->member->membername); } } @@ -2417,6 +2467,7 @@ return res; } + /*! \brief The waiting areas for callers who are not actively calling members * * This function is one large loop. This function will return if a caller @@ -2439,7 +2490,7 @@ break; /* If we have timed out, break out */ - if (qe->expire && (time(NULL) >= qe->expire)) { + if (qe->expire && (time(NULL) > qe->expire)) { *reason = QUEUE_TIMEOUT; break; } @@ -2455,8 +2506,14 @@ } /* leave the queue if no reachable agents, if enabled */ - if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { + if ((qe->parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) { *reason = QUEUE_LEAVEUNAVAIL; + 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; + } + if ((qe->parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { + *reason = QUEUE_LEAVEUNAVAIL; 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; @@ -2467,23 +2524,11 @@ (res = say_position(qe))) break; - /* If we have timed out, break out */ - if (qe->expire && (time(NULL) >= qe->expire)) { - *reason = QUEUE_TIMEOUT; - break; - } - /* Make a periodic announcement, if enabled */ if (qe->parent->periodicannouncefrequency && !ringing && (res = say_periodic_announcement(qe))) break; - /* If we have timed out, break out */ - if (qe->expire && (time(NULL) >= qe->expire)) { - *reason = QUEUE_TIMEOUT; - break; - } - /* Wait a second before checking again */ if ((res = ast_waitfordigit(qe->chan, RECHECK * 1000))) { if (res > 0 && !valid_exit(qe, res)) @@ -2491,12 +2536,6 @@ else break; } - - /* If we have timed out, break out */ - if (qe->expire && (time(NULL) >= qe->expire)) { - *reason = QUEUE_TIMEOUT; - break; - } } return res; @@ -2504,9 +2543,31 @@ static int update_queue(struct call_queue *q, struct member *member, int callcompletedinsl) { + struct member *mem; + struct call_queue *qtmp; + + if (shared_lastcall) { + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, qtmp, list) { + ast_mutex_lock(&qtmp->lock); + if ((mem = ao2_find(qtmp->members, member, OBJ_POINTER))) { + time(&mem->lastcall); + mem->calls++; + mem->lastqueue = q; + ao2_ref(mem, -1); + } + ast_mutex_unlock(&qtmp->lock); + } + AST_LIST_UNLOCK(&queues); + } else { + ast_mutex_lock(&q->lock); + time(&member->lastcall); + member->calls++; + member->lastqueue = q; + ast_mutex_unlock(&q->lock); + } + ast_mutex_lock(&q->lock); - time(&member->lastcall); - member->calls++; q->callscompleted++; if (callcompletedinsl) q->callscompletedinsl++; @@ -2514,6 +2575,63 @@ return 0; } +static int update_lastcall(const char *interface) +{ + struct member *mem, tmpmem; + struct call_queue *qtmp; + + ast_copy_string(tmpmem.interface, interface, sizeof(tmpmem.interface)); + + if (shared_lastcall) { + AST_LIST_LOCK(&queues); + AST_LIST_TRAVERSE(&queues, qtmp, list) { + ast_mutex_lock(&qtmp->lock); + if ((mem = ao2_find(qtmp->members, &tmpmem, OBJ_POINTER))) { + ast_verbose(VERBOSE_PREFIX_3 "Update lastcall of member %s in queue %s\n", interface, qtmp->name); + time(&mem->lastcall); + ao2_ref(mem, -1); + } + ast_mutex_unlock(&qtmp->lock); + } + AST_LIST_UNLOCK(&queues); + } else { + ast_log(LOG_WARNING, "Can only update lastcall of member %s in shared_lastcall mode\n", interface); + } + + return 0; +} + +static int ulc_exec(struct ast_channel *chan, void *data) +{ + struct ast_module_user *lu; + char *parse = NULL; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(interface); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_WARNING, "UpdateLastCall requires an argument (interface)\n"); + return -1; + } + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + lu = ast_module_user_add(chan); + + if (ast_strlen_zero(args.interface)) { + ast_log(LOG_WARNING, "Interface cannot be zero-length\n"); + return -1; + } + + update_lastcall(args.interface); + + ast_module_user_remove(lu); + + return 0; +} + /*! \brief Calculate the metric of each member in the outgoing callattempts * * A numeric metric is given to each member depending on the ring strategy used @@ -2574,104 +2692,6 @@ } return 0; } - -struct queue_transfer_ds { - struct queue_ent *qe; - struct member *member; - time_t starttime; - int callcompletedinsl; -}; - -static void queue_transfer_destroy(void *data) -{ - struct queue_transfer_ds *qtds = data; - ast_free(qtds); -} - -/*! \brief a datastore used to help correctly log attended transfers of queue callers - */ -static const struct ast_datastore_info queue_transfer_info = { - .type = "queue_transfer", - .chan_fixup = queue_transfer_fixup, - .destroy = queue_transfer_destroy, -}; - -/*! \brief Log an attended transfer when a queue caller channel is masqueraded - * - * When a caller is masqueraded, we want to log a transfer. Fixup time is the closest we can come to when - * the actual transfer occurs. This happens during the masquerade after datastores are moved from old_chan - * to new_chan. This is why new_chan is referenced for exten, context, and datastore information. - * - * At the end of this, we want to remove the datastore so that this fixup function is not called on any - * future masquerades of the caller during the current call. - */ -static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) -{ - struct queue_transfer_ds *qtds = data; - struct queue_ent *qe = qtds->qe; - struct member *member = qtds->member; - time_t callstart = qtds->starttime; - int callcompletedinsl = qtds->callcompletedinsl; - struct ast_datastore *datastore; - - ast_queue_log(qe->parent->name, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld", - new_chan->exten, new_chan->context, (long) (callstart - qe->start), - (long) (time(NULL) - callstart)); - - update_queue(qe->parent, member, callcompletedinsl); - - /* No need to lock the channels because they are already locked in ast_do_masquerade */ - if ((datastore = ast_channel_datastore_find(old_chan, &queue_transfer_info, NULL))) { - ast_channel_datastore_remove(old_chan, datastore); - } else { - ast_log(LOG_WARNING, "Can't find the queue_transfer datastore.\n"); - } -} - -/*! \brief mechanism to tell if a queue caller was atxferred by a queue member. - * - * When a caller is atxferred, then the queue_transfer_info datastore - * is removed from the channel. If it's still there after the bridge is - * broken, then the caller was not atxferred. - * - * \note Only call this with chan locked - */ -static int attended_transfer_occurred(struct ast_channel *chan) -{ - return ast_channel_datastore_find(chan, &queue_transfer_info, NULL) ? 0 : 1; -} - -/*! \brief create a datastore for storing relevant info to log attended transfers in the queue_log - */ -static struct ast_datastore *setup_transfer_datastore(struct queue_ent *qe, struct member *member, time_t starttime, int callcompletedinsl) -{ - struct ast_datastore *ds; - struct queue_transfer_ds *qtds = ast_calloc(1, sizeof(*qtds)); - - if (!qtds) { - ast_log(LOG_WARNING, "Memory allocation error!\n"); - return NULL; - } - - ast_channel_lock(qe->chan); - if (!(ds = ast_channel_datastore_alloc(&queue_transfer_info, NULL))) { - ast_channel_unlock(qe->chan); - ast_log(LOG_WARNING, "Unable to create transfer datastore. queue_log will not show attended transfer\n"); - return NULL; - } - - qtds->qe = qe; - /* This member is refcounted in try_calling, so no need to add it here, too */ - qtds->member = member; - qtds->starttime = starttime; - qtds->callcompletedinsl = callcompletedinsl; - ds->data = qtds; - ast_channel_datastore_add(qe->chan, ds); - ast_channel_unlock(qe->chan); - return ds; -} - - /*! \brief A large function which calls members, updates statistics, and bridges the caller and a member * * Here is the process of this function @@ -2732,7 +2752,7 @@ int forwardsallowed = 1; int callcompletedinsl; struct ao2_iterator memi; - struct ast_datastore *datastore, *transfer_ds; + struct ast_datastore *datastore; ast_channel_lock(qe->chan); datastore = ast_channel_datastore_find(qe->chan, &dialed_interface_info, NULL); @@ -2740,15 +2760,6 @@ memset(&bridge_config, 0, sizeof(bridge_config)); time(&now); - - /* If we've already exceeded our timeout, then just stop - * This should be extremely rare. queue_exec will take care - * of removing the caller and reporting the timeout as the reason. - */ - if (qe->expire && now >= qe->expire) { - res = 0; - goto out; - } for (; options && *options; options++) switch (*options) { @@ -2876,6 +2887,7 @@ tmp->member = cur; tmp->oldstatus = cur->status; tmp->lastcall = cur->lastcall; + tmp->lastqueue = cur->lastqueue; ast_copy_string(tmp->interface, cur->interface, sizeof(tmp->interface)); /* Special case: If we ring everyone, go ahead and ring them, otherwise just calculate their metric for the appropriate strategy */ @@ -2903,17 +2915,10 @@ if (use_weight) AST_LIST_UNLOCK(&queues); lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed); - /* The ast_channel_datastore_remove() function could fail here if the - * datastore was moved to another channel during a masquerade. If this is - * the case, don't free the datastore here because later, when the channel - * to which the datastore was moved hangs up, it will attempt to free this - * datastore again, causing a crash - */ - ast_channel_lock(qe->chan); - if (datastore && !ast_channel_datastore_remove(qe->chan, datastore)) { + if (datastore) { + ast_channel_datastore_remove(qe->chan, datastore); ast_channel_datastore_free(datastore); } - ast_channel_unlock(qe->chan); ast_mutex_lock(&qe->parent->lock); if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) { store_next(qe, outgoing); @@ -2935,6 +2940,7 @@ /* Ah ha! Someone answered within the desired timeframe. Of course after this we will always return with -1 so that it is hung up properly after the conversation. */ + qe->handled++; if (!strcmp(qe->chan->tech->type, "Zap")) ast_channel_setoption(qe->chan, AST_OPTION_TONE_VERIFY, &nondataquality, sizeof(nondataquality), 0); if (!strcmp(peer->tech->type, "Zap")) @@ -2982,6 +2988,7 @@ /* Agent must have hung up */ ast_log(LOG_WARNING, "Agent on %s hungup on the customer.\n", peer->name); ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "AGENTDUMP", "%s", ""); + record_abandoned(qe); if (qe->parent->eventwhencalled) manager_event(EVENT_FLAG_AGENT, "AgentDump", "Queue: %s\r\n" @@ -3135,7 +3142,6 @@ } else ast_log(LOG_WARNING, "Asked to execute an AGI on this channel, but could not find application (agi)!\n"); } - qe->handled++; ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "CONNECT", "%ld|%s", (long)time(NULL) - qe->start, peer->uniqueid); if (qe->parent->eventwhencalled) manager_event(EVENT_FLAG_AGENT, "AgentConnect", @@ -3157,61 +3163,51 @@ if (member->status == AST_DEVICE_NOT_INUSE) ast_log(LOG_WARNING, "The device state of this queue member, %s, is still 'Not in Use' when it probably should not be! Please check UPGRADE.txt for correct configuration settings.\n", member->membername); - transfer_ds = setup_transfer_datastore(qe, member, callstart, callcompletedinsl); + bridge = ast_bridge_call(qe->chan,peer, &bridge_config); - ast_channel_lock(qe->chan); - if (!attended_transfer_occurred(qe->chan)) { - struct ast_datastore *tds; - if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) { - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld", - qe->chan->exten, qe->chan->context, (long) (callstart - qe->start), - (long) (time(NULL) - callstart)); - } else if (qe->chan->_softhangup) { - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d", - (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); - if (qe->parent->eventwhencalled) - manager_event(EVENT_FLAG_AGENT, "AgentComplete", - "Queue: %s\r\n" - "Uniqueid: %s\r\n" - "Channel: %s\r\n" - "Member: %s\r\n" - "MemberName: %s\r\n" - "HoldTime: %ld\r\n" - "TalkTime: %ld\r\n" - "Reason: caller\r\n" - "%s", - queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, - (long)(callstart - qe->start), (long)(time(NULL) - callstart), - qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); - } else { - ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d", - (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); - if (qe->parent->eventwhencalled) - manager_event(EVENT_FLAG_AGENT, "AgentComplete", - "Queue: %s\r\n" - "Uniqueid: %s\r\n" - "Channel: %s\r\n" - "MemberName: %s\r\n" - "HoldTime: %ld\r\n" - "TalkTime: %ld\r\n" - "Reason: agent\r\n" - "%s", - queuename, qe->chan->uniqueid, peer->name, member->membername, (long)(callstart - qe->start), - (long)(time(NULL) - callstart), - qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); - } - if ((tds = ast_channel_datastore_find(qe->chan, &queue_transfer_info, NULL))) { - ast_channel_datastore_remove(qe->chan, tds); - } - update_queue(qe->parent, member, callcompletedinsl); + if (strcasecmp(oldcontext, qe->chan->context) || strcasecmp(oldexten, qe->chan->exten)) { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "TRANSFER", "%s|%s|%ld|%ld", + qe->chan->exten, qe->chan->context, (long) (callstart - qe->start), + (long) (time(NULL) - callstart)); + } else if (qe->chan->_softhangup) { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETECALLER", "%ld|%ld|%d", + (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); + if (qe->parent->eventwhencalled) + manager_event(EVENT_FLAG_AGENT, "AgentComplete", + "Queue: %s\r\n" + "Uniqueid: %s\r\n" + "Channel: %s\r\n" + "Member: %s\r\n" + "MemberName: %s\r\n" + "HoldTime: %ld\r\n" + "TalkTime: %ld\r\n" + "Reason: caller\r\n" + "%s", + queuename, qe->chan->uniqueid, peer->name, member->interface, member->membername, + (long)(callstart - qe->start), (long)(time(NULL) - callstart), + qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); + } else { + ast_queue_log(queuename, qe->chan->uniqueid, member->membername, "COMPLETEAGENT", "%ld|%ld|%d", + (long) (callstart - qe->start), (long) (time(NULL) - callstart), qe->opos); + if (qe->parent->eventwhencalled) + manager_event(EVENT_FLAG_AGENT, "AgentComplete", + "Queue: %s\r\n" + "Uniqueid: %s\r\n" + "Channel: %s\r\n" + "MemberName: %s\r\n" + "HoldTime: %ld\r\n" + "TalkTime: %ld\r\n" + "Reason: agent\r\n" + "%s", + queuename, qe->chan->uniqueid, peer->name, member->membername, (long)(callstart - qe->start), + (long)(time(NULL) - callstart), + qe->parent->eventwhencalled == QUEUE_EVENT_VARIABLES ? vars2manager(qe->chan, vars, sizeof(vars)) : ""); } - if (transfer_ds) { - ast_channel_datastore_free(transfer_ds); - } - ast_channel_unlock(qe->chan); - ast_hangup(peer); + if (bridge != AST_PBX_NO_HANGUP_PEER) + ast_hangup(peer); + update_queue(qe->parent, member, callcompletedinsl); res = bridge ? bridge : 1; ao2_ref(member, -1); } @@ -3453,7 +3449,7 @@ /* Reload dynamic queue members persisted into the astdb */ static void reload_queue_members(void) { - char *cur_ptr; + char *cur_ptr; char *queue_name; char *member; char *interface; @@ -3471,10 +3467,10 @@ /* Each key in 'pm_family' is the name of a queue */ db_tree = ast_db_gettree(pm_family, NULL); - for (entry = db_tree; entry; entry = entry->next) { - - queue_name = entry->key + strlen(pm_family) + 2; - + //for (entry = db_tree; entry; entry = entry->next) { + for (entry = db_tree; entry; entry = AST_LIST_NEXT(entry, list)) { + //queue_name = entry->key + strlen(pm_family) + 2; + queue_name = entry->key; AST_LIST_TRAVERSE(&queues, cur_queue, list) { ast_mutex_lock(&cur_queue->lock); if (!strcmp(queue_name, cur_queue->name)) @@ -3590,7 +3586,8 @@ } ast_module_user_remove(lu); pbx_builtin_setvar_helper(chan, "PQMSTATUS", "NOTFOUND"); - return 0; + // return -1; + return 0; // We want to be able to process PQMSTATUS == NOTFOUND in extensions.conf/ael. } ast_module_user_remove(lu); @@ -3643,7 +3640,8 @@ } ast_module_user_remove(lu); pbx_builtin_setvar_helper(chan, "UPQMSTATUS", "NOTFOUND"); - return 0; + // return -1; + return 0; // We want to be able to process UPQMSTATUS == NOTFOUND in extensions.conf/ael. } ast_module_user_remove(lu); @@ -3884,7 +3882,7 @@ qe.start = time(NULL); /* set the expire time based on the supplied timeout; */ - if (!ast_strlen_zero(args.queuetimeoutstr)) + if (args.queuetimeoutstr) qe.expire = qe.start + atoi(args.queuetimeoutstr); else qe.expire = 0; @@ -3963,7 +3961,7 @@ enum queue_member_status stat; /* Leave if we have exceeded our queuetimeout */ - if (qe.expire && (time(NULL) >= qe.expire)) { + if (qe.expire && (time(NULL) > qe.expire)) { record_abandoned(&qe); reason = QUEUE_TIMEOUT; res = 0; @@ -3980,27 +3978,11 @@ } makeannouncement = 1; - /* Leave if we have exceeded our queuetimeout */ - if (qe.expire && (time(NULL) >= qe.expire)) { - record_abandoned(&qe); - reason = QUEUE_TIMEOUT; - res = 0; - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); - break; - } /* Make a periodic announcement, if enabled */ if (qe.parent->periodicannouncefrequency && !ringing) if ((res = say_periodic_announcement(&qe))) goto stop; - /* Leave if we have exceeded our queuetimeout */ - if (qe.expire && (time(NULL) >= qe.expire)) { - record_abandoned(&qe); - reason = QUEUE_TIMEOUT; - res = 0; - ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITWITHTIMEOUT", "%d", qe.pos); - break; - } /* Try calling all queue members for 'timeout' seconds */ res = try_calling(&qe, args.options, args.announceoverride, args.url, &tries, &noption, args.agi); if (res) @@ -4029,16 +4011,22 @@ } /* leave the queue if no reachable agents, if enabled */ - if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { + if ((qe.parent->leavewhenempty == QUEUE_EMPTY_STRICT) && (stat == QUEUE_NO_REACHABLE_MEMBERS || stat == QUEUE_NO_UNPAUSED_REACHABLE_MEMBERS)) { record_abandoned(&qe); reason = QUEUE_LEAVEUNAVAIL; ast_queue_log(args.queuename, chan->uniqueid, "NONE", "EXITEMPTY", "%d|%d|%ld", qe.pos, qe.opos, (long)(time(NULL) - qe.start)); res = 0; break; } + if ((qe.parent->leavewhenempty == QUEUE_EMPTY_LOOSE) && (stat == QUEUE_NO_REACHABLE_MEMBERS)) { + record_abandoned(&qe); + reason = QUEUE_LEAVEUNAVAIL; + res = 0; + break; + } /* Leave if we have exceeded our queuetimeout */ - if (qe.expire && (time(NULL) >= qe.expire)) { + if (qe.expire && (time(NULL) > qe.expire)) { record_abandoned(&qe); reason = QUEUE_TIMEOUT; res = 0; @@ -4054,6 +4042,7 @@ if (res) goto stop; + /* Since this is a priority queue and * it is not sure that we are still at the head * of the queue, go and check for our turn again. @@ -4083,7 +4072,7 @@ } /* Don't allow return code > 0 */ - if (res >= 0) { + if (res >= 0 && res != AST_PBX_KEEPALIVE) { res = 0; if (ringing) { ast_indicate(chan, -1); @@ -4324,9 +4313,13 @@ if ((general_val = ast_variable_retrieve(cfg, "general", "autofill"))) autofill_default = ast_true(general_val); montype_default = 0; - if ((general_val = ast_variable_retrieve(cfg, "general", "monitor-type"))) + if ((general_val = ast_variable_retrieve(cfg, "general", "monitor-type"))) { if (!strcasecmp(general_val, "mixmonitor")) montype_default = 1; + } + shared_lastcall = 0; + if ((general_val = ast_variable_retrieve(cfg, "general", "shared_lastcall"))) + shared_lastcall = ast_true(general_val); } else { /* Define queue */ /* Look for an existing one */ AST_LIST_TRAVERSE(&queues, q, list) { @@ -4464,7 +4457,7 @@ struct member *mem; int pos, queue_show; time_t now; - char max_buf[150]; + char max_buf[80]; char *max; size_t max_left; float sl = 0; @@ -4480,18 +4473,8 @@ return RESULT_SHOWUSAGE; /* We only want to load realtime queues when a specific queue is asked for. */ - if (queue_show) { + if (queue_show) load_realtime_queue(argv[2]); - } else if (ast_check_realtime("queues")) { - struct ast_config *cfg = ast_load_realtime_multientry("queues", "name LIKE", "%", (char *) NULL); - char *queuename; - if (cfg) { - for (queuename = ast_category_browse(cfg, NULL); !ast_strlen_zero(queuename); queuename = ast_category_browse(cfg, queuename)) { - load_realtime_queue(queuename); - } - ast_config_destroy(cfg); - } - } AST_LIST_LOCK(&queues); if (AST_LIST_EMPTY(&queues)) { @@ -4548,9 +4531,6 @@ max_buf[0] = '\0'; max = max_buf; max_left = sizeof(max_buf); - if (strcasecmp(mem->membername, mem->interface)) { - ast_build_string(&max, &max_left, " (%s)", mem->interface); - } if (mem->penalty) ast_build_string(&max, &max_left, " with penalty %d", mem->penalty); if (mem->dynamic) @@ -4997,9 +4977,9 @@ if (++which > state) { char *tmp; ast_mutex_unlock(&q->lock); - tmp = ast_strdup(m->interface); + tmp = m->membername; ao2_ref(m, -1); - return tmp; + return ast_strdup(tmp); } ao2_ref(m, -1); } @@ -5069,6 +5049,7 @@ ast_cli_unregister_multiple(cli_queue, sizeof(cli_queue) / sizeof(struct ast_cli_entry)); res = ast_manager_unregister("QueueStatus"); res |= ast_manager_unregister("Queues"); + res |= ast_manager_unregister("QueueStatus"); res |= ast_manager_unregister("QueueAdd"); res |= ast_manager_unregister("QueueRemove"); res |= ast_manager_unregister("QueuePause"); @@ -5077,6 +5058,7 @@ res |= ast_unregister_application(app_pqm); res |= ast_unregister_application(app_upqm); res |= ast_unregister_application(app_ql); + res |= ast_unregister_application(app_ulc); res |= ast_unregister_application(app); res |= ast_custom_function_unregister(&queueagentcount_function); res |= ast_custom_function_unregister(&queuemembercount_function); @@ -5112,6 +5094,7 @@ res |= ast_register_application(app_pqm, pqm_exec, app_pqm_synopsis, app_pqm_descrip); res |= ast_register_application(app_upqm, upqm_exec, app_upqm_synopsis, app_upqm_descrip); res |= ast_register_application(app_ql, ql_exec, app_ql_synopsis, app_ql_descrip); + res |= ast_register_application(app_ulc, ulc_exec, app_ulc_synopsis, app_ulc_descrip); res |= ast_manager_register("Queues", 0, manager_queues_show, "Queues"); res |= ast_manager_register("QueueStatus", 0, manager_queues_status, "Queue Status"); res |= ast_manager_register("QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member, "Add interface to queue."); @@ -5129,6 +5112,8 @@ static int reload(void) { reload_queues(); + reload_queue_members(); + return 0; }