diff --git a/CHANGES b/CHANGES index 1dfadcbf7e..2dcc9b5e2d 100644 --- a/CHANGES +++ b/CHANGES @@ -439,6 +439,15 @@ Queue changes supports sending the event arguments to 5 individual fields, although it will fallback to the previous data definition, if the new table layout is not found. + * Added general option negative_penalty_invalid default off. when set + members are seen as invalid/logged out when there penalty is negative. + for realtime members when set remove from queue will set penalty to -1. + * Added queue option autopausedelay when autopause is enabled it will be + delayed for this number of seconds since last successful call if there + was no prior call the agent will be autopaused immediately. + * Added member option ignorebusy this when set and ringinuse is not + will allow per member control of multiple calls as ringinuse does for + the Queue. mISDN channel driver (chan_misdn) changes ---------------------------------------- diff --git a/UPGRADE.txt b/UPGRADE.txt index 3525621d39..dfa85684ec 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -50,5 +50,9 @@ pbx_lua: - the autoservice now defaults to being on by default - autoservice_start() and autoservice_start() no longer return a value. +Queue: + - Mark QUEUE_MEMBER_PENALTY Deprecated it never worked for realtime members + - QUEUE_MEMBER is now R/W supporting setting paused, ignorebusy and penalty. + =========================================================== =========================================================== diff --git a/apps/app_queue.c b/apps/app_queue.c index be929c1dcd..b9beafdd54 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -521,11 +521,25 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Returns the total number of members for the specified queue. + + Gets or sets queue member penalty. + + + Gets or sets queue member paused status. + + + Gets or sets queue member ignorebusy. + + - Returns the number of members currently associated with the specified queuename. + Allows access to queue counts [R] and member information [R/W]. + + queuename is required for all operations + interface is required for all member operations. + Queue @@ -658,6 +672,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Gets or sets queue members penalty. + This function has been deprecated in favor of the QUEUE_MEMBER() function Queue @@ -934,6 +949,9 @@ static struct ast_event_sub *device_state_sub; /*! \brief queues.conf [general] option */ static int update_cdr = 0; +/*! \brief queues.conf [general] option */ +static int negative_penalty_invalid = 0; + enum queue_result { QUEUE_UNKNOWN = 0, QUEUE_TIMEOUT = 1, @@ -1043,6 +1061,7 @@ struct member { unsigned int dead:1; /*!< Used to detect members deleted in realtime */ unsigned int delme:1; /*!< Flag to delete entry on reload */ char rt_uniqueid[80]; /*!< Unique id of realtime member entry */ + unsigned int ignorebusy:1; /*!< Flag to ignore member if the status is not available */ }; enum empty_conditions { @@ -1160,6 +1179,7 @@ struct call_queue { int timeout; /*!< How long to wait for an answer */ int weight; /*!< Respective weight */ int autopause; /*!< Auto pause queue members if they fail to answer */ + int autopausedelay; /*!< Delay auto pause for autopausedelay seconds since last call */ int timeoutpriority; /*!< Do we allow a fraction of the timeout to occur for a ring? */ /* Queue strategy things */ @@ -1190,9 +1210,10 @@ static AST_LIST_HEAD_STATIC(rule_lists, rule_list); static struct ao2_container *queues; static void update_realtime_members(struct call_queue *q); +static struct member *interface_exists(struct call_queue *q, const char *interface); static int set_member_paused(const char *queuename, const char *interface, const char *reason, int paused); -static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); +static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan); /*! \brief sets the QUEUESTATUS channel variable */ static void set_queue_result(struct ast_channel *chan, enum queue_result res) { @@ -1698,6 +1719,7 @@ static void init_queue(struct call_queue *q) q->numperiodicannounce = 0; q->autopause = QUEUE_AUTOPAUSE_OFF; q->timeoutpriority = TIMEOUT_PRIORITY_APP; + q->autopausedelay = 0; if (!q->members) { if (q->strategy == QUEUE_STRATEGY_LINEAR || q->strategy == QUEUE_STRATEGY_RRORDERED) /* linear strategy depends on order, so we have to place all members in a single bucket */ @@ -2003,6 +2025,8 @@ static void queue_set_param(struct call_queue *q, const char *param, const char q->montype = 1; } else if (!strcasecmp(param, "autopause")) { q->autopause = autopause2int(val); + } else if (!strcasecmp(param, "autopausedelay")) { + q->autopausedelay = atoi(val); } else if (!strcasecmp(param, "maxlen")) { q->maxlen = atoi(val); if (q->maxlen < 0) @@ -2081,7 +2105,9 @@ static void rt_handle_member_record(struct call_queue *q, char *interface, struc int penalty = 0; int paused = 0; int found = 0; + int ignorebusy = 0; + const char *config_val; 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); @@ -2095,8 +2121,11 @@ static void rt_handle_member_record(struct call_queue *q, char *interface, struc if (penalty_str) { penalty = atoi(penalty_str); - if (penalty < 0) + if ((penalty < 0) && negative_penalty_invalid) { + return; + } else if (penalty < 0) { penalty = 0; + } } if (paused_str) { @@ -2105,31 +2134,39 @@ static void rt_handle_member_record(struct call_queue *q, char *interface, struc paused = 0; } - /* 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 (paused_str) - m->paused = paused; - if (strcasecmp(state_interface, m->state_interface)) { - ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface)); - } - m->penalty = penalty; - found = 1; - ao2_ref(m, -1); - break; - } - ao2_ref(m, -1); - } + if ((config_val = ast_variable_retrieve(member_config, interface, "ignorebusy"))) { + ignorebusy = ast_true(config_val); + } else { + ignorebusy = 1; + } + + /* 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 (paused_str) + m->paused = paused; + if (strcasecmp(state_interface, m->state_interface)) { + ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface)); + } + m->penalty = penalty; + m->ignorebusy = ignorebusy; + found = 1; + ao2_ref(m, -1); + break; + } + ao2_ref(m, -1); + } ao2_iterator_destroy(&mem_iter); - /* Create a new member */ - if (!found) { + /* Create a new member */ + if (!found) { if ((m = create_queue_member(interface, membername, penalty, paused, state_interface))) { m->dead = 0; m->realtime = 1; + m->ignorebusy = ignorebusy; ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid)); ast_queue_log(q->name, "REALTIME", m->interface, "ADDMEMBER", "%s", ""); ao2_link(q->members, m); @@ -2873,16 +2910,24 @@ static int num_available_members(struct call_queue *q) 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) + 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 ((!q->ringinuse) || (!mem->ignorebusy)) { + break; + } + /* else fall through */ + case AST_DEVICE_NOT_INUSE: + case AST_DEVICE_UNKNOWN: + if (!mem->paused) { + avl++; + } break; - /* else fall through */ - case AST_DEVICE_NOT_INUSE: - case AST_DEVICE_UNKNOWN: - if (!mem->paused) { - avl++; - } - break; } ao2_ref(mem, -1); @@ -3010,38 +3055,54 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies char tech[256]; char *location; const char *macrocontext, *macroexten; + enum ast_device_state newstate; /* on entry here, we know that tmp->chan == NULL */ - 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))) { - ast_debug(1, "Wrapuptime not yet expired on queue %s for %s\n", - (tmp->lastqueue ? tmp->lastqueue->name : qe->parent->name), tmp->interface); - if (qe->chan->cdr) + if (tmp->member->paused) { + ast_debug(1, "%s paused, can't receive call\n", tmp->interface); + if (qe->chan->cdr) { ast_cdr_busy(qe->chan->cdr); + } tmp->stillgoing = 0; - (*busies)++; return 0; } - if (!qe->parent->ringinuse && (tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) { - ast_debug(1, "%s in use, can't receive call\n", tmp->interface); - if (qe->chan->cdr) + 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))) { + ast_debug(1, "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; + (*busies)++; return 0; } - if (tmp->member->paused) { - ast_debug(1, "%s paused, can't receive call\n", tmp->interface); - if (qe->chan->cdr) - ast_cdr_busy(qe->chan->cdr); - tmp->stillgoing = 0; - return 0; + if (!qe->parent->ringinuse || !tmp->member->ignorebusy) { + if ((tmp->member->status == AST_DEVICE_UNKNOWN) || (tmp->member->status == AST_DEVICE_NOT_INUSE)) { + newstate = ast_parse_device_state(tmp->member->interface); + if (newstate != tmp->member->status) { + ast_log(LOG_ERROR, "Found a channel matching iterface %s while status was %i changed to %i\n", + tmp->member->interface, tmp->member->status, newstate); + update_status(qe->parent, tmp->member, newstate); + } + } + if ((tmp->member->status != AST_DEVICE_NOT_INUSE) && (tmp->member->status != AST_DEVICE_UNKNOWN)) { + ast_debug(1, "%s in use, can't receive call\n", tmp->interface); + if (qe->chan->cdr) { + ast_cdr_busy(qe->chan->cdr); + } + tmp->stillgoing = 0; + return 0; + } } + if (use_weight && compare_weight(qe->parent,tmp->member)) { ast_debug(1, "Priority queue delaying call to %s:%s\n", qe->parent->name, tmp->interface); - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_cdr_busy(qe->chan->cdr); + } tmp->stillgoing = 0; (*busies)++; return 0; @@ -3056,8 +3117,9 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies /* Request the peer */ tmp->chan = ast_request(tech, qe->chan->nativeformats, qe->chan, location, &status); if (!tmp->chan) { /* If we can't, just go on to the next call */ - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_cdr_busy(qe->chan->cdr); + } tmp->stillgoing = 0; ao2_lock(qe->parent); @@ -3396,6 +3458,18 @@ static void rna(int rnatime, struct queue_ent *qe, char *interface, char *member } ast_queue_log(qe->parent->name, qe->chan->uniqueid, membername, "RINGNOANSWER", "%d", rnatime); if (qe->parent->autopause != QUEUE_AUTOPAUSE_OFF && pause) { + if (qe->parent->autopausedelay > 0) { + struct member *mem; + ao2_lock(qe->parent); + if ((mem = interface_exists(qe->parent, interface))) { + time_t idletime = time(&idletime)-mem->lastcall; + if ((mem->lastcall != 0) && (qe->parent->autopausedelay > idletime)) { + ao2_unlock(qe->parent); + return; + } + } + ao2_unlock(qe->parent); + } if (qe->parent->autopause == QUEUE_AUTOPAUSE_ON) { if (!set_member_paused(qe->parent->name, interface, "Auto-Pause", 1)) { ast_verb(3, "Auto-Pausing Queue Member %s in queue %s since they failed to answer.\n", @@ -4707,8 +4781,9 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce else ast_moh_stop(qe->chan); /* If appropriate, log that we have a destination channel */ - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_cdr_setdestchan(qe->chan->cdr, peer->name); + } /* Make sure channels are compatible */ res = ast_channel_make_compatible(qe->chan, peer); if (res < 0) { @@ -4788,10 +4863,11 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce if (mixmonapp) { ast_debug(1, "Starting MixMonitor as requested.\n"); if (!monitorfilename) { - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_copy_string(tmpid, qe->chan->cdr->uniqueid, sizeof(tmpid)); - else + } else { snprintf(tmpid, sizeof(tmpid), "chan-%lx", ast_random()); + } } else { const char *m = monitorfilename; for (p = tmpid2; p < tmpid2 + sizeof(tmpid2) - 1; p++, m++) { @@ -4858,12 +4934,13 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce ast_debug(1, "Arguments being passed to MixMonitor: %s\n", mixmonargs); /* We purposely lock the CDR so that pbx_exec does not update the application data */ - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_set_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED); + } pbx_exec(qe->chan, mixmonapp, mixmonargs); - if (qe->chan->cdr) + if (qe->chan->cdr) { ast_clear_flag(qe->chan->cdr, AST_CDR_FLAG_LOCKED); - + } } else { ast_log(LOG_WARNING, "Asked to run MixMonitor on this call, but cannot find the MixMonitor app!\n"); } @@ -5180,7 +5257,10 @@ static int remove_from_queue(const char *queuename, const char *interface) ao2_lock(q); if ((mem = ao2_find(q->members, &tmpmem, OBJ_POINTER))) { /* XXX future changes should beware of this assumption!! */ - if (!mem->dynamic) { + /*Change Penalty on realtime users*/ + if (mem->realtime && !ast_strlen_zero(mem->rt_uniqueid) && negative_penalty_invalid) { + update_realtime_member_field(mem, q->name, "penalty", "-1"); + } else if (!mem->dynamic) { ao2_ref(mem, -1); ao2_unlock(q); queue_t_unref(q, "Interface wasn't dynamic, expiring temporary reference"); @@ -5355,35 +5435,34 @@ static int set_member_penalty(const char *queuename, const char *interface, int int foundinterface = 0, foundqueue = 0; struct call_queue *q; struct member *mem; - struct ao2_iterator queue_iter; + char rtpenalty[80]; - if (penalty < 0) { + if (penalty < 0 && !negative_penalty_invalid) { ast_log(LOG_ERROR, "Invalid penalty (%d)\n", penalty); return RESULT_FAILURE; } - queue_iter = ao2_iterator_init(queues, 0); - while ((q = ao2_t_iterator_next(&queue_iter, "Iterate through queues"))) { + if ((q = load_realtime_queue(queuename))) { + foundqueue++; ao2_lock(q); - if (ast_strlen_zero(queuename) || !strcasecmp(q->name, queuename)) { - foundqueue++; - if ((mem = interface_exists(q, interface))) { - foundinterface++; + if ((mem = interface_exists(q, interface))) { + foundinterface++; + if (!mem->realtime) { mem->penalty = penalty; - - ast_queue_log(q->name, "NONE", interface, "PENALTY", "%d", penalty); - manager_event(EVENT_FLAG_AGENT, "QueueMemberPenalty", - "Queue: %s\r\n" - "Location: %s\r\n" - "Penalty: %d\r\n", - q->name, mem->interface, penalty); - ao2_ref(mem, -1); + } else { + sprintf(rtpenalty,"%i", penalty); + update_realtime_member_field(mem, q->name, "penalty", rtpenalty); } + ast_queue_log(q->name, "NONE", interface, "PENALTY", "%d", penalty); + manager_event(EVENT_FLAG_AGENT, "QueueMemberPenalty", + "Queue: %s\r\n" + "Location: %s\r\n" + "Penalty: %d\r\n", + q->name, mem->interface, penalty); + ao2_ref(mem, -1); } ao2_unlock(q); - queue_t_unref(q, "Done with iterator"); } - ao2_iterator_destroy(&queue_iter); if (foundinterface) { return RESULT_SUCCESS; @@ -6157,31 +6236,37 @@ static int queue_function_exists(struct ast_channel *chan, const char *cmd, char return 0; } -/*! +/*! * \brief Get number either busy / free / ready or total members of a specific queue - * \retval number of members (busy / free / ready / total) + * \brief Get or set member properties penalty / paused / ignorebusy + * \retval number of members (busy / free / ready / total) or member info (penalty / paused / ignorebusy) * \retval -1 on error */ -static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +static int queue_function_mem_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { int count = 0; struct member *m; struct ao2_iterator mem_iter; struct call_queue *q; - char *option; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(option); + AST_APP_ARG(interface); + ); + /* Make sure the returned value on error is zero length string. */ + buf[0] = '\0'; if (ast_strlen_zero(data)) { ast_log(LOG_ERROR, "%s requires an argument: queuename\n", cmd); return -1; } - if ((option = strchr(data, ','))) - *option++ = '\0'; - else - option = "logged"; - if ((q = load_realtime_queue(data))) { + AST_STANDARD_APP_ARGS(args, data); + + if ((q = load_realtime_queue(args.queuename))) { ao2_lock(q); - if (!strcasecmp(option, "logged")) { + if (!strcasecmp(args.option, "logged")) { mem_iter = ao2_iterator_init(q->members, 0); while ((m = ao2_iterator_next(&mem_iter))) { /* Count the agents who are logged in and presently answering calls */ @@ -6191,7 +6276,7 @@ static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *d ao2_ref(m, -1); } ao2_iterator_destroy(&mem_iter); - } else if (!strcasecmp(option, "free")) { + } else if (!strcasecmp(args.option, "free")) { mem_iter = ao2_iterator_init(q->members, 0); while ((m = ao2_iterator_next(&mem_iter))) { /* Count the agents who are logged in and presently answering calls */ @@ -6201,7 +6286,7 @@ static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *d ao2_ref(m, -1); } ao2_iterator_destroy(&mem_iter); - } else if (!strcasecmp(option, "ready")) { + } else if (!strcasecmp(args.option, "ready")) { time_t now; time(&now); mem_iter = ao2_iterator_init(q->members, 0); @@ -6214,22 +6299,104 @@ static int queue_function_qac(struct ast_channel *chan, const char *cmd, char *d ao2_ref(m, -1); } ao2_iterator_destroy(&mem_iter); - } else /* must be "count" */ + } else if (!strcasecmp(args.option, "count") || ast_strlen_zero(args.option)) { count = q->membercount; + } else if (!strcasecmp(args.option, "penalty") && !ast_strlen_zero(args.interface) && + ((m = interface_exists(q, args.interface)))) { + count = m->penalty; + } else if (!strcasecmp(args.option, "paused") && !ast_strlen_zero(args.interface) && + ((m = interface_exists(q, args.interface)))) { + count = m->paused; + } else if (!strcasecmp(args.option, "ignorebusy") && !ast_strlen_zero(args.interface) && + ((m = interface_exists(q, args.interface)))) { + count = m->ignorebusy; + } ao2_unlock(q); queue_t_unref(q, "Done with temporary reference in QUEUE_MEMBER()"); - } else - ast_log(LOG_WARNING, "queue %s was not found\n", data); + } else { + ast_log(LOG_WARNING, "queue %s was not found\n", args.queuename); + } snprintf(buf, len, "%d", count); return 0; } -/*! +/*! \brief Dialplan function QUEUE_MEMBER() Sets the members penalty / paused / ignorebusy. */ +static int queue_function_mem_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + int memvalue; + struct call_queue *q; + struct member *m; + char rtvalue[80]; + + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(queuename); + AST_APP_ARG(option); + AST_APP_ARG(interface); + ); + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Missing argument. QUEUE_MEMBER(,