<p>Friendly Automation <strong>submitted</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/17989">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Joshua Colp: Looks good to me, but someone else must approve
Kevin Harwell: Looks good to me, but someone else must approve
Benjamin Keith Ford: Looks good to me, approved
Friendly Automation: Approved for Submit
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">app_queue: Add QueueWithdrawCaller AMI action<br><br>This adds a new AMI action called QueueWithdrawCaller.<br>This AMI action makes it possible to withdraw a caller from a queue,<br>in a safe and a generic manner.<br>This can be useful for retrieving a specific call and<br>dispatching it to a specific extension.<br>It works by signaling the caller to exit the queue application<br>whenever it can. Therefore, it is not guaranteed<br>that the call will leave the queue.<br><br>ASTERISK-29909 #close<br><br>Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec<br>---<br>M apps/app_queue.c<br>A doc/CHANGES-staging/queue_withdraw_caller.txt<br>2 files changed, 159 insertions(+), 1 deletion(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/apps/app_queue.c b/apps/app_queue.c</span><br><span>index 8ec98e2..3505413 100644</span><br><span>--- a/apps/app_queue.c</span><br><span>+++ b/apps/app_queue.c</span><br><span>@@ -290,6 +290,7 @@</span><br><span> <value name="JOINUNAVAIL" /></span><br><span> <value name="LEAVEUNAVAIL" /></span><br><span> <value name="CONTINUE" /></span><br><span style="color: hsl(120, 100%, 40%);">+ <value name="WITHDRAW" /></span><br><span> </variable></span><br><span> <variable name="ABANDONED"></span><br><span> <para>If the call was not answered by an agent this variable will be TRUE.</para></span><br><span>@@ -298,6 +299,9 @@</span><br><span> <variable name="DIALEDPEERNUMBER"></span><br><span> <para>Resource of the agent that was dialed set on the outbound channel.</para></span><br><span> </variable></span><br><span style="color: hsl(120, 100%, 40%);">+ <variable name="QUEUE_WITHDRAW_INFO"></span><br><span style="color: hsl(120, 100%, 40%);">+ <para>If the call was successfully withdrawn from the queue, and the withdraw request was provided with optional withdraw info, the withdraw info will be stored in this variable.</para></span><br><span style="color: hsl(120, 100%, 40%);">+ </variable></span><br><span> </variablelist></span><br><span> </description></span><br><span> <see-also></span><br><span>@@ -1057,6 +1061,25 @@</span><br><span> <description></span><br><span> </description></span><br><span> </manager></span><br><span style="color: hsl(120, 100%, 40%);">+ <manager name="QueueWithdrawCaller" language="en_US"></span><br><span style="color: hsl(120, 100%, 40%);">+ <synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+ Request to withdraw a caller from the queue back to the dialplan.</span><br><span style="color: hsl(120, 100%, 40%);">+ </synopsis></span><br><span style="color: hsl(120, 100%, 40%);">+ <syntax></span><br><span style="color: hsl(120, 100%, 40%);">+ <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /></span><br><span style="color: hsl(120, 100%, 40%);">+ <parameter name="Queue" required="true"></span><br><span style="color: hsl(120, 100%, 40%);">+ <para>The name of the queue to take action on.</para></span><br><span style="color: hsl(120, 100%, 40%);">+ </parameter></span><br><span style="color: hsl(120, 100%, 40%);">+ <parameter name="Caller" required="true"></span><br><span style="color: hsl(120, 100%, 40%);">+ <para>The caller (channel) to withdraw from the queue.</para></span><br><span style="color: hsl(120, 100%, 40%);">+ </parameter></span><br><span style="color: hsl(120, 100%, 40%);">+ <parameter name="WithdrawInfo" required="false"></span><br><span style="color: hsl(120, 100%, 40%);">+ <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></span><br><span style="color: hsl(120, 100%, 40%);">+ </parameter></span><br><span style="color: hsl(120, 100%, 40%);">+ </syntax></span><br><span style="color: hsl(120, 100%, 40%);">+ <description></span><br><span style="color: hsl(120, 100%, 40%);">+ </description></span><br><span style="color: hsl(120, 100%, 40%);">+ </manager></span><br><span> </span><br><span> <managerEvent language="en_US" name="QueueParams"></span><br><span> <managerEventInstance class="EVENT_FLAG_AGENT"></span><br><span>@@ -1602,6 +1625,7 @@</span><br><span> QUEUE_LEAVEUNAVAIL = 5,</span><br><span> QUEUE_FULL = 6,</span><br><span> QUEUE_CONTINUE = 7,</span><br><span style="color: hsl(120, 100%, 40%);">+ QUEUE_WITHDRAW = 8,</span><br><span> };</span><br><span> </span><br><span> static const struct {</span><br><span>@@ -1616,6 +1640,7 @@</span><br><span> { QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },</span><br><span> { QUEUE_FULL, "FULL" },</span><br><span> { QUEUE_CONTINUE, "CONTINUE" },</span><br><span style="color: hsl(120, 100%, 40%);">+ { QUEUE_WITHDRAW, "WITHDRAW" },</span><br><span> };</span><br><span> </span><br><span> enum queue_timeout_priority {</span><br><span>@@ -1684,6 +1709,8 @@</span><br><span> time_t start; /*!< When we started holding */</span><br><span> time_t expire; /*!< When this entry should expire (time out of queue) */</span><br><span> int cancel_answered_elsewhere; /*!< Whether we should force the CAE flag on this call (C) option*/</span><br><span style="color: hsl(120, 100%, 40%);">+ unsigned int withdraw:1; /*!< Should this call exit the queue at its next iteration? Used for QueueWithdrawCaller */</span><br><span style="color: hsl(120, 100%, 40%);">+ char *withdraw_info; /*!< Optional info passed by the caller of QueueWithdrawCaller */</span><br><span> struct ast_channel *chan; /*!< Our channel */</span><br><span> AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */</span><br><span> struct penalty_rule *pr; /*!< Pointer to the next penalty rule to implement */</span><br><span>@@ -5802,6 +5829,13 @@</span><br><span> /* This is the holding pen for callers 2 through maxlen */</span><br><span> for (;;) {</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* A request to withdraw this call from the queue arrived */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (qe->withdraw) {</span><br><span style="color: hsl(120, 100%, 40%);">+ *reason = QUEUE_WITHDRAW;</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> if (is_our_turn(qe)) {</span><br><span> break;</span><br><span> }</span><br><span>@@ -7622,6 +7656,51 @@</span><br><span> }</span><br><span> </span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*! \brief Request to withdraw a caller from a queue</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval RES_NOSUCHQUEUE queue does not exist</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval RES_OKAY withdraw request sent</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval RES_NOT_CALLER queue exists but no caller</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval RES_EXISTS a withdraw request was already sent for this caller (channel) and queue</span><br><span style="color: hsl(120, 100%, 40%);">+*/</span><br><span style="color: hsl(120, 100%, 40%);">+static int request_withdraw_caller_from_queue(const char *queuename, const char *caller, const char *withdraw_info)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct call_queue *q;</span><br><span style="color: hsl(120, 100%, 40%);">+ struct queue_ent *qe;</span><br><span style="color: hsl(120, 100%, 40%);">+ int res = RES_NOSUCHQUEUE;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ /*! \note Ensure the appropriate realtime queue is loaded. Note that this</span><br><span style="color: hsl(120, 100%, 40%);">+ * short-circuits if the queue is already in memory. */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!(q = find_load_queue_rt_friendly(queuename))) {</span><br><span style="color: hsl(120, 100%, 40%);">+ return res;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_lock(q);</span><br><span style="color: hsl(120, 100%, 40%);">+ res = RES_NOT_CALLER;</span><br><span style="color: hsl(120, 100%, 40%);">+ for (qe = q->head; qe; qe = qe->next) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!strcmp(ast_channel_name(qe->chan), caller)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (qe->withdraw) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_debug(1, "Ignoring duplicate withdraw request of caller %s from queue %s\n", caller, queuename);</span><br><span style="color: hsl(120, 100%, 40%);">+ res = RES_EXISTS;</span><br><span style="color: hsl(120, 100%, 40%);">+ } else {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_debug(1, "Requested withdraw of caller %s from queue %s\n", caller, queuename);</span><br><span style="color: hsl(120, 100%, 40%);">+ /* It is not possible to change the withdraw info by further withdraw requests for this caller (channel)</span><br><span style="color: hsl(120, 100%, 40%);">+ in this queue, so we do not need to worry about a memory leak here. */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (withdraw_info) {</span><br><span style="color: hsl(120, 100%, 40%);">+ qe->withdraw_info = ast_strdup(withdraw_info);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ qe->withdraw = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ res = RES_OKAY;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ ao2_unlock(q);</span><br><span style="color: hsl(120, 100%, 40%);">+ queue_unref(q);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return res;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static int publish_queue_member_pause(struct call_queue *q, struct member *member)</span><br><span> {</span><br><span> struct ast_json *json_blob = queue_member_blob_create(q, member);</span><br><span>@@ -8569,6 +8648,13 @@</span><br><span> /* they may dial a digit from the queue context; */</span><br><span> /* or, they may timeout. */</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* A request to withdraw this call from the queue arrived */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (qe.withdraw) {</span><br><span style="color: hsl(120, 100%, 40%);">+ reason = QUEUE_WITHDRAW;</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /* Leave if we have exceeded our queuetimeout */</span><br><span> if (qe.expire && (time(NULL) >= qe.expire)) {</span><br><span> record_abandoned(&qe);</span><br><span>@@ -8596,6 +8682,13 @@</span><br><span> }</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* A request to withdraw this call from the queue arrived */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (qe.withdraw) {</span><br><span style="color: hsl(120, 100%, 40%);">+ reason = QUEUE_WITHDRAW;</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 1;</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /* Leave if we have exceeded our queuetimeout */</span><br><span> if (qe.expire && (time(NULL) >= qe.expire)) {</span><br><span> record_abandoned(&qe);</span><br><span>@@ -8670,7 +8763,14 @@</span><br><span> </span><br><span> stop:</span><br><span> if (res) {</span><br><span style="color: hsl(0, 100%, 40%);">- if (res < 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (reason == QUEUE_WITHDRAW) {</span><br><span style="color: hsl(120, 100%, 40%);">+ record_abandoned(&qe);</span><br><span style="color: hsl(120, 100%, 40%);">+ 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 : "");</span><br><span style="color: hsl(120, 100%, 40%);">+ if (qe.withdraw_info) {</span><br><span style="color: hsl(120, 100%, 40%);">+ pbx_builtin_setvar_helper(qe.chan, "QUEUE_WITHDRAW_INFO", qe.withdraw_info);</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+ res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ } else if (res < 0) {</span><br><span> if (!qe.handled) {</span><br><span> record_abandoned(&qe);</span><br><span> ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "ABANDON",</span><br><span>@@ -8690,6 +8790,13 @@</span><br><span> }</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+ /* Free the optional withdraw info if present */</span><br><span style="color: hsl(120, 100%, 40%);">+ /* This is done here to catch all cases. e.g. if the call eventually wasn't withdrawn, e.g. answered */</span><br><span style="color: hsl(120, 100%, 40%);">+ if (qe.withdraw_info) {</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_free(qe.withdraw_info);</span><br><span style="color: hsl(120, 100%, 40%);">+ qe.withdraw_info = NULL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /* Don't allow return code > 0 */</span><br><span> if (res >= 0) {</span><br><span> res = 0;</span><br><span>@@ -10743,6 +10850,41 @@</span><br><span> return 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static int manager_request_withdraw_caller_from_queue(struct mansession *s, const struct message *m)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ const char *queuename, *caller, *withdraw_info;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ queuename = astman_get_header(m, "Queue");</span><br><span style="color: hsl(120, 100%, 40%);">+ caller = astman_get_header(m, "Caller");</span><br><span style="color: hsl(120, 100%, 40%);">+ withdraw_info = astman_get_header(m, "WithdrawInfo");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_strlen_zero(queuename)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ astman_send_error(s, m, "'Queue' not specified.");</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ if (ast_strlen_zero(caller)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ astman_send_error(s, m, "'Caller' not specified.");</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ switch (request_withdraw_caller_from_queue(queuename, caller, withdraw_info)) {</span><br><span style="color: hsl(120, 100%, 40%);">+ case RES_OKAY:</span><br><span style="color: hsl(120, 100%, 40%);">+ astman_send_ack(s, m, "Withdraw requested successfully");</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ case RES_NOSUCHQUEUE:</span><br><span style="color: hsl(120, 100%, 40%);">+ astman_send_error(s, m, "Unable to request withdraw from queue: No such queue");</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ case RES_NOT_CALLER:</span><br><span style="color: hsl(120, 100%, 40%);">+ astman_send_error(s, m, "Unable to request withdraw from queue: No such caller");</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ case RES_EXISTS:</span><br><span style="color: hsl(120, 100%, 40%);">+ astman_send_error(s, m, "Unable to request withdraw from queue: Already requested");</span><br><span style="color: hsl(120, 100%, 40%);">+ break;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return 0;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span> </span><br><span> </span><br><span> static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)</span><br><span>@@ -11472,6 +11614,7 @@</span><br><span> ast_manager_unregister("QueueReset");</span><br><span> ast_manager_unregister("QueueMemberRingInUse");</span><br><span> ast_manager_unregister("QueueChangePriorityCaller");</span><br><span style="color: hsl(120, 100%, 40%);">+ ast_manager_unregister("QueueWithdrawCaller");</span><br><span> ast_unregister_application(app_aqm);</span><br><span> ast_unregister_application(app_rqm);</span><br><span> ast_unregister_application(app_pqm);</span><br><span>@@ -11585,6 +11728,7 @@</span><br><span> err |= ast_manager_register_xml("QueueReload", 0, manager_queue_reload);</span><br><span> err |= ast_manager_register_xml("QueueReset", 0, manager_queue_reset);</span><br><span> err |= ast_manager_register_xml("QueueChangePriorityCaller", 0, manager_change_priority_caller_on_queue);</span><br><span style="color: hsl(120, 100%, 40%);">+ err |= ast_manager_register_xml("QueueWithdrawCaller", 0, manager_request_withdraw_caller_from_queue);</span><br><span> err |= ast_custom_function_register(&queuevar_function);</span><br><span> err |= ast_custom_function_register(&queueexists_function);</span><br><span> err |= ast_custom_function_register(&queuemembercount_function);</span><br><span>diff --git a/doc/CHANGES-staging/queue_withdraw_caller.txt b/doc/CHANGES-staging/queue_withdraw_caller.txt</span><br><span>new file mode 100644</span><br><span>index 0000000..04e43d0</span><br><span>--- /dev/null</span><br><span>+++ b/doc/CHANGES-staging/queue_withdraw_caller.txt</span><br><span>@@ -0,0 +1,14 @@</span><br><span style="color: hsl(120, 100%, 40%);">+Subject: app_queue</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Added a new AMI action: QueueWithdrawCaller</span><br><span style="color: hsl(120, 100%, 40%);">+This AMI action makes it possible to withdraw a caller from a queue</span><br><span style="color: hsl(120, 100%, 40%);">+back to the dialplan. The call will be signaled to leave the queue</span><br><span style="color: hsl(120, 100%, 40%);">+whenever it can, hence, it not guaranteed that the call will leave</span><br><span style="color: hsl(120, 100%, 40%);">+the queue.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+Optional custom data can be passed in the request, in the WithdrawInfo</span><br><span style="color: hsl(120, 100%, 40%);">+parameter. If the call successfully withdrawn the queue,</span><br><span style="color: hsl(120, 100%, 40%);">+it can be retrieved using the QUEUE_WITHDRAW_INFO variable.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+This can be useful for certain uses, such as dispatching the call</span><br><span style="color: hsl(120, 100%, 40%);">+to a specific extension.</span><br><span></span><br></pre><div style="white-space:pre-wrap"></div><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/17989">change 17989</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/c/asterisk/+/17989"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 18 </div>
<div style="display:none"> Gerrit-Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec </div>
<div style="display:none"> Gerrit-Change-Number: 17989 </div>
<div style="display:none"> Gerrit-PatchSet: 13 </div>
<div style="display:none"> Gerrit-Owner: Kfir Itzhak <mastertheknife@gmail.com> </div>
<div style="display:none"> Gerrit-Reviewer: Benjamin Keith Ford <bford@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@sangoma.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>