[svn-commits] irroot: branch irroot/patches r333568 - /team/irroot/patches/
SVN commits to the Digium repositories
svn-commits at lists.digium.com
Sun Aug 28 12:52:13 CDT 2011
Author: irroot
Date: Sun Aug 28 12:52:07 2011
New Revision: 333568
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=333568
Log:
Add patch for skill based routing patch
Added:
team/irroot/patches/app_queue_skill-trunk.patch (with props)
Added: team/irroot/patches/app_queue_skill-trunk.patch
URL: http://svnview.digium.com/svn/asterisk/team/irroot/patches/app_queue_skill-trunk.patch?view=auto&rev=333568
==============================================================================
--- team/irroot/patches/app_queue_skill-trunk.patch (added)
+++ team/irroot/patches/app_queue_skill-trunk.patch Sun Aug 28 12:52:07 2011
@@ -1,0 +1,2282 @@
+Index: apps/app_queue.c
+===================================================================
+--- apps/app_queue.c (.../trunk) (revision 333567)
++++ apps/app_queue.c (.../team/irroot/app_queue_skill) (revision 333567)
+@@ -868,6 +868,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 {
+@@ -1016,9 +1018,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 */
+@@ -1038,8 +1045,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 */
+@@ -1047,18 +1057,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 */
+@@ -1189,12 +1283,13 @@
+ int rrpos; /*!< Round Robin - position */
+ int memberdelay; /*!< Seconds to delay connecting member to caller */
+ int autofill; /*!< Ignore the head call status and ring an available agent */
+-
++
++ struct ao2_container *vqueues; /*!< Virtual queues */
+ struct ao2_container *members; /*!< Head of the list of members */
+- /*!
++ /*!
+ * \brief Number of members _logged in_
+ * \note There will be members in the members container that are not logged
+- * in, so this can not simply be replaced with ao2_container_count().
++ * in, so this can not simply be replaced with ao2_container_count().
+ */
+ int membercount;
+ struct queue_ent *head; /*!< Head of the list of callers */
+@@ -1215,6 +1310,8 @@
+ 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 int update_queue_ent_skills_next_check(struct call_queue *q);
++static int member_is_selected(struct queue_ent *qe, struct member *mem);
+
+ static void queue_transfer_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan);
+
+@@ -1370,7 +1467,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;
+@@ -1385,6 +1482,11 @@
+ }
+ }
+
++ if (!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) {
+ case AST_DEVICE_INVALID:
+ if (conditions & QUEUE_EMPTY_INVALID) {
+@@ -1458,6 +1560,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"
+@@ -1468,9 +1571,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->state_interface, 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;
+@@ -1623,10 +1727,10 @@
+ }
+
+ /*! \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;
+-
++
+ if ((cur = ao2_alloc(sizeof(*cur), NULL))) {
+ cur->penalty = penalty;
+ cur->paused = paused;
+@@ -1649,6 +1753,11 @@
+ 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;
+@@ -2124,6 +2233,7 @@
+ const char *state_interface = S_OR(ast_variable_retrieve(member_config, interface, "state_interface"), interface);
+ const char *penalty_str = ast_variable_retrieve(member_config, interface, "penalty");
+ const char *paused_str = ast_variable_retrieve(member_config, interface, "paused");
++ const char *skills = ast_variable_retrieve(member_config, interface, "skills");
+
+ if (ast_strlen_zero(rt_uniqueid)) {
+ ast_log(LOG_WARNING, "Realtime field uniqueid is empty for member %s\n", S_OR(membername, "NULL"));
+@@ -2164,8 +2274,14 @@
+ }
+ m->penalty = penalty;
+ m->ignorebusy = ignorebusy;
++ 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);
+@@ -2174,10 +2290,11 @@
+
+ /* 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;
+ m->ignorebusy = ignorebusy;
++ update_queue_ent_skills_next_check(q);
+ ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid));
+ if (!log_membername_as_agent) {
+ ast_queue_log(q->name, "REALTIME", m->interface, "ADDMEMBER", "%s", "");
+@@ -2222,6 +2339,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)
+@@ -2505,6 +2626,915 @@
+ 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;
++ }
++
++ if ((m = ao2_find(qe->mem_selection, mem, OBJ_POINTER))) {
++ 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;
+@@ -2522,7 +3552,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);
+@@ -2538,6 +3570,11 @@
+ * 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
+@@ -2569,6 +3606,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"
+@@ -2659,7 +3697,11 @@
+
+ static int say_position(struct queue_ent *qe, int ringing)
+ {
+- int res = 0, avgholdmins, avgholdsecs, announceposition = 0;
++ int res = 0;
++ int avgholdmins;
++ int avgholdsecs;
++ int holdtime;
++ int announceposition = 0;
+ int say_thanks = 1;
+ time_t now;
+
+@@ -2724,11 +3766,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;
+@@ -2801,6 +3844,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;
+@@ -2837,6 +3887,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",
[... 1015 lines stripped ...]
More information about the svn-commits
mailing list