[asterisk-commits] irroot: branch irroot/app_queue_skill r322068 - /team/irroot/app_queue_skill/...

SVN commits to the Asterisk project asterisk-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 asterisk-commits mailing list