[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