[Asterisk-code-review] app_queue: Add QueueWithdrawCaller AMI action (asterisk[16])

Kfir Itzhak asteriskteam at digium.com
Wed Feb 9 04:45:00 CST 2022


Kfir Itzhak has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/18008 )


Change subject: app_queue: Add QueueWithdrawCaller AMI action
......................................................................

app_queue: Add QueueWithdrawCaller AMI action

This adds a new AMI action called QueueWithdrawCaller.
This AMI action makes it possible to withdraw a caller from a queue, in a safe and generic manner.
This is useful for retrieving a specific call and dispatching it to a specific extension.
It is a best-effort request. It works by signaling the caller to exit the queue application whenever it can.

ASTERISK-29909 #close

Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec
---
M apps/app_queue.c
1 file changed, 129 insertions(+), 1 deletion(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/08/18008/1

diff --git a/apps/app_queue.c b/apps/app_queue.c
index df5dba2..76841ee 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -290,6 +290,7 @@
 					<value name="JOINUNAVAIL" />
 					<value name="LEAVEUNAVAIL" />
 					<value name="CONTINUE" />
+					<value name="WITHDRAW" />
 				</variable>
 				<variable name="ABANDONED">
 					<para>If the call was not answered by an agent this variable will be TRUE.</para>
@@ -1057,6 +1058,25 @@
 		<description>
 		</description>
 	</manager>
+	<manager name="QueueWithdrawCaller" language="en_US">
+		<synopsis>
+			Request to withdraw a caller from the queue back to the dialplan.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Queue" required="true">
+				<para>The name of the queue to take action on.</para>
+			</parameter>
+			<parameter name="Caller" required="true">
+				<para>The caller (channel) to withdraw from the queue.</para>
+			</parameter>
+			<parameter name="WithdrawInfo" required="false">
+				<para>Optional info to store. If the call is successfully withdrawn from the queue, this information will be available in the QUEUE_WITHDRAW_INFO variable.</para>
+			</parameter>
+		</syntax>
+		<description>
+		</description>
+	</manager>
 
 	<managerEvent language="en_US" name="QueueParams">
 		<managerEventInstance class="EVENT_FLAG_AGENT">
@@ -1605,6 +1625,7 @@
 	QUEUE_LEAVEUNAVAIL = 5,
 	QUEUE_FULL = 6,
 	QUEUE_CONTINUE = 7,
+	QUEUE_WITHDRAW = 8,
 };
 
 static const struct {
@@ -1619,6 +1640,7 @@
 	{ QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },
 	{ QUEUE_FULL, "FULL" },
 	{ QUEUE_CONTINUE, "CONTINUE" },
+	{ QUEUE_WITHDRAW, "WITHDRAW" },
 };
 
 enum queue_timeout_priority {
@@ -1687,6 +1709,8 @@
 	time_t start;                          /*!< When we started holding */
 	time_t expire;                         /*!< When this entry should expire (time out of queue) */
 	int cancel_answered_elsewhere;         /*!< Whether we should force the CAE flag on this call (C) option*/
+	unsigned int withdraw:1;               /*!< Should this call exit the queue at its next iteration? Used for QueueWithdrawCaller */
+	char* withdraw_info;                   /*!< Optional info passed by the caller of QueueWithdrawCaller */
 	struct ast_channel *chan;              /*!< Our channel */
 	AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */
 	struct penalty_rule *pr;               /*!< Pointer to the next penalty rule to implement */
@@ -5771,6 +5795,13 @@
 	/* This is the holding pen for callers 2 through maxlen */
 	for (;;) {
 
+		/* A request to withdraw this call from the queue arrived */
+		if (qe->withdraw) {
+			*reason = QUEUE_WITHDRAW;
+			res = 1;
+			break;
+		}
+
 		if (is_our_turn(qe)) {
 			break;
 		}
@@ -7591,6 +7622,41 @@
 }
 
 
+/*! \brief Request to withdraw a caller from a queue
+ * \retval RES_NOSUCHQUEUE queue does not exist
+ * \retval RES_OKAY withdraw request sent
+ * \retval RES_NOT_CALLER queue exists but no caller
+*/
+static int request_withdraw_caller_from_queue(const char *queuename, const char *caller, const char *withdraw_info)
+{
+	struct call_queue *q;
+	struct queue_ent *qe;
+	int res = RES_NOSUCHQUEUE;
+
+	/*! \note Ensure the appropriate realtime queue is loaded.  Note that this
+	 * short-circuits if the queue is already in memory. */
+	if (!(q = find_load_queue_rt_friendly(queuename))) {
+		return res;
+	}
+
+	ao2_lock(q);
+	res = RES_NOT_CALLER;
+	for (qe = q->head; qe; qe = qe->next) {
+		if (strcmp(ast_channel_name(qe->chan), caller) == 0) {
+			ast_debug(1, "Requested withdraw of caller %s from queue %s\n",
+			             caller, queuename);
+			if(withdraw_info && !qe->withdraw_info) {
+				qe->withdraw_info = ast_strdup(withdraw_info);
+			}
+			qe->withdraw = 1;
+			res = RES_OKAY;
+		}
+	}
+	ao2_unlock(q);
+	return res;
+}
+
+
 static int publish_queue_member_pause(struct call_queue *q, struct member *member, const char *reason)
 {
 	struct ast_json *json_blob = queue_member_blob_create(q, member);
@@ -8536,6 +8602,13 @@
 		/* they may dial a digit from the queue context; */
 		/* or, they may timeout. */
 
+		/* A request to withdraw this call from the queue arrived */
+		if (qe.withdraw) {
+			reason = QUEUE_WITHDRAW;
+			res = 1;
+			break;
+		}
+
 		/* Leave if we have exceeded our queuetimeout */
 		if (qe.expire && (time(NULL) >= qe.expire)) {
 			record_abandoned(&qe);
@@ -8563,6 +8636,13 @@
 			}
 		}
 
+		/* A request to withdraw this call from the queue arrived */
+		if (qe.withdraw) {
+			reason = QUEUE_WITHDRAW;
+			res = 1;
+			break;
+		}
+
 		/* Leave if we have exceeded our queuetimeout */
 		if (qe.expire && (time(NULL) >= qe.expire)) {
 			record_abandoned(&qe);
@@ -8637,7 +8717,14 @@
 
 stop:
 	if (res) {
-		if (res < 0) {
+		if (reason == QUEUE_WITHDRAW) {
+			record_abandoned(&qe);
+			ast_queue_log(qe.parent->name, ast_channel_uniqueid(qe.chan), "NONE", "WITHDRAW", "%d|%d|%ld|%.40s", qe.pos, qe.opos, (long) (time(NULL) - qe.start), qe.withdraw_info ? qe.withdraw_info : "");
+			if (qe.withdraw_info) {
+				pbx_builtin_setvar_helper(qe.chan, "QUEUE_WITHDRAW_INFO", qe.withdraw_info);
+			}
+			res = 0;
+		} else if (res < 0) {
 			if (!qe.handled) {
 				record_abandoned(&qe);
 				ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "ABANDON",
@@ -8657,6 +8744,13 @@
 		}
 	}
 
+	/* Free the optional withdraw info if present */
+	/* This is done here to catch all cases. e.g. if the call eventually wasn't withdrawn, e.g. answered */
+	if (qe.withdraw_info != NULL) {
+		ast_free(qe.withdraw_info);
+		qe.withdraw_info = NULL;
+	}
+
 	/* Don't allow return code > 0 */
 	if (res >= 0) {
 		res = 0;
@@ -10690,6 +10784,38 @@
 	return 0;
 }
 
+static int manager_request_withdraw_caller_from_queue(struct mansession *s, const struct message *m)
+{
+	const char *queuename, *caller, *withdraw_info;
+
+	queuename = astman_get_header(m, "Queue");
+	caller = astman_get_header(m, "Caller");
+	withdraw_info = astman_get_header(m, "WithdrawInfo");
+
+	if (ast_strlen_zero(queuename)) {
+		astman_send_error(s, m, "'Queue' not specified.");
+		return 0;
+	}
+
+	if (ast_strlen_zero(caller)) {
+		astman_send_error(s, m, "'Caller' not specified.");
+		return 0;
+	}
+
+	switch (request_withdraw_caller_from_queue(queuename, caller, withdraw_info)) {
+	case RES_OKAY:
+		astman_send_ack(s, m, "Withdraw requested successfully");
+		break;
+	case RES_NOSUCHQUEUE:
+		astman_send_error(s, m, "Unable to request withdraw from queue: No such queue");
+		break;
+	case RES_NOT_CALLER:
+		astman_send_error(s, m, "Unable to request withdraw from queue: No such caller");
+		break;
+	}
+
+	return 0;
+}
 
 
 static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
@@ -11419,6 +11545,7 @@
 	ast_manager_unregister("QueueReset");
 	ast_manager_unregister("QueueMemberRingInUse");
 	ast_manager_unregister("QueueChangePriorityCaller");
+	ast_manager_unregister("QueueWithdrawCaller");
 	ast_unregister_application(app_aqm);
 	ast_unregister_application(app_rqm);
 	ast_unregister_application(app_pqm);
@@ -11532,6 +11659,7 @@
 	err |= ast_manager_register_xml("QueueReload", 0, manager_queue_reload);
 	err |= ast_manager_register_xml("QueueReset", 0, manager_queue_reset);
 	err |= ast_manager_register_xml("QueueChangePriorityCaller", 0,  manager_change_priority_caller_on_queue);
+	err |= ast_manager_register_xml("QueueWithdrawCaller", 0,  manager_request_withdraw_caller_from_queue);
 	err |= ast_custom_function_register(&queuevar_function);
 	err |= ast_custom_function_register(&queueexists_function);
 	err |= ast_custom_function_register(&queuemembercount_function);

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/18008
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 16
Gerrit-Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec
Gerrit-Change-Number: 18008
Gerrit-PatchSet: 1
Gerrit-Owner: Kfir Itzhak <mastertheknife at gmail.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20220209/cb5872a3/attachment-0001.html>


More information about the asterisk-code-review mailing list