From cf861b38c79e99feaa66d17c9e6c1d6b75fd988c Mon Sep 17 00:00:00 2001
From: Mark Michelson <mmichelson@digium.com>
Date: Fri, 26 Oct 2007 15:19:46 +0000
Subject: [PATCH] Added queue strategy "linear". This strategy is useful for
 those who always wish for their phones to be rung in a specific order.

(closes issue #7279, reported and initially patched by diLLec, patch reworked by me)



git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@87154 65c4cc65-6c06-0410-ace0-fbb531ad65f3
---
 apps/app_queue.c           | 83 ++++++++++++++++++++++++++++++++------
 configs/queues.conf.sample |  3 ++
 2 files changed, 74 insertions(+), 12 deletions(-)

diff --git a/apps/app_queue.c b/apps/app_queue.c
index 527f3fdd2c..d62acb4901 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -100,7 +100,8 @@ enum {
 	QUEUE_STRATEGY_LEASTRECENT,
 	QUEUE_STRATEGY_FEWESTCALLS,
 	QUEUE_STRATEGY_RANDOM,
-	QUEUE_STRATEGY_RRMEMORY
+	QUEUE_STRATEGY_RRMEMORY,
+	QUEUE_STRATEGY_LINEAR
 };
 
 static struct strategy {
@@ -112,6 +113,7 @@ static struct strategy {
 	{ QUEUE_STRATEGY_FEWESTCALLS, "fewestcalls" },
 	{ QUEUE_STRATEGY_RANDOM, "random" },
 	{ QUEUE_STRATEGY_RRMEMORY, "rrmemory" },
+	{ QUEUE_STRATEGY_LINEAR, "linear" },
 };
 
 #define DEFAULT_RETRY		5
@@ -324,6 +326,8 @@ struct queue_ent {
 	int opos;                           /*!< Where we started in the queue */
 	int handled;                        /*!< Whether our call was handled */
 	int max_penalty;                    /*!< Limit the members that can take this call to this penalty or lower */
+	int linpos;							/*!< If using linear strategy, what position are we at? */
+	int linwrapped;						/*!< Is the linpos wrapped? */
 	time_t start;                       /*!< When we started holding */
 	time_t expire;                      /*!< When this entry should expire (time out of queue) */
 	struct ast_channel *chan;           /*!< Our channel */
@@ -833,8 +837,13 @@ static void init_queue(struct call_queue *q)
 	q->monfmt[0] = '\0';
 	q->periodicannouncefrequency = 0;
 	q->sound_callerannounce[0] = '\0';	/* Default, don't announce the caller that he has been answered */
-	if(!q->members)
-		q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
+	if(!q->members) {
+		if(q->strategy == QUEUE_STRATEGY_LINEAR)
+			/* linear strategy depends on order, so we have to place all members in a single bucket */
+			q->members = ao2_container_alloc(1, member_hash_fn, member_cmp_fn);
+		else
+			q->members = ao2_container_alloc(37, member_hash_fn, member_cmp_fn);
+	}
 	q->membercount = 0;
 	q->found = 1;
 	ast_copy_string(q->sound_next, "queue-youarenext", sizeof(q->sound_next));
@@ -1063,12 +1072,8 @@ static void queue_set_param(struct call_queue *q, const char *param, const char
 	} else if (!strcasecmp(param, "servicelevel")) {
 		q->servicelevel= atoi(val);
 	} else if (!strcasecmp(param, "strategy")) {
-		q->strategy = strat2int(val);
-		if (q->strategy < 0) {
-			ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
-				val, q->name);
-			q->strategy = QUEUE_STRATEGY_RINGALL;
-		}
+		/* We already have set this, no need to do it again */
+		return;
 	} else if (!strcasecmp(param, "joinempty")) {
 		if (!strcasecmp(val, "loose"))
 			q->joinempty = QUEUE_EMPTY_LOOSE;
@@ -1914,8 +1919,10 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies
 
 		ao2_lock(qe->parent);
 		qe->parent->rrpos++;
+		qe->linpos++;
 		ao2_unlock(qe->parent);
 
+
 		(*busies)++;
 		return 0;
 	} else if (status != tmp->oldstatus)
@@ -2021,7 +2028,7 @@ static int ring_one(struct queue_ent *qe, struct callattempt *outgoing, int *bus
 	return ret;
 }
 
-static int store_next(struct queue_ent *qe, struct callattempt *outgoing)
+static int store_next_rr(struct queue_ent *qe, struct callattempt *outgoing)
 {
 	struct callattempt *best = find_best(outgoing);
 
@@ -2044,6 +2051,29 @@ static int store_next(struct queue_ent *qe, struct callattempt *outgoing)
 	return 0;
 }
 
+static int store_next_lin(struct queue_ent *qe, struct callattempt *outgoing)
+{
+	struct callattempt *best = find_best(outgoing);
+
+	if (best) {
+		/* Ring just the best channel */
+		ast_debug(1, "Next is '%s' with metric %d\n", best->interface, best->metric);
+		qe->linpos = best->metric % 1000;
+	} else {
+		/* Just increment rrpos */
+		if (qe->linwrapped) {
+			/* No more channels, start over */
+			qe->linpos = 0;
+		} else {
+			/* Prioritize next entry */
+			qe->linpos++;
+		}
+	}
+	qe->linwrapped = 0;
+
+	return 0;
+}
+
 static int say_periodic_announcement(struct queue_ent *qe, int ringing)
 {
 	int res = 0;
@@ -2541,6 +2571,17 @@ static int calc_metric(struct call_queue *q, struct member *mem, int pos, struct
 		/* Everyone equal, except for penalty */
 		tmp->metric = mem->penalty * 1000000;
 		break;
+	case QUEUE_STRATEGY_LINEAR:
+		if (pos < qe->linpos) {
+			tmp->metric = 1000 + pos;
+		} else {
+			if (pos > qe->linpos)
+				/* Indicate there is another priority */
+				qe->linwrapped = 1;
+			tmp->metric = pos;
+		}
+		tmp->metric += mem->penalty * 1000000;
+		break;
 	case QUEUE_STRATEGY_RRMEMORY:
 		if (pos < q->rrpos) {
 			tmp->metric = 1000 + pos;
@@ -2689,7 +2730,7 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce
                         ast_set_flag(&(bridge_config.features_caller), AST_FEATURE_PARKCALL);
                         break;
 		case 'n':
-			if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY)
+			if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY || qe->parent->strategy == QUEUE_STRATEGY_LINEAR)
 				(*tries)++;
 			else
 				*tries = qe->parent->membercount;
@@ -2757,7 +2798,10 @@ static int try_calling(struct queue_ent *qe, const char *options, char *announce
 	lpeer = wait_for_answer(qe, outgoing, &to, &digit, numbusies, ast_test_flag(&(bridge_config.features_caller), AST_FEATURE_DISCONNECT), forwardsallowed);
 	ao2_lock(qe->parent);
 	if (qe->parent->strategy == QUEUE_STRATEGY_RRMEMORY) {
-		store_next(qe, outgoing);
+		store_next_rr(qe, outgoing);
+	}
+	if (qe->parent->strategy == QUEUE_STRATEGY_LINEAR) {
+		store_next_lin(qe, outgoing);
 	}
 	ao2_unlock(qe->parent);
 	peer = lpeer ? lpeer->chan : NULL;
@@ -4214,6 +4258,7 @@ static int reload_queues(int reload)
 			} else
 				new = 0;
 			if (q) {
+				const char *tmpvar;
 				if (!new)
 					ao2_lock(q);
 				/* Check if a queue with this name already exists */
@@ -4223,6 +4268,20 @@ static int reload_queues(int reload)
 						ao2_unlock(q);
 					continue;
 				}
+				/* Due to the fact that the "linear" strategy will have a different allocation
+				 * scheme for queue members, we must devise the queue's strategy before other initializations
+				 */
+				if((tmpvar = ast_variable_retrieve(cfg, cat, "strategy"))) {
+					ast_log(LOG_DEBUG, "Success!!\n");
+					q->strategy = strat2int(tmpvar);
+					ast_log(LOG_DEBUG, "Queue strategy set to '%s'\n", int2strat(q->strategy));
+					if (q->strategy < 0) {
+						ast_log(LOG_WARNING, "'%s' isn't a valid strategy for queue '%s', using ringall instead\n",
+						tmpvar, q->name);
+						q->strategy = QUEUE_STRATEGY_RINGALL;
+					}
+				} else
+					q->strategy = QUEUE_STRATEGY_RINGALL;
 				/* Re-initialize the queue, and clear statistics */
 				init_queue(q);
 				if (!queue_keep_stats) 
diff --git a/configs/queues.conf.sample b/configs/queues.conf.sample
index 3019903ac3..a7dd92a732 100644
--- a/configs/queues.conf.sample
+++ b/configs/queues.conf.sample
@@ -93,6 +93,9 @@ shared_lastcall=no
 ; fewestcalls - ring the one with fewest completed calls from this queue
 ; random - ring random interface
 ; rrmemory - round robin with memory, remember where we left off last ring pass
+; linear - rings interfaces in the order specified in this configuration file.
+;          If you use dynamic members, the members will be rung in the order in
+;          which they were added
 ;
 ;strategy = ringall
 ;
-- 
GitLab