[svn-commits] irroot: branch irroot/app_queue_skill r322068 - /team/irroot/app_queue_skill/...
SVN commits to the Digium repositories
svn-commits at lists.digium.com
Mon Jun 6 12:44:23 CDT 2011
Author: irroot
Date: Mon Jun 6 12:44:19 2011
New Revision: 322068
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=322068
Log:
Initial RB Patch 1096
Modified:
team/irroot/app_queue_skill/apps/app_queue.c
Modified: team/irroot/app_queue_skill/apps/app_queue.c
URL: http://svnview.digium.com/svn/asterisk/team/irroot/app_queue_skill/apps/app_queue.c?view=diff&rev=322068&r1=322067&r2=322068
==============================================================================
--- team/irroot/app_queue_skill/apps/app_queue.c (original)
+++ team/irroot/app_queue_skill/apps/app_queue.c Mon Jun 6 12:44:19 2011
@@ -853,6 +853,8 @@
QUEUE_RELOAD_MEMBER = (1 << 1),
QUEUE_RELOAD_RULES = (1 << 2),
QUEUE_RESET_STATS = (1 << 3),
+ QUEUE_RELOAD_SKILLS = (1 << 4),
+ QUEUE_RELOAD_SKILL_RULES = (1 << 5),
};
static const struct strategy {
@@ -995,9 +997,14 @@
struct ast_aoc_decoded *aoc_s_rate_list;
};
+struct virtual_queue {
+ char id[80]; /*!< Argument 'ruleset' to the Queue() app. */
+ int holdtime; /*!< Estimated Waiting Time for this virtual queue. */
+};
struct queue_ent {
struct call_queue *parent; /*!< What queue is our parent */
+ struct virtual_queue *vqueue; /*!< Virtual queue in case there is skills routing */
char moh[80]; /*!< Name of musiconhold to be used */
char announce[PATH_MAX]; /*!< Announcement to play for member when call is answered */
char context[AST_MAX_CONTEXT]; /*!< Context when user exits queue */
@@ -1017,8 +1024,11 @@
int min_penalty; /*!< Limit the members that can take this call to this penalty or higher */
int linpos; /*!< If using linear strategy, what position are we at? */
int linwrapped; /*!< Is the linpos wrapped? */
+ char skill_ruleset[80]; /*!< Name of the skill ruleset */
+ time_t skills_next_check; /*!< Next check of skills rules. */
time_t start; /*!< When we started holding */
time_t expire; /*!< When this entry should expire (time out of queue) */
+ struct ao2_container *mem_selection;/*!< Members who match skill rules. */
int cancel_answered_elsewhere; /*!< Whether we should force the CAE flag on this call (C) option*/
struct ast_channel *chan; /*!< Our channel */
AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */
@@ -1026,18 +1036,102 @@
struct queue_ent *next; /*!< The next queue entry */
};
+enum skill_rule_operand_type {
+ SKILL_RULE_OPERAND_UNKNOWN,
+ SKILL_RULE_OPERAND_VARIABLE,
+ SKILL_RULE_OPERAND_VALUE,
+ SKILL_RULE_OPERAND_OPERATOR,
+};
+
+struct skill_rule_operand {
+ union {
+ char var[80];
+ int value;
+ struct skill_rule_operator* operator;
+ } u;
+ enum skill_rule_operand_type type;
+ AST_LIST_ENTRY(skill_rule_operand) entry;
+};
+
+enum skill_rule_operator_type {
+ SKILL_RULE_OPERATOR_UNKNOWN,
+ SKILL_RULE_OPERATOR_DIVISION, /*!< op1 / op2 */
+ SKILL_RULE_OPERATOR_MULTIPLICATION, /*!< op1 * op2 */
+ SKILL_RULE_OPERATOR_SUBTRACTION, /*!< op1 - op2 */
+ SKILL_RULE_OPERATOR_ADDITION, /*!< op1 + op2 */
+ SKILL_RULE_OPERATOR_NOTEQUAL, /*!< op1 ! op2 */
+ SKILL_RULE_OPERATOR_EQUAL, /*!< op1 = op2 */
+ SKILL_RULE_OPERATOR_GREATER, /*!< op1 > op2 */
+ SKILL_RULE_OPERATOR_LESSER, /*!< op1 < op2 */
+ SKILL_RULE_OPERATOR_AND, /*!< op1 & op2 */
+ SKILL_RULE_OPERATOR_OR /*!< op1 | op2 */
+};
+
+#define SKILL_RULE_OPERATORS_CHARS "/*-+!=><&|"
+static enum skill_rule_operator_type skill_rule_operator_type_str[] = {
+ ['/'] = SKILL_RULE_OPERATOR_DIVISION,
+ ['*'] = SKILL_RULE_OPERATOR_MULTIPLICATION,
+ ['-'] = SKILL_RULE_OPERATOR_SUBTRACTION,
+ ['+'] = SKILL_RULE_OPERATOR_ADDITION,
+ ['!'] = SKILL_RULE_OPERATOR_NOTEQUAL,
+ ['='] = SKILL_RULE_OPERATOR_EQUAL,
+ ['>'] = SKILL_RULE_OPERATOR_GREATER,
+ ['<'] = SKILL_RULE_OPERATOR_LESSER,
+ ['&'] = SKILL_RULE_OPERATOR_AND,
+ ['|'] = SKILL_RULE_OPERATOR_OR,
+};
+
+struct skill_rule_operator {
+ struct skill_rule_operator *parent;
+ AST_LIST_HEAD_NOLOCK(,skill_rule_operand) operands;
+ enum skill_rule_operator_type type;
+};
+
+struct skill_rule {
+ struct skill_rule_operator *dcond; /*!< Condition against dynamical variables */
+ struct skill_rule_operator *cond; /*!< Condition against skills */
+};
+
+struct skill_ruleset {
+ char name[80];
+ struct ao2_container *rules;
+ AST_LIST_ENTRY(skill_ruleset) entry;
+};
+
+static AST_LIST_HEAD_STATIC(skill_rulesets, skill_ruleset);
+
+struct rule_var {
+ char name[80];
+ char value[80];
+};
+
+struct skill {
+ char name[80]; /*!< Name of skill */
+ int weight; /*!< Weight */
+};
+
+struct skills_group {
+ char name[80];
+ struct ao2_container *skills; /*!< Head of the list of skills */
+ AST_LIST_ENTRY(skills_group) entry;
+};
+
+static AST_LIST_HEAD_STATIC(skills_groups, skills_group);
+
struct member {
char interface[80]; /*!< Technology/Location to dial to reach this member*/
char state_exten[AST_MAX_EXTENSION]; /*!< Extension to get state from (if using hint) */
char state_context[AST_MAX_CONTEXT]; /*!< Context to use when getting state (if using hint) */
char state_interface[80]; /*!< Technology/Location from which to read devicestate changes */
char membername[80]; /*!< Member name to use in queue logs */
+ char skills[80]; /*!< Member skills */
int penalty; /*!< Are we a last resort? */
int calls; /*!< Number of calls serviced by this member */
int dynamic; /*!< Are we dynamically added? */
int realtime; /*!< Is this member realtime? */
int status; /*!< Status of queue member */
int paused; /*!< Are we paused (not accepting calls)? */
+ int holdtime; /*!< Average holdtime. */
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 */
@@ -1167,6 +1261,7 @@
int memberdelay; /*!< Seconds to delay connecting member to caller */
int autofill; /*!< Ignore the head call status and ring an available agent */
+ struct ao2_container *vqueues; /*!< Virtual queues */
struct ao2_container *members; /*!< Head of the list of members */
/*!
* \brief Number of members _logged in_
@@ -1193,6 +1288,9 @@
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 int update_queue_ent_skills_next_check(struct call_queue *q);
+static int member_is_selected(struct queue_ent *qe, struct member *mem);
+
/*! \brief sets the QUEUESTATUS channel variable */
static void set_queue_result(struct ast_channel *chan, enum queue_result res)
{
@@ -1344,7 +1442,7 @@
* is available, the function immediately returns 0. If no members are available,
* then -1 is returned.
*/
-static int get_member_status(struct call_queue *q, int max_penalty, int min_penalty, enum empty_conditions conditions)
+static int get_member_status(struct call_queue *q, int max_penalty, int min_penalty, enum empty_conditions conditions, struct queue_ent* qe)
{
struct member *member;
struct ao2_iterator mem_iter;
@@ -1357,6 +1455,11 @@
ast_debug(4, "%s is unavailable because his penalty is not between %d and %d\n", member->membername, min_penalty, max_penalty);
continue;
}
+ }
+
+ if (qe && !member_is_selected(qe, member)) {
+ ast_debug(4, "%s is unavailable because it is not selected by rule '%s'\n", member->membername, qe->skill_ruleset);
+ continue;
}
switch (member->status) {
@@ -1432,6 +1535,7 @@
if (q->maskmemberstatus)
return 0;
+ update_queue_ent_skills_next_check(q);
manager_event(EVENT_FLAG_AGENT, "QueueMemberStatus",
"Queue: %s\r\n"
"Location: %s\r\n"
@@ -1441,9 +1545,10 @@
"CallsTaken: %d\r\n"
"LastCall: %d\r\n"
"Status: %d\r\n"
- "Paused: %d\r\n",
+ "Paused: %d\r\n"
+ "Skills: %s\r\n",
q->name, m->interface, m->membername, m->dynamic ? "dynamic" : m->realtime ? "realtime" : "static",
- m->penalty, m->calls, (int)m->lastcall, m->status, m->paused
+ m->penalty, m->calls, (int)m->lastcall, m->status, m->paused, m->skills
);
return 0;
@@ -1596,7 +1701,7 @@
}
/*! \brief allocate space for new queue member and set fields based on parameters passed */
-static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused, const char *state_interface)
+static struct member *create_queue_member(const char *interface, const char *membername, int penalty, int paused, const char *state_interface, const char *skills)
{
struct member *cur;
@@ -1622,6 +1727,10 @@
ast_copy_string(cur->state_context, S_OR(context, "default"), sizeof(cur->state_context));
}
cur->status = get_queue_member_status(cur);
+ if (!ast_strlen_zero(skills))
+ ast_copy_string(cur->skills, skills, sizeof(cur->skills));
+ else
+ cur->skills[0] = '\0';
}
return cur;
@@ -2074,7 +2183,7 @@
* Search for member in queue, if found update penalty/paused state,
* if no member exists create one flag it as a RT member and add to queue member list.
*/
-static void rt_handle_member_record(struct call_queue *q, char *interface, const char *rt_uniqueid, const char *membername, const char *penalty_str, const char *paused_str, const char* state_interface)
+static void rt_handle_member_record(struct call_queue *q, char *interface, const char *rt_uniqueid, const char *membername, const char *penalty_str, const char *paused_str, const char* state_interface, const char *skills)
{
struct member *m;
struct ao2_iterator mem_iter;
@@ -2111,8 +2220,13 @@
ast_copy_string(m->state_interface, state_interface, sizeof(m->state_interface));
}
m->penalty = penalty;
+ if (!ast_strlen_zero(skills))
+ ast_copy_string(m->skills, skills, sizeof(m->skills));
+ else
+ m->skills[0] = '\0';
found = 1;
ao2_ref(m, -1);
+ update_queue_ent_skills_next_check(q);
break;
}
ao2_ref(m, -1);
@@ -2121,9 +2235,10 @@
/* Create a new member */
if (!found) {
- if ((m = create_queue_member(interface, membername, penalty, paused, state_interface))) {
+ if ((m = create_queue_member(interface, membername, penalty, paused, state_interface, skills))) {
m->dead = 0;
m->realtime = 1;
++ update_queue_ent_skills_next_check(q);
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);
@@ -2164,6 +2279,10 @@
free(q->sound_periodicannounce[i]);
}
ao2_ref(q->members, -1);
+
+ if (q->vqueues) {
+ ao2_ref(q->vqueues, -1);
+ }
}
static struct call_queue *alloc_queue(const char *queuename)
@@ -2304,7 +2423,8 @@
S_OR(ast_variable_retrieve(member_config, interface, "membername"),interface),
ast_variable_retrieve(member_config, interface, "penalty"),
ast_variable_retrieve(member_config, interface, "paused"),
- S_OR(ast_variable_retrieve(member_config, interface, "state_interface"),interface));
+ S_OR(ast_variable_retrieve(member_config, interface, "state_interface"),interface),
+ ast_variable_retrieve(member_config, interface, "skills"));
}
/* Delete all realtime members that have been deleted in DB. */
@@ -2431,7 +2551,8 @@
S_OR(ast_variable_retrieve(member_config, interface, "membername"), interface),
ast_variable_retrieve(member_config, interface, "penalty"),
ast_variable_retrieve(member_config, interface, "paused"),
- S_OR(ast_variable_retrieve(member_config, interface, "state_interface"), interface));
+ S_OR(ast_variable_retrieve(member_config, interface, "state_interface"), interface),
+ ast_variable_retrieve(member_config, interface, "skills"));
}
/* Delete all realtime members that have been deleted in DB. */
@@ -2450,6 +2571,886 @@
ast_config_destroy(member_config);
}
+static void destroy_skills_group(void *obj)
+{
+ struct skills_group *skgrp = obj;
+ struct skill *cur;
+ struct ao2_iterator sk_iter = ao2_iterator_init(skgrp->skills, 0);
+
+ while ((cur = ao2_iterator_next(&sk_iter))) {
+ ao2_unlink(skgrp->skills, cur);
+ ao2_ref(cur, -1);
+ }
+ ao2_iterator_destroy(&sk_iter);
+ ao2_ref(skgrp->skills, -1);
+}
+
+static void destroy_operator(struct skill_rule_operator *op)
+{
+ struct skill_rule_operand *operand;
+
+ if (!op)
+ return;
+
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&op->operands, operand, entry) {
+ AST_LIST_REMOVE_CURRENT(entry);
+ if (operand->type == SKILL_RULE_OPERAND_OPERATOR)
+ destroy_operator(operand->u.operator);
+
+ ast_free(operand);
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ ast_free(op);
+}
+
+static void destroy_skill_rule(void* obj)
+{
+ struct skill_rule* r = obj;
+ if (r->dcond)
+ destroy_operator(r->dcond);
+ if (r->cond)
+ destroy_operator(r->cond);
+}
+
+static void destroy_skill_ruleset(void *obj)
+{
+ struct skill_ruleset *ruleset = obj;
+ struct skill_rule *cur;
+ struct ao2_iterator rule_iter = ao2_iterator_init(ruleset->rules, 0);
+
+ while ((cur = ao2_iterator_next(&rule_iter))) {
+ ao2_unlink(ruleset->rules, cur);
+ ao2_ref(cur, -1);
+ }
+ ao2_iterator_destroy(&rule_iter);
+ ao2_ref(ruleset->rules, -1);
+}
+
+static struct skill_rule_operator *create_skill_rule_operator(enum skill_rule_operator_type t, struct skill_rule_operator *parent)
+{
+ struct skill_rule_operator *op;
+ op = ast_calloc(1, sizeof(*op));
+ if (!op)
+ return NULL;
+
+ op->type = t;
+ AST_LIST_HEAD_INIT_NOLOCK(&op->operands);
+ op->parent = parent;
+
+ return op;
+}
+
+static struct skill_rule_operand *create_skill_rule_operand(enum skill_rule_operand_type t)
+{
+ struct skill_rule_operand *operand;
+ operand = ast_calloc(1, sizeof(*operand));
+ if (!operand)
+ return NULL;
+
+ operand->type = t;
+ return operand;
+}
+
+static char* display_operator(struct skill_rule_operator *op)
+{
+ struct skill_rule_operand *operand;
+ size_t len = 512;
+ char *str = malloc(len);
+ char *s = str;
+
+ *str = '\0';
+ AST_LIST_TRAVERSE(&op->operands, operand, entry) {
+ char t;
+ switch(op->type) {
+ case SKILL_RULE_OPERATOR_NOTEQUAL: t = '!'; break;
+ case SKILL_RULE_OPERATOR_EQUAL: t = '='; break;
+ case SKILL_RULE_OPERATOR_GREATER: t = '>'; break;
+ case SKILL_RULE_OPERATOR_LESSER: t = '<'; break;
+ case SKILL_RULE_OPERATOR_AND: t = '&'; break;
+ case SKILL_RULE_OPERATOR_OR: t = '|'; break;
+ default: t = '?'; break;
+ }
+
+ if (*str != '\0')
+ ast_build_string(&s, &len, "%c", t);
+
+ switch(operand->type) {
+ case SKILL_RULE_OPERAND_VARIABLE:
+ ast_build_string(&s, &len, "%s", operand->u.var);
+ break;
+ case SKILL_RULE_OPERAND_VALUE:
+ ast_build_string(&s, &len, "%d", operand->u.value);
+ break;
+ case SKILL_RULE_OPERAND_OPERATOR:
+ {
+ char *tmp = display_operator(operand->u.operator);
+ ast_build_string(&s, &len, "(%s)", tmp);
+ free(tmp);
+ break;
+ }
+ case SKILL_RULE_OPERAND_UNKNOWN:
+ ast_build_string(&s, &len, "<unknown>");
+ }
+ }
+ return str;
+}
+
+static struct skill_rule_operator* parse_expr(const char *expr)
+{
+ struct skill_rule_operator *op, *head;
+ struct skill_rule_operand *operand = NULL;
+ const char *ptr, *start = NULL;
+
+ op = create_skill_rule_operator(SKILL_RULE_OPERATOR_UNKNOWN, NULL);
+ if (!op)
+ return NULL;
+
+ head = op;
+ ptr = expr;
+ do {
+ if (start) {
+ /* currently parsing a variable name. */
+ if ((*ptr >= 'a' && *ptr <= 'z') ||
+ (*ptr >= 'A' && *ptr <= 'Z') ||
+ (*ptr >= '0' && *ptr <= '9') ||
+ (*ptr != '\0' && strchr("$-_", *ptr))) {
+ ++ptr;
+ continue;
+ }
+
+ if (operand) {
+ ast_log(LOG_ERROR, "Unable to parse rule: syntax error, missing operator.\n");
+ goto error;
+ }
+
+ operand = create_skill_rule_operand(SKILL_RULE_OPERAND_VARIABLE);
+ if (!operand) {
+ /* OOM */
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+ goto error;
+ }
+ ast_copy_string(operand->u.var, start, (ptr + 1 - start) > sizeof(operand->u.var)
+ ? sizeof(operand->u.var)
+ : (ptr + 1 - start));
+ start = NULL;
+ }
+ if ((*ptr >= 'a' && *ptr <= 'z') ||
+ (*ptr >= 'A' && *ptr <= 'Z') ||
+ *ptr == '$') {
+ /* starting to parse a variable name. */
+ start = ptr++;
+ continue;
+ }
+ if ((*ptr >= '0' && *ptr <= '9') || *ptr == '-') {
+ /* parsing an integer value. */
+ int value;
+ start = ptr;
+ errno = 0;
+ value = strtol(start, (char**)&ptr, 10);
+ if (start == ptr) {
+ /* no digits found */
+ ast_log(LOG_ERROR, "Unable to parse rule: syntax error, no-digits.\n");
+ goto error;
+ }
+ if ((errno == ERANGE && (value == LONG_MAX || value == LONG_MIN)) ||
+ (errno != 0 && value == 0)) {
+ /* error */
+ ast_log(LOG_ERROR, "Unable to parse rule: strtol error: %s.\n", strerror(errno));
+ goto error;
+ }
+
+ if (operand) {
+ /* WTF syn error */
+ ast_log(LOG_ERROR, "Unable to parse rule: syntax error, missing operator.\n");
+ goto error;
+ }
+ operand = create_skill_rule_operand(SKILL_RULE_OPERAND_VALUE);
+ if (!operand) {
+ /* OOM */
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+ goto error;
+ }
+ operand->u.value = value;
+ start = NULL;
+ continue;
+ }
+ if (*ptr == '(') {
+ struct skill_rule_operator *newop;
+ const char *end;
+ char *tmp;
+ unsigned count = 0;
+
+ if (operand) {
+ ast_log(LOG_ERROR, "Unable to parse rule: missing operator before '('\n");
+ goto error;
+ }
+
+ start = ++ptr;
+ end = ptr + strlen(ptr);
+
+ /* Look for the closing bracket. */
+ while (ptr < end && (count > 0 || *ptr != ')'))
+ switch(*ptr++) {
+ case '(': count++; break;
+ case ')': count--; break;
+ }
+
+ if (ptr == start) {
+ ast_log(LOG_ERROR, "Unable to parse rule: empty expression between ()\n");
+ goto error;
+ }
+ if (ptr == end) {
+ ast_log(LOG_ERROR, "Unable to parse rule: missing ')'\n");
+ goto error;
+ }
+
+ tmp = ast_strndup(start, ptr-start);
+ newop = parse_expr(tmp);
+ ast_free(tmp);
+
+ if (!newop) {
+ /* Something failed while parsing subexpr. Do
+ * not display any message as parse_expr()
+ * probably dit it.
+ */
+ goto error;
+ }
+
+ operand = create_skill_rule_operand(SKILL_RULE_OPERAND_OPERATOR);
+ if (!operand) {
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory\n");
+ ast_free(newop);
+ goto error;
+ }
+ operand->u.operator = newop;
+ start = NULL;
+ ++ptr;
+ }
+ /* if *ptr == '\0', strchr("...", *ptr) != NULL */
+ if (strchr(SKILL_RULE_OPERATORS_CHARS, *ptr)) {
+ /* operator */
+ enum skill_rule_operator_type flag = SKILL_RULE_OPERATOR_UNKNOWN;
+
+ if (!operand) {
+ /* syntax error */
+ ast_log(LOG_ERROR, "Unable to parse rule: syntax error, no operand before '%c'.\n", *ptr ? *ptr : ';');
+ goto error;
+ }
+
+ if (*ptr != '\0')
+ flag = skill_rule_operator_type_str[(size_t)*ptr];
+ else
+ flag = op->type;
+
+ if (op->type == SKILL_RULE_OPERATOR_UNKNOWN) {
+ if (flag == SKILL_RULE_OPERATOR_UNKNOWN) {
+ /* syntax error */
+ ast_log(LOG_ERROR, "Unable to parse rule: syntax error, no operator.\n");
+ goto error;
+ }
+ op->type = flag;
+ }
+
+ if (op->type < flag) {
+ /* last operator has a greater priority than current operator. */
+ struct skill_rule_operator *parent;
+
+ /* Firstly, add the operand in the current operator. */
+ AST_LIST_INSERT_TAIL(&op->operands, operand, entry);
+ operand = NULL;
+
+ /* Then we try to jump to an upper operator, or to create one. */
+
+ /* look for a parent operator with a lower or equal priority. */
+ for(parent = op->parent; parent && parent->type < flag; parent = parent->parent)
+ op = parent;
+
+ if (!parent) {
+ /* There isn't any other operator with a lower or equal priority */
+ parent = create_skill_rule_operator(flag, NULL);
+ if (!parent) {
+ /* OOM */
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+ goto error;
+ }
+
+ operand = create_skill_rule_operand(SKILL_RULE_OPERAND_OPERATOR);
+ if (!operand) {
+ /* OOM */
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+ ast_free(parent);
+ goto error;
+ }
+ operand->u.operator = op;
+
+ op->parent = parent;
+ AST_LIST_INSERT_TAIL(&parent->operands, operand, entry);
+
+ head = parent;
+
+ operand = NULL;
+ } else if (parent->type > flag) {
+ /* There is an operator with a greater priority, so we insert this
+ * operator between this one and his last child. */
+ struct skill_rule_operator *newop;
+ newop = create_skill_rule_operator(flag, parent);
+ if (!newop) {
+ /* OOM */
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+ goto error;
+ }
+
+ AST_LIST_TRAVERSE(&parent->operands, operand, entry) {
+ if (operand->type == SKILL_RULE_OPERAND_OPERATOR && operand->u.operator == op)
+ break;
+ }
+
+ if (!operand) {
+ /* WTF */
+ ast_free(newop);
+ ast_log(LOG_ERROR, "Unable to parse rule: internal error (unable to find operand).\n");
+ goto error;
+ }
+ op->parent = newop;
+
+ AST_LIST_REMOVE(&parent->operands, operand, entry);
+ AST_LIST_INSERT_TAIL(&newop->operands, operand, entry);
+ operand = NULL;
+
+ operand = create_skill_rule_operand(SKILL_RULE_OPERAND_OPERATOR);
+ if (!operand) {
+ /* OOM */
+ ast_free(newop);
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+ goto error;
+ }
+ operand->u.operator = newop;
+ AST_LIST_INSERT_TAIL(&parent->operands, operand, entry);
+
+ operand = NULL;
+
+ parent = newop;
+ }
+ op = parent;
+
+ } else if (op->type > flag) {
+ /* last operator has a lower priority than current operator. */
+ struct skill_rule_operator *newop;
+ newop = create_skill_rule_operator(flag, op);
+ if (!newop) {
+ /* OOM */
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+ goto error;
+ }
+
+ AST_LIST_INSERT_TAIL(&newop->operands, operand, entry);
+ operand = NULL;
+
+ operand = create_skill_rule_operand(SKILL_RULE_OPERAND_OPERATOR);
+ if (!operand) {
+ /* OOM */
+ ast_log(LOG_ERROR, "Unable to parse rule: out-of-memory.\n");
+ ast_free(newop);
+ goto error;
+ }
+ operand->u.operator = newop;
+ AST_LIST_INSERT_TAIL(&op->operands, operand, entry);
+ operand = NULL;
+
+ op = newop;
+ } else {
+ AST_LIST_INSERT_TAIL(&op->operands, operand, entry);
+ operand = NULL;
+ }
+ }
+
+ ++ptr;
+ } while (*(ptr-1));
+
+ return head;
+
+error:
+ destroy_operator(head);
+ if(operand)
+ ast_free(operand);
+ return NULL;
+}
+
+static int parse_skill_rule(struct skill_rule *r, const char *line)
+{
+ char* dcond = ast_strdupa(line);
+ char* cond;
+
+ cond = strchr(dcond, ',');
+ if (cond) {
+ *cond++ = '\0';
+ r->dcond = parse_expr(dcond);
+ }
+ else
+ cond = dcond;
+
+ r->cond = parse_expr(cond);
+ return 0;
+}
+
+static int operator_eval(struct skill_rule_operator *op, struct ao2_container *variables, struct ast_channel* chan,
+ int (*getvalue_fn) (const char* key, void* data), void* data,
+ void (*operator_proceeded_cb) (const char* left_name, int left_value, enum skill_rule_operator_type operator, const char* right_name, int right_value, void* data))
+{
+ struct skill_rule_operand *opnd = NULL;
+ int ret = 0;
+ const char* last_name = NULL;
+ int first = 1;
+
+ if (!op) {
+ ast_log(LOG_WARNING, "Rule is empty\n");
+ return 0;
+ }
+
+ AST_LIST_TRAVERSE(&op->operands, opnd, entry) {
+ const char *name = NULL;
+ int value = 0;
+ switch(opnd->type) {
+ case SKILL_RULE_OPERAND_VARIABLE:
+ {
+ struct rule_var *var = NULL;
+ name = opnd->u.var;
+
+ if (*name == '$') {
+ ++name;
+
+ /* This is a meta-variable, find the value in the variables list. */
+ if (variables != NULL) {
+ struct ao2_iterator variter = ao2_iterator_init(variables, 0);
+
+ while ((var = ao2_iterator_next(&variter)) && strcmp(name, var->name))
+ ao2_ref(var, -1);
+
+ if (var)
+ name = var->value;
+
+ ao2_iterator_destroy(&variter);
+ }
+
+ /* If doesn't found in variables list, try with env vars */
+ if (!var && chan != NULL)
+ name = pbx_builtin_getvar_helper(chan, name);
+ }
+
+ if (name) {
+ char *endptr = NULL;
+ int v = strtoul(name, &endptr, 10);
+
+ if (endptr == '\0') {
+ value = v; /* name is an integer, so that's the value. */
+ } else if (getvalue_fn) {
+ /* Use callback to get the value of this variable. */
+ value = getvalue_fn(name, data);
+ } else {
+ ast_log(LOG_ERROR, "There is no 'getvalue' callback defined");
+ }
+ }
+
+ if (var)
+ ao2_ref(var, -1);
+ break;
+ }
+ case SKILL_RULE_OPERAND_VALUE:
+ value = opnd->u.value;
+ break;
+ case SKILL_RULE_OPERAND_OPERATOR:
+ value = operator_eval(opnd->u.operator, variables, chan, getvalue_fn, data, operator_proceeded_cb);
+ break;
+ case SKILL_RULE_OPERAND_UNKNOWN:
+ /* WTF */
+ return 0;
+ }
+
+ if (first)
+ ret = value;
+ else {
+ if (operator_proceeded_cb)
+ operator_proceeded_cb(last_name, ret, op->type, name, value, data);
+
+ switch(op->type) {
+ case SKILL_RULE_OPERATOR_DIVISION:
+ if (value != 0)
+ ret /= value;
+ else {
+ ast_log(LOG_WARNING, "Rule error: division by zero.\n");
+ return 0;
+ }
+ break;
+ case SKILL_RULE_OPERATOR_MULTIPLICATION:
+ ret *= value;
+ break;
+ case SKILL_RULE_OPERATOR_SUBTRACTION:
+ ret -= value;
+ break;
+ case SKILL_RULE_OPERATOR_ADDITION:
+ ret += value;
+ break;
+ case SKILL_RULE_OPERATOR_NOTEQUAL:
+ ret = (ret != value);
+ break;
+ case SKILL_RULE_OPERATOR_EQUAL:
+ ret = (ret == value);
+ break;
+ case SKILL_RULE_OPERATOR_GREATER:
+ ret = (ret > value);
+ break;
+ case SKILL_RULE_OPERATOR_LESSER:
+ ret = (ret < value);
+ break;
+ case SKILL_RULE_OPERATOR_AND:
+ ret = (ret && value);
+ break;
+ case SKILL_RULE_OPERATOR_OR:
+ ret = (ret || value);
+ break;
+ case SKILL_RULE_OPERATOR_UNKNOWN:
+ /* WTF */
+ return 0;
+ }
+ }
+
+ first = 0;
+ last_name = name;
+ }
+
+ return ret;
+}
+
+static int operator_eval_skills_getvalue(const char *key, void* data)
+{
+ struct skills_group *skills = data;
+ int value = 0;
+ struct skill* skill;
+ struct ao2_iterator iter = ao2_iterator_init(skills->skills, 0);
+ while ((skill = ao2_iterator_next(&iter)) && strcasecmp(skill->name, key))
+ ao2_ref(skill, -1);
+
+ if (!skill)
+ value = 0;
+ else {
+ value = skill->weight;
+ ao2_ref(skill, -1);
+ }
+ ao2_iterator_destroy(&iter);
+ return value;
+}
+
+static int operator_eval_skills(struct skill_rule_operator *op, struct skills_group *skills, struct ao2_container *variables, struct queue_ent *qe)
+{
+ return operator_eval(op, variables, qe->chan, operator_eval_skills_getvalue, skills, NULL);
+}
+
+static int calculate_estimated_waiting_time(struct queue_ent *qe)
+{
+ struct ao2_iterator iter;
+ struct member *mem;
+ struct queue_ent *ch;
+ int sum = 0, count = 0;
+ float aht, ciqu = 0;
+ float ali;
+
+ if (!qe->mem_selection || ao2_container_count(qe->mem_selection) == 0)
+ return qe->vqueue->holdtime;
+
+ iter = ao2_iterator_init(qe->mem_selection, 0);
+ while ((mem = ao2_iterator_next(&iter))) {
+ sum += mem->holdtime;
+ count++;
+ ao2_ref(mem, -1);
+ }
+ ao2_iterator_destroy(&iter);
+
+ ali = count > 0 ? count : 0.0001;
+ aht = sum / ali;
+
+ for (ch = qe->parent->head; ch; ch = ch->next) {
+ if (!ch->pending && ch->vqueue == qe->vqueue)
+ ciqu++;
+ }
+
+ return (qe->vqueue->holdtime = aht * ciqu / ali);
+}
+
+static int get_estimated_waiting_time(struct queue_ent *qe)
+{
+ if (qe->vqueue)
+ return calculate_estimated_waiting_time(qe);
+ else
+ return qe->parent->holdtime;
+}
+
+static int get_waiting_time(struct queue_ent *qe)
+{
+ return time(NULL) - qe->start;
+}
+
+static int operator_eval_dynamics_getvalue(const char *key, void* data)
+{
+ static const struct {
+ const char *name;
+ int (*func) (struct queue_ent *qe);
+ } static_vars[] = {
+ { "EWT", get_estimated_waiting_time },
+ { "WT", get_waiting_time },
+ };
+ struct queue_ent* qe = data;
+ size_t i;
+ int value = 0;
+
+ for (i = 0; i < sizeof(static_vars) / sizeof(*static_vars) && strcasecmp(static_vars[i].name, key); ++i)
+ ;
+
+ if (i < (sizeof(static_vars) / sizeof(*static_vars)))
+ value = static_vars[i].func(qe);
+
+ return value;
+}
+
+static void operator_eval_dynamics_proceed_cb(const char *left_name, int left_value, enum skill_rule_operator_type op,
+ const char *right_name, int right_value, void* data)
+{
+ struct queue_ent* qe = data;
+ int left_wt = left_name && !strcasecmp(left_name, "WT");
+ int right_wt = right_name && !strcasecmp(right_name, "WT");
+ int new_check = 0;
+
+ if (left_wt && right_wt) {
+ /* WTF */
+ return;
+ }
+
+ switch(op)
+ {
+ case SKILL_RULE_OPERATOR_EQUAL:
+ if (left_wt)
+ new_check = time(NULL) + right_value - get_waiting_time(qe);
+ if (right_wt)
+ new_check = time(NULL) + left_value - get_waiting_time(qe);
+
+ break;
+ case SKILL_RULE_OPERATOR_GREATER:
+ if (right_wt)
+ new_check = time(NULL) + left_value - get_waiting_time(qe);
+ break;
+ case SKILL_RULE_OPERATOR_LESSER:
+ if (left_wt)
+ new_check = time(NULL) + right_value - get_waiting_time(qe);
+ break;
+ case SKILL_RULE_OPERATOR_DIVISION:
+ case SKILL_RULE_OPERATOR_MULTIPLICATION:
+ case SKILL_RULE_OPERATOR_SUBTRACTION:
+ case SKILL_RULE_OPERATOR_ADDITION:
+ case SKILL_RULE_OPERATOR_NOTEQUAL:
+ case SKILL_RULE_OPERATOR_AND:
+ case SKILL_RULE_OPERATOR_OR:
+ case SKILL_RULE_OPERATOR_UNKNOWN:
+ break;
+ }
+ if (new_check && (!qe->skills_next_check || qe->skills_next_check > new_check)) {
+ qe->skills_next_check = new_check;
+ }
+}
+
+static int operator_eval_dynamics(struct skill_rule_operator *op, struct ao2_container* variables, struct queue_ent* qe)
+{
+ return operator_eval(op, variables, qe->chan, operator_eval_dynamics_getvalue, qe, operator_eval_dynamics_proceed_cb);
+}
+
+/** Syntax of a rule name with their arguments: Rule(arg1=value1^arg2=value2^...)
+ *
+ * This function returns a container of 'struct rule_var' with every variables,
+ * and rulename value is set to the real rulename.
+ */
+static struct ao2_container *get_rule_variables(struct queue_ent *qe, char **rulename)
+{
+ char *ptr, *var;
+ struct rule_var *v;
+ struct ao2_container *variables = ao2_container_alloc(37, NULL, NULL);
+
+ if (!variables) {
+ return NULL;
+ }
+
+ if (!(ptr = strchr(*rulename, '(')))
+ return variables;
+
+ *ptr++ = '\0';
+ while ((var = strsep(&ptr, ",|^)"))) {
+ char *value = strchr(var, '=');
+
+ if (!value)
+ continue;
+
+ *value++ = '\0';
+ v = ao2_alloc(sizeof(*v), NULL);
+ if (!v)
+ break;
+ ast_copy_string(v->name, var, sizeof(v->name));
+ ast_copy_string(v->value, value, sizeof(v->value));
+ ao2_link(variables, v);
+ ao2_ref(v, -1);
+ }
+
+ return variables;
+}
+
+static int member_is_selected(struct queue_ent *qe, struct member *mem)
+{
+ struct member *m;
+
+ /* If there isn't any queue entry or if there isn't any ruleset on the
+ * queue, it's because he doesn't use the skills routing.
+ */
+ if (!qe || ast_strlen_zero(qe->skill_ruleset))
+ return 1;
+
+ /* No member is selected. */
+ if (!qe->mem_selection)
+ return 0;
+
+ m = ao2_find(qe->mem_selection, mem, OBJ_POINTER);
+
+ if (m)
+ ao2_ref(m, -1);
+
+ return m != NULL;
+}
+
+static int join_virtual_queue(struct call_queue *q, struct queue_ent *qe)
+{
+ struct virtual_queue *vq = NULL;
+
+ if (!q->vqueues) {
+ q->vqueues = ao2_container_alloc(37, NULL, NULL);
+ if (!q->vqueues)
+ return -1;
+ } else {
+ struct ao2_iterator iter;
+
+ iter = ao2_iterator_init(q->vqueues, 0);
+ while ((vq = ao2_iterator_next(&iter)) && strcmp(vq->id, qe->skill_ruleset))
+ ao2_ref(vq, -1);
+ ao2_iterator_destroy(&iter);
+ }
+
+ if (!vq) {
+ vq = ao2_alloc(sizeof(*vq), NULL);
+ if (!vq)
+ return -1;
+ ast_copy_string(vq->id, qe->skill_ruleset, sizeof(vq->id));
+ ao2_link(q->vqueues, vq);
+ }
+
+ qe->vqueue = vq;
+ /* do not unref vq because it's keept by the queue entry. */
+
+ return 0;
+}
+
+/* Use rules to search members from the queue_ent's skills.
+ *
+ * Returns -1 when an error is occured.
+ * Returns 0 when agents are selected.
+ * Returns 1 when the ruleset matches no agent.
+ */
+static int select_members_from_skills(struct queue_ent *qe)
+{
+ struct call_queue* q = qe->parent;
+ struct member *member;
+ struct ao2_container *variables;
+ struct skill_ruleset* rs;
+ struct skill_rule* rule;
+ struct ao2_iterator rule_iter, mem_iter;
+ char* ruleset_name;
+
+ if (ast_strlen_zero(qe->skill_ruleset))
+ return 0;
+
+ ruleset_name = ast_strdupa(qe->skill_ruleset);
+ qe->skills_next_check = 0;
+ variables = get_rule_variables(qe, &ruleset_name);
+
+ if (!variables)
+ return -1;
+
+ AST_LIST_LOCK(&skill_rulesets);
+ AST_LIST_LOCK(&skills_groups);
+
+ AST_LIST_TRAVERSE(&skill_rulesets, rs, entry) {
+ if (!strcmp(rs->name, ruleset_name))
+ break;
+ }
+
+ if (!rs) {
+ ast_log(LOG_WARNING, "Ruleset '%s' does not exist.\n", ruleset_name);
+ } else {
+ rule_iter = ao2_iterator_init(rs->rules, 0);
+
+ /* Clear the current selection (if any) */
+ if (qe->mem_selection) {
+ ao2_ref(qe->mem_selection, -1);
+ qe->mem_selection = NULL;
+ }
+
+ while (!qe->mem_selection && (rule = ao2_iterator_next(&rule_iter))) {
+ qe->mem_selection = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
+ mem_iter = ao2_iterator_init(q->members, 0);
+ while ((member = ao2_iterator_next(&mem_iter))) {
+ struct skills_group* skills;
+ AST_LIST_TRAVERSE(&skills_groups, skills, entry) {
+ if (!strcmp(skills->name, member->skills))
+ break;
+ }
+
+ if (!skills) {
+ ast_log(LOG_WARNING, "Skills group '%s' does not exist.\n", member->skills);
+ continue;
+ }
+
+ if (!ast_strlen_zero(member->skills) &&
+ operator_eval_skills(rule->cond, skills, variables, qe)) {
+ ao2_link(qe->mem_selection, member);
+ ast_log(LOG_DEBUG, "Member %s is associated.\n", member->interface);
+ } else
+ ast_log(LOG_DEBUG, "Member %s is NOT associated.\n", member->interface);
+ ao2_ref(member, -1);
+ }
+ ao2_iterator_destroy(&mem_iter);
+ if (!ao2_container_count(qe->mem_selection) || (rule->dcond && !operator_eval_dynamics(rule->dcond, variables, qe))) {
+ /* CLEAR to retry. */
+ ast_log(LOG_DEBUG, "Jump to the next rule.\n");
+ ao2_ref(qe->mem_selection, -1);
+ qe->mem_selection = NULL;
+ qe->skills_next_check = 0;
+ }
+ ao2_ref(rule, -1);
+ }
+ ao2_iterator_destroy(&rule_iter);
+ }
+
+ AST_LIST_UNLOCK(&skill_rulesets);
+ AST_LIST_UNLOCK(&skills_groups);
+
+ ast_log(LOG_DEBUG, "End of member selection, found? %d\n", qe->mem_selection != NULL);
+
+ /* 0 only if a rule match. */
+ return qe->mem_selection == NULL ? 1 : 0;
+}
+
+static int update_queue_ent_skills_next_check(struct call_queue *q)
+{
+ struct queue_ent* ch = q->head;
+ time_t now = time(NULL);
+ for (; ch; ch = ch->next)
+ ch->skills_next_check = now;
+ return 0;
+}
+
static int join_queue(char *queuename, struct queue_ent *qe, enum queue_result *reason, int position)
{
struct call_queue *q;
@@ -2467,7 +3468,9 @@
/* This is our one */
if (q->joinempty) {
int status = 0;
- if ((status = get_member_status(q, qe->max_penalty, qe->min_penalty, q->joinempty))) {
+
+ /* do not give 'qe' because the members selection isn't made yet. */
+ if ((status = get_member_status(q, qe->max_penalty, qe->min_penalty, q->joinempty, NULL))) {
*reason = QUEUE_JOINEMPTY;
ao2_unlock(q);
ao2_unlock(queues);
@@ -2482,6 +3485,10 @@
* Take into account the priority of the calling user */
inserted = 0;
prev = NULL;
+
+ if (!ast_strlen_zero(qe->skill_ruleset))
+ join_virtual_queue(q, qe);
+
cur = q->head;
while (cur) {
/* We have higher priority than the current user, enter
@@ -2513,6 +3520,7 @@
ast_copy_string(qe->announce, q->announce, sizeof(qe->announce));
ast_copy_string(qe->context, q->context, sizeof(qe->context));
q->count++;
++ update_queue_ent_skills_next_check(q);
res = 0;
ast_manager_event(qe->chan, EVENT_FLAG_CALL, "Join",
"Channel: %s\r\n"
@@ -2602,7 +3610,7 @@
static int say_position(struct queue_ent *qe, int ringing)
{
- int res = 0, avgholdmins, avgholdsecs, announceposition = 0;
+ int res = 0, avgholdmins, avgholdsecs, holdtime, announceposition = 0;
int say_thanks = 1;
time_t now;
@@ -2667,11 +3675,12 @@
}
}
/* Round hold time to nearest minute */
- avgholdmins = abs(((qe->parent->holdtime + 30) - (now - qe->start)) / 60);
+ holdtime = get_estimated_waiting_time(qe);
+ avgholdmins = abs(((holdtime + 30) - (now - qe->start)) / 60);
/* If they have specified a rounding then round the seconds as well */
if (qe->parent->roundingseconds) {
- avgholdsecs = (abs(((qe->parent->holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
+ avgholdsecs = (abs(((holdtime + 30) - (now - qe->start))) - 60 * avgholdmins) / qe->parent->roundingseconds;
avgholdsecs *= qe->parent->roundingseconds;
} else {
avgholdsecs = 0;
@@ -2744,6 +3753,13 @@
return res;
}
+static void recalc_member_holdtime(struct member *mem, int newholdtime)
+{
+ int oldvalue;
+ oldvalue = mem->holdtime;
+ mem->holdtime = (((oldvalue << 2) - oldvalue) + newholdtime) >> 2;
+}
+
static void recalc_holdtime(struct queue_ent *qe, int newholdtime)
{
int oldvalue;
@@ -2780,6 +3796,7 @@
if (current == qe) {
char posstr[20];
q->count--;
+ update_queue_ent_skills_next_check(q);
/* Take us out of the queue */
ast_manager_event(qe->chan, EVENT_FLAG_CALL, "Leave",
@@ -2866,9 +3883,10 @@
* \note The queue passed in should be locked prior to this function call
*
* \param[in] q The queue for which we are couting the number of available members
+ * \param[in] qe The queue entry for which we are counting the number of available associated members (can be NULL).
* \return Return the number of available members in queue q
*/
-static int num_available_members(struct call_queue *q)
+static int num_available_members(struct call_queue *q, struct queue_ent *qe)
{
struct member *mem;
int avl = 0;
@@ -2883,7 +3901,8 @@
/* else fall through */
case AST_DEVICE_NOT_INUSE:
case AST_DEVICE_UNKNOWN:
- if (!mem->paused) {
+ if (!mem->paused &&
++ (!qe || member_is_selected(qe, mem))) {
avl++;
}
break;
@@ -2930,7 +3949,7 @@
if (q->count && q->members) {
if ((mem = ao2_find(q->members, member, OBJ_POINTER))) {
ast_debug(1, "Found matching member %s in queue '%s'\n", mem->interface, q->name);
- if (q->weight > rq->weight && q->count >= num_available_members(q)) {
[... 759 lines stripped ...]
More information about the svn-commits
mailing list