mirror of https://github.com/asterisk/asterisk
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							727 lines
						
					
					
						
							22 KiB
						
					
					
				
			
		
		
	
	
							727 lines
						
					
					
						
							22 KiB
						
					
					
				| /*
 | |
|  * Asterisk -- An open source telephony toolkit.
 | |
|  *
 | |
|  * Copyright (C) 2013, Digium, Inc.
 | |
|  *
 | |
|  * Jonathan Rose <jrose@digium.com>
 | |
|  *
 | |
|  * See http://www.asterisk.org for more information about
 | |
|  * the Asterisk project. Please do not directly contact
 | |
|  * any of the maintainers of this project for assistance;
 | |
|  * the project provides a web site, mailing lists and IRC
 | |
|  * channels for your use.
 | |
|  *
 | |
|  * This program is free software, distributed under the terms of
 | |
|  * the GNU General Public License Version 2. See the LICENSE file
 | |
|  * at the top of the source tree.
 | |
|  */
 | |
| 
 | |
| /*! \file
 | |
|  *
 | |
|  * \brief Call Parking Manager Actions and Events
 | |
|  *
 | |
|  * \author Jonathan Rose <jrose@digium.com>
 | |
|  */
 | |
| 
 | |
| #include "asterisk.h"
 | |
| 
 | |
| #include "res_parking.h"
 | |
| #include "asterisk/config.h"
 | |
| #include "asterisk/config_options.h"
 | |
| #include "asterisk/utils.h"
 | |
| #include "asterisk/module.h"
 | |
| #include "asterisk/cli.h"
 | |
| #include "asterisk/astobj2.h"
 | |
| #include "asterisk/features.h"
 | |
| #include "asterisk/manager.h"
 | |
| #include "asterisk/bridge.h"
 | |
| #include "asterisk/module.h"
 | |
| 
 | |
| /*** DOCUMENTATION
 | |
| 	<manager name="Parkinglots" language="en_US">
 | |
| 		<synopsis>
 | |
| 			Get a list of parking lots
 | |
| 		</synopsis>
 | |
| 		<syntax>
 | |
| 			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
 | |
| 		</syntax>
 | |
| 		<description>
 | |
| 			<para>List all parking lots as a series of AMI events</para>
 | |
| 		</description>
 | |
| 	</manager>
 | |
| 	<manager name="ParkedCalls" language="en_US">
 | |
| 		<synopsis>
 | |
| 			List parked calls.
 | |
| 		</synopsis>
 | |
| 		<syntax>
 | |
| 			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
 | |
| 			<parameter name="ParkingLot">
 | |
| 				<para>If specified, only show parked calls from the parking lot with this name.</para>
 | |
| 			</parameter>
 | |
| 		</syntax>
 | |
| 		<description>
 | |
| 			<para>List parked calls.</para>
 | |
| 		</description>
 | |
| 	</manager>
 | |
| 	<manager name="Park" language="en_US">
 | |
| 		<synopsis>
 | |
| 			Park a channel.
 | |
| 		</synopsis>
 | |
| 		<syntax>
 | |
| 			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
 | |
| 			<parameter name="Channel" required="true">
 | |
| 				<para>Channel name to park.</para>
 | |
| 			</parameter>
 | |
| 			<parameter name="TimeoutChannel" required="false">
 | |
| 				<para>Channel name to use when constructing the dial string that will be dialed if the parked channel
 | |
| 				times out. If <literal>TimeoutChannel</literal> is in a two party bridge with
 | |
| 				<literal>Channel</literal>, then <literal>TimeoutChannel</literal> will receive an announcement and be
 | |
| 				treated as having parked <literal>Channel</literal> in the same manner as the Park Call DTMF feature.
 | |
| 				</para>
 | |
| 			</parameter>
 | |
| 			<parameter name="AnnounceChannel" required="false">
 | |
| 				<para>If specified, then this channel will receive an announcement when <literal>Channel</literal>
 | |
| 				is parked if <literal>AnnounceChannel</literal> is in a state where it can receive announcements
 | |
| 				(AnnounceChannel must be bridged). <literal>AnnounceChannel</literal> has no bearing on the actual
 | |
| 				state of the parked call.</para>
 | |
| 			</parameter>
 | |
| 			<parameter name="Timeout" required="false">
 | |
| 				<para>Overrides the timeout of the parking lot for this park action. Specified in milliseconds, but will be converted to
 | |
| 					seconds. Use a value of 0 to disable the timeout.
 | |
| 				</para>
 | |
| 			</parameter>
 | |
| 			<parameter name="Parkinglot" required="false">
 | |
| 				<para>The parking lot to use when parking the channel</para>
 | |
| 			</parameter>
 | |
| 			<parameter name="ParkingSpace" required="false">
 | |
| 				<para>The parking space extension in the parking lot.
 | |
| 					If the space is already in use then execution will continue at the next priority.
 | |
| 				</para>
 | |
| 			</parameter>
 | |
| 		</syntax>
 | |
| 		<description>
 | |
| 			<para>Park an arbitrary channel with optional arguments for specifying the parking lot used, how long
 | |
| 				the channel should remain parked, and what dial string to use as the parker if the call times out.
 | |
| 			</para>
 | |
| 		</description>
 | |
| 	</manager>
 | |
| 	<managerEvent language="en_US" name="ParkedCall">
 | |
| 		<managerEventInstance class="EVENT_FLAG_CALL">
 | |
| 			<synopsis>Raised when a channel is parked.</synopsis>
 | |
| 			<syntax>
 | |
| 				<channel_snapshot prefix="Parkee"/>
 | |
| 				<parameter name="ParkerDialString">
 | |
| 					<para>Dial String that can be used to call back the parker on ParkingTimeout.</para>
 | |
| 				</parameter>
 | |
| 				<parameter name="Parkinglot">
 | |
| 					<para>Name of the parking lot that the parkee is parked in</para>
 | |
| 				</parameter>
 | |
| 				<parameter name="ParkingSpace">
 | |
| 					<para>Parking Space that the parkee is parked in</para>
 | |
| 				</parameter>
 | |
| 				<parameter name="ParkingTimeout">
 | |
| 				<para>Time remaining until the parkee is forcefully removed from parking in seconds</para>
 | |
| 				</parameter>
 | |
| 				<parameter name="ParkingDuration">
 | |
| 					<para>Time the parkee has been in the parking bridge (in seconds)</para>
 | |
| 				</parameter>
 | |
| 			</syntax>
 | |
| 		</managerEventInstance>
 | |
| 	</managerEvent>
 | |
| 	<managerEvent language="en_US" name="ParkedCallTimeOut">
 | |
| 		<managerEventInstance class="EVENT_FLAG_CALL">
 | |
| 			<synopsis>Raised when a channel leaves a parking lot due to reaching the time limit of being parked.</synopsis>
 | |
| 			<syntax>
 | |
| 				<channel_snapshot prefix="Parkee"/>
 | |
| 				<channel_snapshot prefix="Parker"/>
 | |
| 				<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
 | |
| 			</syntax>
 | |
| 		</managerEventInstance>
 | |
| 	</managerEvent>
 | |
| 	<managerEvent language="en_US" name="ParkedCallGiveUp">
 | |
| 		<managerEventInstance class="EVENT_FLAG_CALL">
 | |
| 			<synopsis>Raised when a channel leaves a parking lot because it hung up without being answered.</synopsis>
 | |
| 			<syntax>
 | |
| 				<channel_snapshot prefix="Parkee"/>
 | |
| 				<channel_snapshot prefix="Parker"/>
 | |
| 				<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
 | |
| 			</syntax>
 | |
| 		</managerEventInstance>
 | |
| 	</managerEvent>
 | |
| 	<managerEvent language="en_US" name="UnParkedCall">
 | |
| 		<managerEventInstance class="EVENT_FLAG_CALL">
 | |
| 			<synopsis>Raised when a channel leaves a parking lot because it was retrieved from the parking lot and reconnected.</synopsis>
 | |
| 			<syntax>
 | |
| 				<channel_snapshot prefix="Parkee"/>
 | |
| 				<channel_snapshot prefix="Parker"/>
 | |
| 				<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
 | |
| 				<channel_snapshot prefix="Retriever"/>
 | |
| 			</syntax>
 | |
| 		</managerEventInstance>
 | |
| 	</managerEvent>
 | |
| 	<managerEvent language="en_US" name="ParkedCallSwap">
 | |
| 		<managerEventInstance class="EVENT_FLAG_CALL">
 | |
| 			<synopsis>Raised when a channel takes the place of a previously parked channel</synopsis>
 | |
| 			<syntax>
 | |
| 				<channel_snapshot prefix="Parkee"/>
 | |
| 				<channel_snapshot prefix="Parker"/>
 | |
| 				<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
 | |
| 			</syntax>
 | |
| 			<description>
 | |
| 				<para>This event is raised when a channel initially parked in the parking lot
 | |
| 				is swapped out with a different channel. The most common case for this is when
 | |
| 				an attended transfer to a parking lot occurs. The Parkee information in the event
 | |
| 				will indicate the party that was swapped into the parking lot.</para>
 | |
| 			</description>
 | |
| 		</managerEventInstance>
 | |
| 	</managerEvent>
 | |
|  ***/
 | |
| 
 | |
| /*! \brief subscription to the parking lot topic */
 | |
| static struct stasis_subscription *parking_sub;
 | |
| 
 | |
| static struct ast_parked_call_payload *parked_call_payload_from_failure(struct ast_channel *chan)
 | |
| {
 | |
| 	RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
 | |
| 
 | |
| 	ast_channel_lock(chan);
 | |
| 	parkee_snapshot = ast_channel_snapshot_create(chan);
 | |
| 	ast_channel_unlock(chan);
 | |
| 	if (!parkee_snapshot) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	return ast_parked_call_payload_create(PARKED_CALL_FAILED, parkee_snapshot, NULL, NULL, NULL, 0, 0, 0);
 | |
| }
 | |
| 
 | |
| static struct ast_parked_call_payload *parked_call_payload_from_parked_user(struct parked_user *pu, enum ast_parked_call_event_type event_type)
 | |
| {
 | |
| 	RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
 | |
| 	long int timeout;
 | |
| 	long int duration;
 | |
| 	struct timeval now = ast_tvnow();
 | |
| 	const char *lot_name = pu->lot->name;
 | |
| 
 | |
| 	ast_channel_lock(pu->chan);
 | |
| 	parkee_snapshot = ast_channel_snapshot_create(pu->chan);
 | |
| 	ast_channel_unlock(pu->chan);
 | |
| 	if (!parkee_snapshot) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	timeout = pu->start.tv_sec + (long) pu->time_limit - now.tv_sec;
 | |
| 	duration = now.tv_sec - pu->start.tv_sec;
 | |
| 
 | |
| 	return ast_parked_call_payload_create(event_type, parkee_snapshot, pu->parker_dial_string, pu->retriever, lot_name, pu->parking_space, timeout, duration);
 | |
| 
 | |
| }
 | |
| 
 | |
| /*! \brief Builds a manager string based on the contents of a parked call payload */
 | |
| static struct ast_str *manager_build_parked_call_string(const struct ast_parked_call_payload *payload)
 | |
| {
 | |
| 	struct ast_str *out = ast_str_create(1024);
 | |
| 	RAII_VAR(struct ast_str *, parkee_string, NULL, ast_free);
 | |
| 	RAII_VAR(struct ast_str *, retriever_string, NULL, ast_free);
 | |
| 
 | |
| 	if (!out) {
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	parkee_string = ast_manager_build_channel_state_string_prefix(payload->parkee, "Parkee");
 | |
| 	if (!parkee_string) {
 | |
| 		ast_free(out);
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (payload->retriever) {
 | |
| 		retriever_string = ast_manager_build_channel_state_string_prefix(payload->retriever, "Retriever");
 | |
| 		if (!retriever_string) {
 | |
| 			ast_free(out);
 | |
| 			return NULL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ast_str_set(&out, 0,
 | |
| 		"%s" /* parkee channel state */
 | |
| 		"%s" /* retriever channel state (when available) */
 | |
| 		"ParkerDialString: %s\r\n"
 | |
| 		"Parkinglot: %s\r\n"
 | |
| 		"ParkingSpace: %u\r\n"
 | |
| 		"ParkingTimeout: %lu\r\n"
 | |
| 		"ParkingDuration: %lu\r\n",
 | |
| 
 | |
| 		ast_str_buffer(parkee_string),
 | |
| 		retriever_string ? ast_str_buffer(retriever_string) : "",
 | |
| 		payload->parker_dial_string,
 | |
| 		payload->parkinglot,
 | |
| 		payload->parkingspace,
 | |
| 		payload->timeout,
 | |
| 		payload->duration);
 | |
| 
 | |
| 	return out;
 | |
| }
 | |
| 
 | |
| static void manager_parking_status_single_lot(struct mansession *s, const struct message *m, const char *id_text, const char *lot_name)
 | |
| {
 | |
| 	RAII_VAR(struct parking_lot *, curlot, NULL, ao2_cleanup);
 | |
| 	struct parked_user *curuser;
 | |
| 	struct ao2_iterator iter_users;
 | |
| 	int total = 0;
 | |
| 
 | |
| 	curlot = parking_lot_find_by_name(lot_name);
 | |
| 	if (!curlot) {
 | |
| 		astman_send_error(s, m, "Requested parking lot could not be found.");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	astman_send_listack(s, m, "Parked calls will follow", "start");
 | |
| 
 | |
| 	iter_users = ao2_iterator_init(curlot->parked_users, 0);
 | |
| 	while ((curuser = ao2_iterator_next(&iter_users))) {
 | |
| 		RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
 | |
| 		RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
 | |
| 
 | |
| 		payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
 | |
| 		if (!payload) {
 | |
| 			ao2_ref(curuser, -1);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		parked_call_string = manager_build_parked_call_string(payload);
 | |
| 		if (!parked_call_string) {
 | |
| 			ao2_ref(curuser, -1);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		total++;
 | |
| 
 | |
| 		astman_append(s, "Event: ParkedCall\r\n"
 | |
| 			"%s" /* The parked call string */
 | |
| 			"%s" /* The action ID */
 | |
| 			"\r\n",
 | |
| 			ast_str_buffer(parked_call_string),
 | |
| 			id_text);
 | |
| 
 | |
| 		ao2_ref(curuser, -1);
 | |
| 	}
 | |
| 	ao2_iterator_destroy(&iter_users);
 | |
| 
 | |
| 	astman_send_list_complete_start(s, m, "ParkedCallsComplete", total);
 | |
| 	astman_append(s, "Total: %d\r\n", total);
 | |
| 	astman_send_list_complete_end(s);
 | |
| }
 | |
| 
 | |
| static void manager_parking_status_all_lots(struct mansession *s, const struct message *m, const char *id_text)
 | |
| {
 | |
| 	struct parked_user *curuser;
 | |
| 	struct ao2_container *lot_container;
 | |
| 	struct ao2_iterator iter_lots;
 | |
| 	struct ao2_iterator iter_users;
 | |
| 	struct parking_lot *curlot;
 | |
| 	int total = 0;
 | |
| 
 | |
| 	lot_container = get_parking_lot_container();
 | |
| 	if (!lot_container) {
 | |
| 		ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
 | |
| 		astman_send_error(s, m, "Could not create parking lot list");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	astman_send_listack(s, m, "Parked calls will follow", "start");
 | |
| 
 | |
| 	iter_lots = ao2_iterator_init(lot_container, 0);
 | |
| 	while ((curlot = ao2_iterator_next(&iter_lots))) {
 | |
| 		iter_users = ao2_iterator_init(curlot->parked_users, 0);
 | |
| 		while ((curuser = ao2_iterator_next(&iter_users))) {
 | |
| 			RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
 | |
| 			RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
 | |
| 
 | |
| 			payload = parked_call_payload_from_parked_user(curuser, PARKED_CALL);
 | |
| 			if (!payload) {
 | |
| 				ao2_ref(curuser, -1);
 | |
| 				ao2_iterator_destroy(&iter_users);
 | |
| 				ao2_ref(curlot, -1);
 | |
| 				goto abort_list;
 | |
| 			}
 | |
| 
 | |
| 			parked_call_string = manager_build_parked_call_string(payload);
 | |
| 			if (!parked_call_string) {
 | |
| 				ao2_ref(curuser, -1);
 | |
| 				ao2_iterator_destroy(&iter_users);
 | |
| 				ao2_ref(curlot, -1);
 | |
| 				goto abort_list;
 | |
| 			}
 | |
| 
 | |
| 			total++;
 | |
| 
 | |
| 			astman_append(s, "Event: ParkedCall\r\n"
 | |
| 				"%s" /* The parked call string */
 | |
| 				"%s" /* The action ID */
 | |
| 				"\r\n",
 | |
| 				ast_str_buffer(parked_call_string),
 | |
| 				id_text);
 | |
| 
 | |
| 			ao2_ref(curuser, -1);
 | |
| 		}
 | |
| 		ao2_iterator_destroy(&iter_users);
 | |
| 		ao2_ref(curlot, -1);
 | |
| 	}
 | |
| abort_list:
 | |
| 	ao2_iterator_destroy(&iter_lots);
 | |
| 
 | |
| 	astman_send_list_complete_start(s, m, "ParkedCallsComplete", total);
 | |
| 	astman_append(s, "Total: %d\r\n", total);
 | |
| 	astman_send_list_complete_end(s);
 | |
| }
 | |
| 
 | |
| static int manager_parking_status(struct mansession *s, const struct message *m)
 | |
| {
 | |
| 	const char *id = astman_get_header(m, "ActionID");
 | |
| 	const char *lot_name = astman_get_header(m, "ParkingLot");
 | |
| 	char id_text[256];
 | |
| 
 | |
| 	id_text[0] = '\0';
 | |
| 	if (!ast_strlen_zero(id)) {
 | |
| 		snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_strlen_zero(lot_name)) {
 | |
| 		manager_parking_status_single_lot(s, m, id_text, lot_name);
 | |
| 	} else {
 | |
| 		manager_parking_status_all_lots(s, m, id_text);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| struct park_list_data {
 | |
| 	const char *id_text;
 | |
| 	int count;
 | |
| };
 | |
| 
 | |
| static int manager_append_event_parking_lot_data_cb(void *obj, void *arg, void *data, int flags)
 | |
| {
 | |
| 	struct parking_lot *curlot = obj;
 | |
| 	struct mansession *s = arg;
 | |
| 	struct park_list_data *list_data = data;
 | |
| 
 | |
| 	astman_append(s, "Event: Parkinglot\r\n"
 | |
| 		"%s" /* The Action ID */
 | |
| 		"Name: %s\r\n"
 | |
| 		"StartSpace: %d\r\n"
 | |
| 		"StopSpace: %d\r\n"
 | |
| 		"Timeout: %u\r\n"
 | |
| 		"\r\n",
 | |
| 		list_data->id_text,
 | |
| 		curlot->name,
 | |
| 		curlot->cfg->parking_start,
 | |
| 		curlot->cfg->parking_stop,
 | |
| 		curlot->cfg->parkingtime);
 | |
| 	++list_data->count;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int manager_parking_lot_list(struct mansession *s, const struct message *m)
 | |
| {
 | |
| 	const char *id = astman_get_header(m, "ActionID");
 | |
| 	struct ao2_container *lot_container;
 | |
| 	char id_text[256];
 | |
| 	struct park_list_data list_data;
 | |
| 
 | |
| 	id_text[0] = '\0';
 | |
| 	if (!ast_strlen_zero(id)) {
 | |
| 		snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
 | |
| 	}
 | |
| 
 | |
| 	lot_container = get_parking_lot_container();
 | |
| 	if (!lot_container) {
 | |
| 		ast_log(LOG_ERROR, "Failed to obtain parking lot list. Action canceled.\n");
 | |
| 		astman_send_error(s, m, "Could not create parking lot list");
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	astman_send_listack(s, m, "Parking lots will follow", "start");
 | |
| 
 | |
| 	list_data.id_text = id_text;
 | |
| 	list_data.count = 0;
 | |
| 	ao2_callback_data(lot_container, OBJ_MULTIPLE | OBJ_NODATA,
 | |
| 		manager_append_event_parking_lot_data_cb, s, &list_data);
 | |
| 
 | |
| 	astman_send_list_complete_start(s, m, "ParkinglotsComplete", list_data.count);
 | |
| 	astman_send_list_complete_end(s);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void manager_park_unbridged(struct mansession *s, const struct message *m,
 | |
| 		struct ast_channel *chan, const char *parkinglot, int timeout_override)
 | |
| {
 | |
| 	struct ast_bridge *parking_bridge = park_common_setup(chan,
 | |
| 		chan, parkinglot, NULL, 0, 0, timeout_override, 1);
 | |
| 
 | |
| 	if (!parking_bridge) {
 | |
| 		astman_send_error(s, m, "Park action failed\n");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (ast_bridge_add_channel(parking_bridge, chan, NULL, 0, NULL)) {
 | |
| 		astman_send_error(s, m, "Park action failed\n");
 | |
| 		ao2_cleanup(parking_bridge);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	astman_send_ack(s, m, "Park successful\n");
 | |
| 	ao2_cleanup(parking_bridge);
 | |
| }
 | |
| 
 | |
| static void manager_park_bridged(struct mansession *s, const struct message *m,
 | |
| 		struct ast_channel *chan, struct ast_channel *parker_chan,
 | |
| 		const char *parkinglot, int timeout_override)
 | |
| {
 | |
| 	struct ast_bridge_channel *bridge_channel;
 | |
| 	char *app_data;
 | |
| 
 | |
| 	if (timeout_override != -1) {
 | |
| 		if (ast_asprintf(&app_data, "%s,t(%d)", parkinglot, timeout_override) == -1) {
 | |
| 			astman_send_error(s, m, "Park action failed\n");
 | |
| 			return;
 | |
| 		}
 | |
| 	} else {
 | |
| 		if (ast_asprintf(&app_data, "%s", parkinglot) == -1) {
 | |
| 			astman_send_error(s, m, "Park action failed\n");
 | |
| 			return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	ast_channel_lock(parker_chan);
 | |
| 	bridge_channel = ast_channel_get_bridge_channel(parker_chan);
 | |
| 	ast_channel_unlock(parker_chan);
 | |
| 
 | |
| 	if (!bridge_channel) {
 | |
| 		ast_free(app_data);
 | |
| 		astman_send_error(s, m, "Park action failed\n");
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* Subscribe to park messages for the channel being parked */
 | |
| 	if (create_parked_subscription(parker_chan, ast_channel_uniqueid(chan), 1)) {
 | |
| 		ast_free(app_data);
 | |
| 		astman_send_error(s, m, "Park action failed\n");
 | |
| 		ao2_cleanup(bridge_channel);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	ast_bridge_channel_write_park(bridge_channel, ast_channel_uniqueid(chan),
 | |
| 			ast_channel_uniqueid(parker_chan), app_data);
 | |
| 
 | |
| 	ast_free(app_data);
 | |
| 
 | |
| 	astman_send_ack(s, m, "Park successful\n");
 | |
| 	ao2_cleanup(bridge_channel);
 | |
| }
 | |
| 
 | |
| static int manager_park(struct mansession *s, const struct message *m)
 | |
| {
 | |
| 	const char *channel = astman_get_header(m, "Channel");
 | |
| 	const char *timeout_channel = S_OR(astman_get_header(m, "TimeoutChannel"), astman_get_header(m, "Channel2"));
 | |
| 	const char *announce_channel = astman_get_header(m, "AnnounceChannel");
 | |
| 	const char *timeout = astman_get_header(m, "Timeout");
 | |
| 	const char *parkinglot = astman_get_header(m, "Parkinglot");
 | |
| 	const char *parkingspace = astman_get_header(m, "ParkingSpace");
 | |
| 	char buf[BUFSIZ];
 | |
| 	int timeout_override = -1;
 | |
| 
 | |
| 	RAII_VAR(struct ast_channel *, parker_chan, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup);
 | |
| 
 | |
| 	if (ast_strlen_zero(channel)) {
 | |
| 		astman_send_error(s, m, "Channel not specified");
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_strlen_zero(timeout)) {
 | |
| 		if (sscanf(timeout, "%30d", &timeout_override) != 1 || timeout_override < 0) {
 | |
| 			astman_send_error(s, m, "Invalid Timeout value.");
 | |
| 			return 0;
 | |
| 		}
 | |
| 
 | |
| 		if (timeout_override) {
 | |
| 			/* If greater than zero, convert to seconds for internal use. Must be >= 1 second. */
 | |
| 			timeout_override = MAX(1, timeout_override / 1000);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (!(chan = ast_channel_get_by_name(channel))) {
 | |
| 		snprintf(buf, sizeof(buf), "Channel does not exist: %s", channel);
 | |
| 		astman_send_error(s, m, buf);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_strlen_zero(timeout_channel)) {
 | |
| 		ast_channel_lock(chan);
 | |
| 		ast_bridge_set_transfer_variables(chan, timeout_channel, 0);
 | |
| 		ast_channel_unlock(chan);
 | |
| 	}
 | |
| 	
 | |
| 	if (!ast_strlen_zero(parkingspace)) {
 | |
| 		pbx_builtin_setvar_helper(chan, "PARKINGEXTEN", parkingspace);
 | |
| 	}
 | |
| 	
 | |
| 	parker_chan = ast_channel_bridge_peer(chan);
 | |
| 	if (!parker_chan || strcmp(ast_channel_name(parker_chan), timeout_channel)) {
 | |
| 		if (!ast_strlen_zero(announce_channel)) {
 | |
| 			struct ast_channel *announce_chan = ast_channel_get_by_name(announce_channel);
 | |
| 			if (!announce_channel) {
 | |
| 				astman_send_error(s, m, "AnnounceChannel does not exist");
 | |
| 				return 0;
 | |
| 			}
 | |
| 
 | |
| 			create_parked_subscription(announce_chan, ast_channel_uniqueid(chan), 0);
 | |
| 			ast_channel_cleanup(announce_chan);
 | |
| 		}
 | |
| 
 | |
| 		manager_park_unbridged(s, m, chan, parkinglot, timeout_override);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	if (!ast_strlen_zero(announce_channel) && strcmp(announce_channel, timeout_channel)) {
 | |
| 		/* When using an announce_channel in bridge mode, only add the announce channel if it isn't
 | |
| 		 * the same as the timeout channel (which will play announcements anyway) */
 | |
| 		struct ast_channel *announce_chan = ast_channel_get_by_name(announce_channel);
 | |
| 		if (!announce_channel) {
 | |
| 			astman_send_error(s, m, "AnnounceChannel does not exist");
 | |
| 			return 0;
 | |
| 		}
 | |
| 
 | |
| 		create_parked_subscription(announce_chan, ast_channel_uniqueid(chan), 0);
 | |
| 		ast_channel_cleanup(announce_chan);
 | |
| 	}
 | |
| 
 | |
| 	manager_park_bridged(s, m, chan, parker_chan, parkinglot, timeout_override);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void publish_parked_call_failure(struct ast_channel *parkee)
 | |
| {
 | |
| 	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 | |
| 
 | |
| 	if (!ast_parked_call_type()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	payload = parked_call_payload_from_failure(parkee);
 | |
| 	if (!payload) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	msg = stasis_message_create(ast_parked_call_type(), payload);
 | |
| 	if (!msg) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	stasis_publish(ast_parking_topic(), msg);
 | |
| }
 | |
| 
 | |
| void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type)
 | |
| {
 | |
| 	RAII_VAR(struct ast_parked_call_payload *, payload, NULL, ao2_cleanup);
 | |
| 	RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
 | |
| 
 | |
| 	if (!ast_parked_call_type()) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	payload = parked_call_payload_from_parked_user(pu, event_type);
 | |
| 	if (!payload) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	msg = stasis_message_create(ast_parked_call_type(), payload);
 | |
| 	if (!msg) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	stasis_publish(ast_parking_topic(), msg);
 | |
| }
 | |
| 
 | |
| static void parked_call_message_response(struct ast_parked_call_payload *parked_call)
 | |
| {
 | |
| 	char *event_type = "";
 | |
| 	RAII_VAR(struct ast_str *, parked_call_string, NULL, ast_free);
 | |
| 
 | |
| 	switch (parked_call->event_type) {
 | |
| 	case PARKED_CALL:
 | |
| 		event_type = "ParkedCall";
 | |
| 		break;
 | |
| 	case PARKED_CALL_TIMEOUT:
 | |
| 		event_type = "ParkedCallTimeOut";
 | |
| 		break;
 | |
| 	case PARKED_CALL_GIVEUP:
 | |
| 		event_type = "ParkedCallGiveUp";
 | |
| 		break;
 | |
| 	case PARKED_CALL_UNPARKED:
 | |
| 		event_type = "UnParkedCall";
 | |
| 		break;
 | |
| 	case PARKED_CALL_SWAP:
 | |
| 		event_type = "ParkedCallSwap";
 | |
| 		break;
 | |
| 	case PARKED_CALL_FAILED:
 | |
| 		/* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	parked_call_string = manager_build_parked_call_string(parked_call);
 | |
| 	if (!parked_call_string) {
 | |
| 		ast_log(LOG_ERROR, "Failed to issue an AMI event of '%s' in response to a stasis message.\n", event_type);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	manager_event(EVENT_FLAG_CALL, event_type,
 | |
| 			"%s",
 | |
| 			ast_str_buffer(parked_call_string)
 | |
| 		);
 | |
| }
 | |
| 
 | |
| static void parking_event_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message)
 | |
| {
 | |
| 	if (stasis_message_type(message) == ast_parked_call_type()) {
 | |
| 		struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
 | |
| 		parked_call_message_response(parked_call_message);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static void parking_manager_enable_stasis(void)
 | |
| {
 | |
| 	if (!parking_sub) {
 | |
| 		parking_sub = stasis_subscribe(ast_parking_topic(), parking_event_cb, NULL);
 | |
| 		stasis_subscription_accept_message_type(parking_sub, ast_parked_call_type());
 | |
| 		stasis_subscription_set_filter(parking_sub, STASIS_SUBSCRIPTION_FILTER_SELECTIVE);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| int load_parking_manager(void)
 | |
| {
 | |
| 	int res;
 | |
| 
 | |
| 	res = ast_manager_register_xml("Parkinglots", EVENT_FLAG_CALL, manager_parking_lot_list);
 | |
| 	res |= ast_manager_register_xml("ParkedCalls", EVENT_FLAG_CALL, manager_parking_status);
 | |
| 	res |= ast_manager_register_xml("Park", EVENT_FLAG_CALL, manager_park);
 | |
| 	parking_manager_enable_stasis();
 | |
| 	return res ? -1 : 0;
 | |
| }
 | |
| 
 | |
| static void parking_manager_disable_stasis(void)
 | |
| {
 | |
| 	parking_sub = stasis_unsubscribe_and_join(parking_sub);
 | |
| }
 | |
| 
 | |
| void unload_parking_manager(void)
 | |
| {
 | |
| 	ast_manager_unregister("Parkinglots");
 | |
| 	ast_manager_unregister("ParkedCalls");
 | |
| 	ast_manager_unregister("Park");
 | |
| 	parking_manager_disable_stasis();
 | |
| }
 |