mirror of
				https://github.com/asterisk/asterisk.git
				synced 2025-10-31 18:55:19 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						 Friendly Automation
						Friendly Automation
					
				
			
			
				
	
			
			
			
						parent
						
							b81db677c9
						
					
				
				
					commit
					5edbc54c54
				
			
							
								
								
									
										146
									
								
								apps/app_queue.c
									
									
									
									
									
								
							
							
						
						
									
										146
									
								
								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> | ||||
| @@ -298,6 +299,9 @@ | ||||
| 				<variable name="DIALEDPEERNUMBER"> | ||||
| 					<para>Resource of the agent that was dialed set on the outbound channel.</para> | ||||
| 				</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> | ||||
| 		</description> | ||||
| 		<see-also> | ||||
| @@ -1057,6 +1061,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"> | ||||
| @@ -1602,6 +1625,7 @@ enum queue_result { | ||||
| 	QUEUE_LEAVEUNAVAIL = 5, | ||||
| 	QUEUE_FULL = 6, | ||||
| 	QUEUE_CONTINUE = 7, | ||||
| 	QUEUE_WITHDRAW = 8, | ||||
| }; | ||||
|  | ||||
| static const struct { | ||||
| @@ -1616,6 +1640,7 @@ static const struct { | ||||
| 	{ QUEUE_LEAVEUNAVAIL, "LEAVEUNAVAIL" }, | ||||
| 	{ QUEUE_FULL, "FULL" }, | ||||
| 	{ QUEUE_CONTINUE, "CONTINUE" }, | ||||
| 	{ QUEUE_WITHDRAW, "WITHDRAW" }, | ||||
| }; | ||||
|  | ||||
| enum queue_timeout_priority { | ||||
| @@ -1684,6 +1709,8 @@ struct queue_ent { | ||||
| 	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 */ | ||||
| @@ -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 */ | ||||
| 	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; | ||||
| 		} | ||||
| @@ -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) | ||||
| { | ||||
| 	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; */ | ||||
| 		/* 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); | ||||
| @@ -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 */ | ||||
| 		if (qe.expire && (time(NULL) >= qe.expire)) { | ||||
| 			record_abandoned(&qe); | ||||
| @@ -8670,7 +8763,14 @@ check_turns: | ||||
|  | ||||
| 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", | ||||
| @@ -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 */ | ||||
| 	if (res >= 0) { | ||||
| 		res = 0; | ||||
| @@ -10743,6 +10850,41 @@ static int manager_change_priority_caller_on_queue(struct mansession *s, const s | ||||
| 	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) | ||||
| @@ -11472,6 +11614,7 @@ static int unload_module(void) | ||||
| 	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); | ||||
| @@ -11585,6 +11728,7 @@ static int load_module(void) | ||||
| 	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); | ||||
|   | ||||
							
								
								
									
										14
									
								
								doc/CHANGES-staging/queue_withdraw_caller.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								doc/CHANGES-staging/queue_withdraw_caller.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
		Reference in New Issue
	
	Block a user