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 a generic manner. This can be useful for retrieving a specific call and dispatching it to a specific extension. It works by signaling the caller to exit the queue application whenever it can. Therefore, it is not guaranteed that the call will leave the queue. ASTERISK-29909 #close Change-Id: Ic15aa238e23b2884abdcaadff2fda7679e29b7ec
This commit is contained in:
parent
fbde0186c7
commit
2be01ba40b
146
apps/app_queue.c
146
apps/app_queue.c
|
@ -290,6 +290,7 @@
|
||||||
<value name="JOINUNAVAIL" />
|
<value name="JOINUNAVAIL" />
|
||||||
<value name="LEAVEUNAVAIL" />
|
<value name="LEAVEUNAVAIL" />
|
||||||
<value name="CONTINUE" />
|
<value name="CONTINUE" />
|
||||||
|
<value name="WITHDRAW" />
|
||||||
</variable>
|
</variable>
|
||||||
<variable name="ABANDONED">
|
<variable name="ABANDONED">
|
||||||
<para>If the call was not answered by an agent this variable will be TRUE.</para>
|
<para>If the call was not answered by an agent this variable will be TRUE.</para>
|
||||||
|
@ -298,6 +299,9 @@
|
||||||
<variable name="DIALEDPEERNUMBER">
|
<variable name="DIALEDPEERNUMBER">
|
||||||
<para>Resource of the agent that was dialed set on the outbound channel.</para>
|
<para>Resource of the agent that was dialed set on the outbound channel.</para>
|
||||||
</variable>
|
</variable>
|
||||||
|
<variable name="QUEUE_WITHDRAW_INFO">
|
||||||
|
<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>
|
||||||
|
</variable>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</description>
|
</description>
|
||||||
<see-also>
|
<see-also>
|
||||||
|
@ -1057,6 +1061,25 @@
|
||||||
<description>
|
<description>
|
||||||
</description>
|
</description>
|
||||||
</manager>
|
</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">
|
<managerEvent language="en_US" name="QueueParams">
|
||||||
<managerEventInstance class="EVENT_FLAG_AGENT">
|
<managerEventInstance class="EVENT_FLAG_AGENT">
|
||||||
|
@ -1602,6 +1625,7 @@ enum queue_result {
|
||||||
QUEUE_LEAVEUNAVAIL = 5,
|
QUEUE_LEAVEUNAVAIL = 5,
|
||||||
QUEUE_FULL = 6,
|
QUEUE_FULL = 6,
|
||||||
QUEUE_CONTINUE = 7,
|
QUEUE_CONTINUE = 7,
|
||||||
|
QUEUE_WITHDRAW = 8,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
|
@ -1616,6 +1640,7 @@ static const struct {
|
||||||
{ QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },
|
{ QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" },
|
||||||
{ QUEUE_FULL, "FULL" },
|
{ QUEUE_FULL, "FULL" },
|
||||||
{ QUEUE_CONTINUE, "CONTINUE" },
|
{ QUEUE_CONTINUE, "CONTINUE" },
|
||||||
|
{ QUEUE_WITHDRAW, "WITHDRAW" },
|
||||||
};
|
};
|
||||||
|
|
||||||
enum queue_timeout_priority {
|
enum queue_timeout_priority {
|
||||||
|
@ -1684,6 +1709,8 @@ struct queue_ent {
|
||||||
time_t start; /*!< When we started holding */
|
time_t start; /*!< When we started holding */
|
||||||
time_t expire; /*!< When this entry should expire (time out of queue) */
|
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*/
|
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 */
|
struct ast_channel *chan; /*!< Our channel */
|
||||||
AST_LIST_HEAD_NOLOCK(,penalty_rule) qe_rules; /*!< Local copy of the queue's penalty rules */
|
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 */
|
struct penalty_rule *pr; /*!< Pointer to the next penalty rule to implement */
|
||||||
|
@ -5802,6 +5829,13 @@ static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *r
|
||||||
/* This is the holding pen for callers 2 through maxlen */
|
/* This is the holding pen for callers 2 through maxlen */
|
||||||
for (;;) {
|
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)) {
|
if (is_our_turn(qe)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -7622,6 +7656,51 @@ static int change_priority_caller_on_queue(const char *queuename, const char *ca
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*! \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
|
||||||
|
* \retval RES_EXISTS a withdraw request was already sent for this caller (channel) and queue
|
||||||
|
*/
|
||||||
|
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)) {
|
||||||
|
if (qe->withdraw) {
|
||||||
|
ast_debug(1, "Ignoring duplicate withdraw request of caller %s from queue %s\n", caller, queuename);
|
||||||
|
res = RES_EXISTS;
|
||||||
|
} else {
|
||||||
|
ast_debug(1, "Requested withdraw of caller %s from queue %s\n", caller, queuename);
|
||||||
|
/* It is not possible to change the withdraw info by further withdraw requests for this caller (channel)
|
||||||
|
in this queue, so we do not need to worry about a memory leak here. */
|
||||||
|
if (withdraw_info) {
|
||||||
|
qe->withdraw_info = ast_strdup(withdraw_info);
|
||||||
|
}
|
||||||
|
qe->withdraw = 1;
|
||||||
|
res = RES_OKAY;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ao2_unlock(q);
|
||||||
|
queue_unref(q);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int publish_queue_member_pause(struct call_queue *q, struct member *member)
|
static int publish_queue_member_pause(struct call_queue *q, struct member *member)
|
||||||
{
|
{
|
||||||
struct ast_json *json_blob = queue_member_blob_create(q, member);
|
struct ast_json *json_blob = queue_member_blob_create(q, member);
|
||||||
|
@ -8569,6 +8648,13 @@ check_turns:
|
||||||
/* they may dial a digit from the queue context; */
|
/* they may dial a digit from the queue context; */
|
||||||
/* or, they may timeout. */
|
/* 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 */
|
/* Leave if we have exceeded our queuetimeout */
|
||||||
if (qe.expire && (time(NULL) >= qe.expire)) {
|
if (qe.expire && (time(NULL) >= qe.expire)) {
|
||||||
record_abandoned(&qe);
|
record_abandoned(&qe);
|
||||||
|
@ -8596,6 +8682,13 @@ check_turns:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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 */
|
/* Leave if we have exceeded our queuetimeout */
|
||||||
if (qe.expire && (time(NULL) >= qe.expire)) {
|
if (qe.expire && (time(NULL) >= qe.expire)) {
|
||||||
record_abandoned(&qe);
|
record_abandoned(&qe);
|
||||||
|
@ -8670,7 +8763,14 @@ check_turns:
|
||||||
|
|
||||||
stop:
|
stop:
|
||||||
if (res) {
|
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) {
|
if (!qe.handled) {
|
||||||
record_abandoned(&qe);
|
record_abandoned(&qe);
|
||||||
ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "ABANDON",
|
ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "ABANDON",
|
||||||
|
@ -8690,6 +8790,13 @@ stop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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) {
|
||||||
|
ast_free(qe.withdraw_info);
|
||||||
|
qe.withdraw_info = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
/* Don't allow return code > 0 */
|
/* Don't allow return code > 0 */
|
||||||
if (res >= 0) {
|
if (res >= 0) {
|
||||||
res = 0;
|
res = 0;
|
||||||
|
@ -10743,6 +10850,41 @@ static int manager_change_priority_caller_on_queue(struct mansession *s, const s
|
||||||
return 0;
|
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;
|
||||||
|
case RES_EXISTS:
|
||||||
|
astman_send_error(s, m, "Unable to request withdraw from queue: Already requested");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
static char *handle_queue_add_member(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
||||||
|
@ -11472,6 +11614,7 @@ static int unload_module(void)
|
||||||
ast_manager_unregister("QueueReset");
|
ast_manager_unregister("QueueReset");
|
||||||
ast_manager_unregister("QueueMemberRingInUse");
|
ast_manager_unregister("QueueMemberRingInUse");
|
||||||
ast_manager_unregister("QueueChangePriorityCaller");
|
ast_manager_unregister("QueueChangePriorityCaller");
|
||||||
|
ast_manager_unregister("QueueWithdrawCaller");
|
||||||
ast_unregister_application(app_aqm);
|
ast_unregister_application(app_aqm);
|
||||||
ast_unregister_application(app_rqm);
|
ast_unregister_application(app_rqm);
|
||||||
ast_unregister_application(app_pqm);
|
ast_unregister_application(app_pqm);
|
||||||
|
@ -11585,6 +11728,7 @@ static int load_module(void)
|
||||||
err |= ast_manager_register_xml("QueueReload", 0, manager_queue_reload);
|
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("QueueReset", 0, manager_queue_reset);
|
||||||
err |= ast_manager_register_xml("QueueChangePriorityCaller", 0, manager_change_priority_caller_on_queue);
|
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(&queuevar_function);
|
||||||
err |= ast_custom_function_register(&queueexists_function);
|
err |= ast_custom_function_register(&queueexists_function);
|
||||||
err |= ast_custom_function_register(&queuemembercount_function);
|
err |= ast_custom_function_register(&queuemembercount_function);
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
Subject: app_queue
|
||||||
|
|
||||||
|
Added a new AMI action: QueueWithdrawCaller
|
||||||
|
This AMI action makes it possible to withdraw a caller from a queue
|
||||||
|
back to the dialplan. The call will be signaled to leave the queue
|
||||||
|
whenever it can, hence, it not guaranteed that the call will leave
|
||||||
|
the queue.
|
||||||
|
|
||||||
|
Optional custom data can be passed in the request, in the WithdrawInfo
|
||||||
|
parameter. If the call successfully withdrawn the queue,
|
||||||
|
it can be retrieved using the QUEUE_WITHDRAW_INFO variable.
|
||||||
|
|
||||||
|
This can be useful for certain uses, such as dispatching the call
|
||||||
|
to a specific extension.
|
Loading…
Reference in New Issue