Merge in the bridge_construction branch to make the system use the Bridging API.

Breaks many things until they can be reworked.  A partial list:
chan_agent
chan_dahdi, chan_misdn, chan_iax2 native bridging
app_queue
COLP updates
DTMF attended transfers
Protocol attended transfers


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@389378 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Richard Mudgett
2013-05-21 18:00:22 +00:00
parent e1e1cc2dee
commit 3d63833bd6
99 changed files with 19717 additions and 7682 deletions

View File

@@ -0,0 +1,801 @@
/*
* 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 Applications
*
* \author Jonathan Rose <jrose@digium.com>
*/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "res_parking.h"
#include "asterisk/config.h"
#include "asterisk/config_options.h"
#include "asterisk/event.h"
#include "asterisk/utils.h"
#include "asterisk/astobj2.h"
#include "asterisk/features.h"
#include "asterisk/module.h"
#include "asterisk/app.h"
#include "asterisk/say.h"
#include "asterisk/features.h"
#include "asterisk/bridging_basic.h"
/*** DOCUMENTATION
<application name="Park" language="en_US">
<synopsis>
Park yourself.
</synopsis>
<syntax>
<parameter name="parking_lot_name">
<para>Specify in which parking lot to park a call.</para>
<para>The parking lot used is selected in the following order:</para>
<para>1) parking_lot_name option to this application</para>
<para>2) <variable>PARKINGLOT</variable> variable</para>
<para>3) <literal>CHANNEL(parkinglot)</literal> function
(Possibly preset by the channel driver.)</para>
<para>4) Default parking lot.</para>
</parameter>
<parameter name="options">
<para>A list of options for this parked call.</para>
<optionlist>
<option name="r">
<para>Send ringing instead of MOH to the parked call.</para>
</option>
<option name="R">
<para>Randomize the selection of a parking space.</para>
</option>
<option name="s">
<para>Silence announcement of the parking space number.</para>
</option>
<option name="c" argsep=",">
<argument name="context" required="false" />
<argument name="extension" required="false" />
<argument name="priority" required="true" />
<para>If the parking times out, go to this place in the dialplan
instead of where the parking lot defines the call should go.
</para>
</option>
<option name="t">
<argument name="duration" required="true" />
<para>Use a timeout of <literal>duration</literal> seconds instead
of the timeout specified by the parking lot.</para>
</option>
</optionlist>
</parameter>
</syntax>
<description>
<para>Used to park yourself (typically in combination with an attended
transfer to know the parking space).</para>
<para>If you set the <variable>PARKINGEXTEN</variable> variable to a
parking space extension in the parking lot, Park() will attempt to park the
call on that extension. If the extension is already in use then execution
will continue at the next priority.
</para>
</description>
<see-also>
<ref type="application">ParkedCall</ref>
</see-also>
</application>
<application name="ParkedCall" language="en_US">
<synopsis>
Retrieve a parked call.
</synopsis>
<syntax>
<parameter name="parking_lot_name">
<para>Specify from which parking lot to retrieve a parked call.</para>
<para>The parking lot used is selected in the following order:</para>
<para>1) parking_lot_name option</para>
<para>2) <variable>PARKINGLOT</variable> variable</para>
<para>3) <literal>CHANNEL(parkinglot)</literal> function
(Possibly preset by the channel driver.)</para>
<para>4) Default parking lot.</para>
</parameter>
<parameter name="parking_space">
<para>Parking space to retrieve a parked call from.
If not provided then the first available parked call in the
parking lot will be retrieved.</para>
</parameter>
</syntax>
<description>
<para>Used to retrieve a parked call from a parking lot.</para>
<note>
<para>If a parking lot's parkext option is set, then Parking lots
will automatically create and manage dialplan extensions in
the parking lot context. If that is the case then you will not
need to manage parking extensions yourself, just include the
parking context of the parking lot.</para>
</note>
</description>
<see-also>
<ref type="application">Park</ref>
</see-also>
</application>
<application name="ParkAndAnnounce" language="en_US">
<synopsis>
Park and Announce.
</synopsis>
<syntax>
<parameter name="parking_lot_name">
<para>Specify in which parking lot to park a call.</para>
<para>The parking lot used is selected in the following order:</para>
<para>1) parking_lot_name option to this application</para>
<para>2) <variable>PARKINGLOT</variable> variable</para>
<para>3) <literal>CHANNEL(parkinglot)</literal> function
(Possibly preset by the channel driver.)</para>
<para>4) Default parking lot.</para>
</parameter>
<parameter name="options">
<para>A list of options for this parked call.</para>
<optionlist>
<option name="r">
<para>Send ringing instead of MOH to the parked call.</para>
</option>
<option name="R">
<para>Randomize the selection of a parking space.</para>
</option>
<option name="c" argsep=",">
<argument name="context" required="false" />
<argument name="extension" required="false" />
<argument name="priority" required="true" />
<para>If the parking times out, go to this place in the dialplan
instead of where the parking lot defines the call should go.
</para>
</option>
<option name="t">
<argument name="duration" required="true" />
<para>Use a timeout of <literal>duration</literal> seconds instead
of the timeout specified by the parking lot.</para>
</option>
</optionlist>
</parameter>
<parameter name="announce_template" required="true" argsep=":">
<argument name="announce" required="true">
<para>Colon-separated list of files to announce. The word
<literal>PARKED</literal> will be replaced by a say_digits of the extension in which
the call is parked.</para>
</argument>
<argument name="announce1" multiple="true" />
</parameter>
<parameter name="dial" required="true">
<para>The app_dial style resource to call to make the
announcement. Console/dsp calls the console.</para>
</parameter>
</syntax>
<description>
<para>Park a call into the parkinglot and announce the call to another channel.</para>
<para>The variable <variable>PARKEDAT</variable> will contain the parking extension
into which the call was placed. Use with the Local channel to allow the dialplan to make
use of this information.</para>
</description>
<see-also>
<ref type="application">Park</ref>
<ref type="application">ParkedCall</ref>
</see-also>
</application>
***/
/* Park a call */
enum park_args {
OPT_ARG_COMEBACK,
OPT_ARG_TIMEOUT,
OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */
};
enum park_flags {
MUXFLAG_RINGING = (1 << 0),
MUXFLAG_RANDOMIZE = (1 << 1),
MUXFLAG_NOANNOUNCE = (1 << 2),
MUXFLAG_COMEBACK_OVERRIDE = (1 << 3),
MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4),
};
AST_APP_OPTIONS(park_opts, {
AST_APP_OPTION('r', MUXFLAG_RINGING),
AST_APP_OPTION('R', MUXFLAG_RANDOMIZE),
AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE),
AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK),
AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT),
});
static int apply_option_timeout (int *var, char *timeout_arg)
{
if (ast_strlen_zero(timeout_arg)) {
ast_log(LOG_ERROR, "No duration value provided for the timeout ('t') option.\n");
return -1;
}
if (sscanf(timeout_arg, "%d", var) != 1 || *var < 0) {
ast_log(LOG_ERROR, "Duration value provided for timeout ('t') option must be 0 or greater.\n");
return -1;
}
return 0;
}
static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, char **comeback_override, char **lot_name)
{
char *parse;
struct ast_flags flags = { 0 };
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(lot_name);
AST_APP_ARG(options);
AST_APP_ARG(other); /* Any remaining unused arguments */
);
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
if (args.options) {
char *opts[OPT_ARG_ARRAY_SIZE] = { NULL, };
ast_app_parse_options(park_opts, &flags, opts, args.options);
if (ast_test_flag(&flags, MUXFLAG_TIMEOUT_OVERRIDE)) {
if (apply_option_timeout(time_limit, opts[OPT_ARG_TIMEOUT])) {
return -1;
}
}
if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) {
*comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]);
}
if (ast_test_flag(&flags, MUXFLAG_NOANNOUNCE)) {
if (disable_announce) {
*disable_announce = 1;
}
}
if (ast_test_flag(&flags, MUXFLAG_RINGING)) {
*use_ringing = 1;
}
if (ast_test_flag(&flags, MUXFLAG_RANDOMIZE)) {
*randomize = 1;
}
}
if (!ast_strlen_zero(args.lot_name)) {
*lot_name = ast_strdup(args.lot_name);
}
return 0;
}
static void park_common_datastore_destroy(void *data)
{
struct park_common_datastore *datastore = data;
ast_free(datastore->parker_uuid);
ast_free(datastore->comeback_override);
ast_free(datastore);
}
static const struct ast_datastore_info park_common_info = {
.type = "park entry data",
.destroy = park_common_datastore_destroy,
};
static void wipe_park_common_datastore(struct ast_channel *chan)
{
struct ast_datastore *datastore;
ast_channel_lock(chan);
datastore = ast_channel_datastore_find(chan, &park_common_info, NULL);
if (datastore) {
ast_channel_datastore_remove(chan, datastore);
ast_datastore_free(datastore);
}
ast_channel_unlock(chan);
}
static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce)
{
struct ast_datastore *datastore = NULL;
struct park_common_datastore *park_datastore;
wipe_park_common_datastore(parkee);
if (!(datastore = ast_datastore_alloc(&park_common_info, NULL))) {
return -1;
}
if (!(park_datastore = ast_calloc(1, sizeof(*park_datastore)))) {
ast_datastore_free(datastore);
return -1;
}
park_datastore->parker_uuid = ast_strdup(parker_uuid);
park_datastore->randomize = randomize;
park_datastore->time_limit = time_limit;
park_datastore->silence_announce = silence_announce;
if (comeback_override) {
park_datastore->comeback_override = ast_strdup(comeback_override);
}
datastore->data = park_datastore;
ast_channel_lock(parkee);
ast_channel_datastore_add(parkee, datastore);
ast_channel_unlock(parkee);
return 0;
}
void get_park_common_datastore_data(struct ast_channel *parkee, char **parker_uuid, char **comeback_override,
int *randomize, int *time_limit, int *silence_announce)
{
struct ast_datastore *datastore;
struct park_common_datastore *data;
ast_channel_lock(parkee);
if (!(datastore = ast_channel_datastore_find(parkee, &park_common_info, NULL))) {
ast_channel_unlock(parkee);
return;
}
data = datastore->data;
if (!data) {
/* This data should always be populated if this datastore was appended to the channel */
ast_assert(0);
}
*parker_uuid = ast_strdup(data->parker_uuid);
*randomize = data->randomize;
*time_limit = data->time_limit;
*silence_announce = data->silence_announce;
if (data->comeback_override) {
*comeback_override = ast_strdup(data->comeback_override);
}
ast_channel_unlock(parkee);
}
struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *app_data,
int *silence_announcements)
{
int use_ringing = 0;
int randomize = 0;
int time_limit = -1;
char *lot_name;
struct ast_bridge *parking_bridge;
RAII_VAR(char *, comeback_override, NULL, ast_free);
RAII_VAR(char *, lot_name_app_arg, NULL, ast_free);
RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
if (app_data) {
park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg);
}
lot_name = lot_name_app_arg;
/* If the name of the parking lot isn't specified in the arguments, find it based on the channel. */
if (ast_strlen_zero(lot_name)) {
ast_channel_lock(parker);
lot_name = ast_strdupa(find_channel_parking_lot_name(parker));
ast_channel_unlock(parker);
}
lot = parking_lot_find_by_name(lot_name);
if (!lot) {
ast_log(LOG_ERROR, "Could not find parking lot: '%s'\n", lot_name);
return NULL;
}
ao2_lock(lot);
parking_bridge = parking_lot_get_bridge(lot);
ao2_unlock(lot);
if (parking_bridge) {
/* Apply relevant bridge roles and such to the parking channel */
parking_channel_set_roles(parkee, lot, use_ringing);
setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit,
silence_announcements ? *silence_announcements : 0);
return parking_bridge;
}
/* Couldn't get the parking bridge. Epic failure. */
return NULL;
}
/* XXX BUGBUG - determining the parker when transferred to deep park priority
* Currently all parking by the park application is treated as calls parking themselves.
* However, it's possible for calls to be transferred here when the Park application is
* set after the first priority of an extension. In that case, there used to be a variable
* (BLINDTRANSFER) set indicating which channel placed that call here.
*
* If BLINDTRANSFER is set, this channel name will need to be referenced in Park events
* generated by stasis. Ideally we would get a whole channel snapshot and use that for the
* parker, but that would likely require applying the channel snapshot to a channel datastore
* on all transfers. Alternatively just the name of the parking channel could be applied along
* with an indication that it's dead.
*/
int park_app_exec(struct ast_channel *chan, const char *data)
{
RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
struct ast_bridge_features chan_features;
int res;
int silence_announcements = 0;
const char *blind_transfer;
ast_channel_lock(chan);
if ((blind_transfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
blind_transfer = ast_strdupa(blind_transfer);
}
ast_channel_unlock(chan);
/* Handle the common parking setup stuff */
if (!(parking_bridge = park_common_setup(chan, chan, data, &silence_announcements))) {
if (!silence_announcements && !blind_transfer) {
ast_stream_and_wait(chan, "pbx-parkingfailed", "");
}
return 0;
}
/* Initialize bridge features for the channel. */
res = ast_bridge_features_init(&chan_features);
if (res) {
ast_bridge_features_cleanup(&chan_features);
return -1;
}
/* Now for the fun part... park it! */
ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
/*
* If the bridge was broken for a hangup that isn't real, then
* don't run the h extension, because the channel isn't really
* hung up. This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
*/
res = -1;
ast_channel_lock(chan);
if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
res = 0;
}
ast_channel_unlock(chan);
ast_bridge_features_cleanup(&chan_features);
return res;
}
/* Retrieve a parked call */
int parked_call_app_exec(struct ast_channel *chan, const char *data)
{
RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup); /* Parked user being retrieved */
RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
struct ast_bridge *retrieval_bridge;
int res;
int target_space = -1;
struct ast_bridge_features chan_features;
char *parse;
char *lot_name;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(lot_name);
AST_APP_ARG(parking_space);
AST_APP_ARG(other); /* Any remaining unused arguments */
);
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
/* Answer the channel if needed */
if (ast_channel_state(chan) != AST_STATE_UP) {
ast_answer(chan);
}
lot_name = args.lot_name;
/* If the name of the parking lot isn't in the arguments, find it based on the channel. */
if (ast_strlen_zero(lot_name)) {
ast_channel_lock(chan);
lot_name = ast_strdupa(find_channel_parking_lot_name(chan));
ast_channel_unlock(chan);
}
lot = parking_lot_find_by_name(lot_name);
if (!lot) {
ast_log(LOG_ERROR, "Could not find the requested parking lot\n");
ast_stream_and_wait(chan, "pbx-invalidpark", "");
return -1;
}
if (!ast_strlen_zero(args.parking_space)) {
if (sscanf(args.parking_space, "%d", &target_space) != 1 || target_space < 0) {
ast_stream_and_wait(chan, "pbx-invalidpark", "");
ast_log(LOG_ERROR, "value '%s' for parking_space argument is invalid. Must be an integer greater than 0.\n", args.parking_space);
return -1;
}
}
/* Attempt to get the parked user from the parking lot */
pu = parking_lot_retrieve_parked_user(lot, target_space);
if (!pu) {
ast_stream_and_wait(chan, "pbx-invalidpark", "");
return -1;
}
/* The parked call needs to know who is retrieving it before we move it out of the parking bridge */
pu->retriever = ast_channel_snapshot_create(chan);
/* Create bridge */
retrieval_bridge = ast_bridge_basic_new();
if (!retrieval_bridge) {
return -1;
}
/* Move the parkee into the new bridge */
if (ast_bridge_move(retrieval_bridge, lot->parking_bridge, pu->chan, NULL, 0)) {
ast_bridge_destroy(retrieval_bridge);
return -1;
}
/* Initialize our bridge features */
res = ast_bridge_features_init(&chan_features);
if (res) {
ast_bridge_destroy(retrieval_bridge);
ast_bridge_features_cleanup(&chan_features);
return -1;
}
/* Set the features */
parked_call_retrieve_enable_features(chan, lot, AST_FEATURE_FLAG_BYCALLER);
/* If the parkedplay option is set for the caller to hear, play that tone now. */
if (lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLER) {
ast_stream_and_wait(chan, lot->cfg->courtesytone, NULL);
}
/* Now we should try to join the new bridge ourselves... */
ast_bridge_join(retrieval_bridge, chan, NULL, &chan_features, NULL, 1);
ast_bridge_features_cleanup(&chan_features);
return 0;
}
struct park_announce_subscription_data {
char *parkee_uuid;
char *dial_string;
char *announce_string;
};
static void park_announce_subscription_data_destroy(void *data)
{
struct park_announce_subscription_data *pa_data = data;
ast_free(pa_data->parkee_uuid);
ast_free(pa_data->dial_string);
ast_free(pa_data->announce_string);
ast_free(pa_data);
}
static struct park_announce_subscription_data *park_announce_subscription_data_create(const char *parkee_uuid,
const char *dial_string,
const char *announce_string)
{
struct park_announce_subscription_data *pa_data;
if (!(pa_data = ast_calloc(1, sizeof(*pa_data)))) {
return NULL;
}
if (!(pa_data->parkee_uuid = ast_strdup(parkee_uuid))
|| !(pa_data->dial_string = ast_strdup(dial_string))
|| !(pa_data->announce_string = ast_strdup(announce_string))) {
park_announce_subscription_data_destroy(pa_data);
return NULL;
}
return pa_data;
}
static void announce_to_dial(char *dial_string, char *announce_string, int parkingspace, struct ast_channel_snapshot *parkee_snapshot)
{
struct ast_channel *dchan;
struct outgoing_helper oh = { 0, };
int outstate;
struct ast_format_cap *cap_slin = ast_format_cap_alloc_nolock();
char buf[13];
char *dial_tech;
char *cur_announce;
struct ast_format tmpfmt;
dial_tech = strsep(&dial_string, "/");
ast_verb(3, "Dial Tech,String: (%s,%s)\n", dial_tech, dial_string);
if (!cap_slin) {
ast_log(LOG_WARNING, "PARK: Failed to announce park.\n");
goto announce_cleanup;
}
ast_format_cap_add(cap_slin, ast_format_set(&tmpfmt, AST_FORMAT_SLINEAR, 0));
snprintf(buf, sizeof(buf), "%d", parkingspace);
oh.vars = ast_variable_new("_PARKEDAT", buf, "");
dchan = __ast_request_and_dial(dial_tech, cap_slin, NULL, dial_string, 30000,
&outstate,
parkee_snapshot->caller_number,
parkee_snapshot->caller_name,
&oh);
ast_variables_destroy(oh.vars);
if (!dchan) {
ast_log(LOG_WARNING, "PARK: Unable to allocate announce channel.\n");
goto announce_cleanup;
}
ast_verb(4, "Announce Template: %s\n", announce_string);
for (cur_announce = strsep(&announce_string, ":"); cur_announce; cur_announce = strsep(&announce_string, ":")) {
ast_verb(4, "Announce:%s\n", cur_announce);
if (!strcmp(cur_announce, "PARKED")) {
ast_say_digits(dchan, parkingspace, "", ast_channel_language(dchan));
} else {
int dres = ast_streamfile(dchan, cur_announce, ast_channel_language(dchan));
if (!dres) {
dres = ast_waitstream(dchan, "");
} else {
ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", cur_announce, ast_channel_name(dchan));
}
}
}
ast_stopstream(dchan);
ast_hangup(dchan);
announce_cleanup:
cap_slin = ast_format_cap_destroy(cap_slin);
}
static void park_announce_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
{
struct park_announce_subscription_data *pa_data = data;
char *dial_string = pa_data->dial_string;
struct ast_parked_call_payload *payload = stasis_message_data(message);
if (stasis_subscription_final_message(sub, message)) {
park_announce_subscription_data_destroy(data);
return;
}
if (payload->event_type != PARKED_CALL) {
/* We are only concerned with calls parked */
return;
}
if (strcmp(payload->parkee->uniqueid, pa_data->parkee_uuid)) {
/* We are only concerned with the parkee we are subscribed for. */
return;
}
if (!ast_strlen_zero(dial_string)) {
announce_to_dial(dial_string, pa_data->announce_string, payload->parkingspace, payload->parkee);
}
*dial_string = '\0'; /* If we observe this dial string on a second pass, we don't want to do anything with it. */
}
int park_and_announce_app_exec(struct ast_channel *chan, const char *data)
{
struct ast_bridge_features chan_features;
char *parse;
int res;
int silence_announcements = 1;
struct stasis_subscription *parking_subscription;
struct park_announce_subscription_data *pa_data;
RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(lot_name);
AST_APP_ARG(options);
AST_APP_ARG(announce_template);
AST_APP_ARG(dial);
AST_APP_ARG(others);/* Any remaining unused arguments */
);
if (ast_strlen_zero(data)) {
ast_log(LOG_ERROR, "ParkAndAnnounce has required arguments. No arguments were provided.\n");
return -1;
}
parse = ast_strdupa(data);
AST_STANDARD_APP_ARGS(args, parse);
if (ast_strlen_zero(args.announce_template)) {
/* improperly configured arguments for the application */
ast_log(LOG_ERROR, "ParkAndAnnounce requires the announce_template argument.\n");
return -1;
}
if (ast_strlen_zero(args.dial)) {
/* improperly configured arguments */
ast_log(LOG_ERROR, "ParkAndAnnounce requires the dial argument.\n");
return -1;
}
if (!strchr(args.dial, '/')) {
ast_log(LOG_ERROR, "ParkAndAnnounce dial string '%s' is improperly formed.\n", args.dial);
return -1;
}
/* Handle the common parking setup stuff */
if (!(parking_bridge = park_common_setup(chan, chan, data, &silence_announcements))) {
return 0;
}
/* Initialize bridge features for the channel. */
res = ast_bridge_features_init(&chan_features);
if (res) {
ast_bridge_features_cleanup(&chan_features);
return -1;
}
/* subscribe to the parking message so that we can announce once it is parked */
pa_data = park_announce_subscription_data_create(ast_channel_uniqueid(chan), args.dial, args.announce_template);
if (!pa_data) {
return -1;
}
if (!(parking_subscription = stasis_subscribe(ast_parking_topic(), park_announce_update_cb, pa_data))) {
/* Failed to create subscription */
park_announce_subscription_data_destroy(pa_data);
return -1;
}
/* Now for the fun part... park it! */
ast_bridge_join(parking_bridge, chan, NULL, &chan_features, NULL, 0);
/* Toss the subscription since we aren't bridged at this point. */
stasis_unsubscribe(parking_subscription);
/*
* If the bridge was broken for a hangup that isn't real, then
* don't run the h extension, because the channel isn't really
* hung up. This should only happen with AST_SOFTHANGUP_ASYNCGOTO.
*/
res = -1;
ast_channel_lock(chan);
if (ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) {
res = 0;
}
ast_channel_unlock(chan);
ast_bridge_features_cleanup(&chan_features);
return res;
}

View File

@@ -0,0 +1,421 @@
/*
* 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 Parking Bridge Class
*
* \author Jonathan Rose <jrose@digium.com>
*/
#include "asterisk.h"
#include "asterisk/logger.h"
#include "res_parking.h"
#include "asterisk/astobj2.h"
#include "asterisk/features.h"
#include "asterisk/say.h"
struct ast_bridge_parking
{
struct ast_bridge base;
/* private stuff for parking */
struct parking_lot *lot;
};
/*!
* \internal
* \brief ast_bridge parking class destructor
* \since 12.0.0
*
* \param self Bridge to operate upon.
*
* \note XXX Stub... and it might go unused.
*
* \return Nothing
*/
static void bridge_parking_destroy(struct ast_bridge_parking *self)
{
ast_bridge_base_v_table.destroy(&self->base);
}
static void bridge_parking_dissolving(struct ast_bridge_parking *self)
{
struct parking_lot *lot = self->lot;
/* Unlink the parking bridge from the parking lot that owns it */
lot->parking_bridge = NULL;
ao2_ref(lot, -1);
/* Disassociate the bridge from the parking lot as well. */
self->lot = NULL;
ast_bridge_base_v_table.dissolving(&self->base);
}
static void destroy_parked_user(void *obj)
{
struct parked_user *pu = obj;
ao2_cleanup(pu->lot);
pu->lot = NULL;
ao2_cleanup(pu->parker);
pu->parker = NULL;
}
/*!
* \internal
* \since 12
* \brief Construct a parked_user struct assigned to the specified parking lot
*
* \param lot The parking lot we are assigning the user to
* \param parkee The channel being parked
* \param parker The channel performing the park operation (may be the same channel)
* \param use_random_space if true, prioritize using a random parking space instead
* of ${PARKINGEXTEN} and/or automatic assignment from the parking lot
* \param time_limit If using a custom timeout, this should be supplied so that the
* parked_user struct can provide this information for manager events. If <0,
* use the parking lot limit instead.
*
* \retval NULL on failure
* \retval reference to the parked user
*
* \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
*/
static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, struct ast_channel *parker, int use_random_space, int time_limit)
{
struct parked_user *new_parked_user;
int preferred_space = -1; /* Initialize to use parking lot defaults */
int parking_space;
const char *parkingexten;
if (lot->mode == PARKINGLOT_DISABLED) {
ast_log(LOG_NOTICE, "Tried to park in a parking lot that is no longer able to be parked to.\n");
return NULL;
}
new_parked_user = ao2_alloc(sizeof(*new_parked_user), destroy_parked_user);
if (!new_parked_user) {
return NULL;
}
if (use_random_space) {
preferred_space = ast_random() % (lot->cfg->parking_stop - lot->cfg->parking_start + 1);
preferred_space += lot->cfg->parking_start;
} else {
ast_channel_lock(chan);
if ((parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN"))) {
parkingexten = ast_strdupa(parkingexten);
}
ast_channel_unlock(chan);
if (!ast_strlen_zero(parkingexten)) {
if (sscanf(parkingexten, "%30d", &preferred_space) != 1 || preferred_space <= 0) {
ast_log(LOG_WARNING, "PARKINGEXTEN='%s' is not a valid parking space.\n", parkingexten);
ao2_ref(new_parked_user, -1);
return NULL;
}
}
}
/* We need to keep the lot locked between parking_lot_get_space and actually placing it in the lot. Or until we decide not to. */
ao2_lock(lot);
parking_space = parking_lot_get_space(lot, preferred_space);
if (parking_space == -1) {
ast_log(LOG_NOTICE, "Failed to get parking space in lot '%s'. All full.\n", lot->name);
ao2_ref(new_parked_user, -1);
ao2_unlock(lot);
return NULL;
}
lot->next_space = ((parking_space + 1) - lot->cfg->parking_start) % (lot->cfg->parking_stop - lot->cfg->parking_start + 1) + lot->cfg->parking_start;
new_parked_user->chan = chan;
new_parked_user->parking_space = parking_space;
/* Have the parked user take a reference to the parking lot. This reference should be immutable and released at destruction */
new_parked_user->lot = lot;
ao2_ref(lot, +1);
new_parked_user->start = ast_tvnow();
new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime;
new_parked_user->parker = ast_channel_snapshot_create(parker);
if (!new_parked_user->parker) {
ao2_ref(new_parked_user, -1);
ao2_unlock(lot);
return NULL;
}
/* Insert into the parking lot's parked user list. We can unlock the lot now. */
ao2_link(lot->parked_users, new_parked_user);
ao2_unlock(lot);
return new_parked_user;
}
/* TODO CEL events for parking */
/*!
* \internal
* \brief ast_bridge parking push method.
* \since 12.0.0
*
* \param self Bridge to operate upon
* \param bridge_channel Bridge channel to push
* \param swap Bridge channel to swap places with if not NULL
*
* \note On entry, self is already locked
*
* \retval 0 on success
* \retval -1 on failure
*/
static int bridge_parking_push(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap)
{
struct parked_user *pu;
int randomize = 0;
int time_limit = -1;
int silence = 0;
const char *blind_transfer;
RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
RAII_VAR(char *, parker_uuid, NULL, ast_free);
RAII_VAR(char *, comeback_override, NULL, ast_free);
ast_bridge_base_v_table.push(&self->base, bridge_channel, swap);
/* Answer the channel if needed */
if (ast_channel_state(bridge_channel->chan) != AST_STATE_UP) {
ast_answer(bridge_channel->chan);
}
if (swap) {
ao2_lock(swap);
pu = swap->bridge_pvt;
if (!pu) {
/* This should be impossible since the only way a channel can enter in the first place
* is if it has a parked user associated with it */
publish_parked_call_failure(bridge_channel->chan);
ao2_unlock(swap);
return -1;
}
/* Give the swap channel's parked user reference to the incoming channel */
pu->chan = bridge_channel->chan;
bridge_channel->bridge_pvt = pu;
swap->bridge_pvt = NULL;
/* TODO Add a parked call swap message type to relay information about parked channel swaps */
ao2_unlock(swap);
parking_set_duration(bridge_channel->features, pu);
return 0;
}
get_park_common_datastore_data(bridge_channel->chan, &parker_uuid, &comeback_override, &randomize, &time_limit, &silence);
parker = ast_channel_get_by_name(parker_uuid);
/* If the parker and the parkee are the same channel pointer, then the channel entered using
* the park application. It's possible the the blindtransfer channel is still alive (particularly
* when a multichannel bridge is parked), so try to get the real parker if possible. */
ast_channel_lock(bridge_channel->chan);
blind_transfer = S_OR(pbx_builtin_getvar_helper(bridge_channel->chan, "BLINDTRANSFER"),
ast_channel_name(bridge_channel->chan));
if (blind_transfer) {
blind_transfer = ast_strdupa(blind_transfer);
}
ast_channel_unlock(bridge_channel->chan);
if (parker == bridge_channel->chan) {
struct ast_channel *real_parker = ast_channel_get_by_name(blind_transfer);
if (real_parker) {
ao2_cleanup(parker);
parker = real_parker;
}
}
if (!parker) {
return -1;
}
pu = generate_parked_user(self->lot, bridge_channel->chan, parker, randomize, time_limit);
if (!pu) {
publish_parked_call_failure(bridge_channel->chan);
return -1;
}
/* If a comeback_override was provided, set it for the parked user's comeback string. */
if (comeback_override) {
strncpy(pu->comeback, comeback_override, sizeof(pu->comeback));
pu->comeback[sizeof(pu->comeback) - 1] = '\0';
}
/* Generate ParkedCall Stasis Message */
publish_parked_call(pu, PARKED_CALL);
/* If the parkee and the parker are the same and silence_announce isn't set, play the announcement to the parkee */
if (!strcmp(blind_transfer, ast_channel_name(bridge_channel->chan)) && !silence) {
char saynum_buf[16];
snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 0, pu->parking_space);
ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
}
/* Apply parking duration limits */
parking_set_duration(bridge_channel->features, pu);
/* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */
bridge_channel->bridge_pvt = pu;
return 0;
}
/*!
* \internal
* \brief ast_bridge parking pull method.
* \since 12.0.0
*
* \param self Bridge to operate upon.
* \param bridge_channel Bridge channel to pull.
*
* \note On entry, self is already locked.
*
* \return Nothing
*/
static void bridge_parking_pull(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel)
{
RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup);
ast_bridge_base_v_table.pull(&self->base, bridge_channel);
/* Take over the bridge channel's pu reference. It will be released when we are done. */
pu = bridge_channel->bridge_pvt;
bridge_channel->bridge_pvt = NULL;
/* This should only happen if the exiting channel was swapped out */
if (!pu) {
return;
}
/* If we got here without the resolution being set, that's because the call was hung up for some reason without
* timing out or being picked up. There may be some forcible park removals later, but the resolution should be
* handled in those cases */
ao2_lock(pu);
if (pu->resolution == PARK_UNSET) {
pu->resolution = PARK_ABANDON;
}
ao2_unlock(pu);
switch (pu->resolution) {
case PARK_UNSET:
/* This should be impossible now since the resolution is forcibly set to abandon if it was unset at this point. Resolution
isn't allowed to be changed when it isn't currently PARK_UNSET. */
return;
case PARK_ABANDON:
/* Since the call was abandoned without additional handling, we need to issue the give up event and unpark the user. */
publish_parked_call(pu, PARKED_CALL_GIVEUP);
unpark_parked_user(pu);
return;
case PARK_FORCED:
/* PARK_FORCED is currently unused, but it is expected that it would be handled similar to PARK_ANSWERED.
* There is currently no event related to forced parked calls either */
return;
case PARK_ANSWERED:
/* If answered or forced, the channel should be pulled from the bridge as part of that process and unlinked from
* the parking lot afterwards. We do need to apply bridge features though and play the courtesy tone if set. */
publish_parked_call(pu, PARKED_CALL_UNPARKED);
parked_call_retrieve_enable_features(bridge_channel->chan, pu->lot, AST_FEATURE_FLAG_BYCALLEE);
if (pu->lot->cfg->parkedplay & AST_FEATURE_FLAG_BYCALLEE) {
ast_bridge_channel_queue_playfile(bridge_channel, NULL, pu->lot->cfg->courtesytone, NULL);
}
return;
case PARK_TIMEOUT:
/* Timeout is similar to abandon because it simply sets the bridge state to end and doesn't
* actually pull the channel. Because of that, unpark should happen in here. */
publish_parked_call(pu, PARKED_CALL_TIMEOUT);
unpark_parked_user(pu);
return;
}
}
/*!
* \internal
* \brief ast_bridge parking notify_masquerade method.
* \since 12.0.0
*
* \param self Bridge to operate upon.
* \param bridge_channel Bridge channel that was masqueraded.
*
* \note On entry, self is already locked.
* \note XXX Stub... and it will probably go unused.
*
* \return Nothing
*/
static void bridge_parking_notify_masquerade(struct ast_bridge_parking *self, struct ast_bridge_channel *bridge_channel)
{
ast_bridge_base_v_table.notify_masquerade(&self->base, bridge_channel);
}
static void bridge_parking_get_merge_priority(struct ast_bridge_parking *self)
{
ast_bridge_base_v_table.get_merge_priority(&self->base);
}
struct ast_bridge_methods ast_bridge_parking_v_table = {
.name = "parking",
.destroy = (ast_bridge_destructor_fn) bridge_parking_destroy,
.dissolving = (ast_bridge_dissolving_fn) bridge_parking_dissolving,
.push = (ast_bridge_push_channel_fn) bridge_parking_push,
.pull = (ast_bridge_pull_channel_fn) bridge_parking_pull,
.notify_masquerade = (ast_bridge_notify_masquerade_fn) bridge_parking_notify_masquerade,
.get_merge_priority = (ast_bridge_merge_priority_fn) bridge_parking_get_merge_priority,
};
static struct ast_bridge *ast_bridge_parking_init(struct ast_bridge_parking *self, struct parking_lot *bridge_lot)
{
if (!self) {
return NULL;
}
/* If no lot is defined for the bridge, then we aren't allowing the bridge to be initialized. */
if (!bridge_lot) {
ao2_ref(self, -1);
return NULL;
}
/* It doesn't need to be a reference since the bridge only lives as long as the parking lot lives. */
self->lot = bridge_lot;
return &self->base;
}
struct ast_bridge *bridge_parking_new(struct parking_lot *bridge_lot)
{
void *bridge;
bridge = ast_bridge_alloc(sizeof(struct ast_bridge_parking), &ast_bridge_parking_v_table);
bridge = ast_bridge_base_init(bridge, AST_BRIDGE_CAPABILITY_HOLDING,
AST_BRIDGE_FLAG_MERGE_INHIBIT_TO | AST_BRIDGE_FLAG_MERGE_INHIBIT_FROM
| AST_BRIDGE_FLAG_SWAP_INHIBIT_FROM);
bridge = ast_bridge_parking_init(bridge, bridge_lot);
bridge = ast_bridge_register(bridge);
return bridge;
}

View File

@@ -0,0 +1,479 @@
/*
* 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 Parking Bridge DTMF and Interval features
*
* \author Jonathan Rose <jrose@digium.com>
*/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "res_parking.h"
#include "asterisk/utils.h"
#include "asterisk/astobj2.h"
#include "asterisk/logger.h"
#include "asterisk/pbx.h"
#include "asterisk/bridging.h"
#include "asterisk/bridging_features.h"
#include "asterisk/features.h"
#include "asterisk/say.h"
#include "asterisk/datastore.h"
#include "asterisk/stasis.h"
struct parked_subscription_datastore {
struct stasis_subscription *parked_subscription;
};
struct parked_subscription_data {
char *parkee_uuid;
char parker_uuid[0];
};
static void parked_subscription_datastore_destroy(void *data)
{
struct parked_subscription_datastore *subscription_datastore = data;
stasis_unsubscribe(subscription_datastore->parked_subscription);
subscription_datastore->parked_subscription = NULL;
ast_free(subscription_datastore);
}
static const struct ast_datastore_info parked_subscription_info = {
.type = "park subscription",
.destroy = parked_subscription_datastore_destroy,
};
static void wipe_subscription_datastore(struct ast_channel *chan)
{
struct ast_datastore *datastore;
ast_channel_lock(chan);
datastore = ast_channel_datastore_find(chan, &parked_subscription_info, NULL);
if (datastore) {
ast_channel_datastore_remove(chan, datastore);
ast_datastore_free(datastore);
}
ast_channel_unlock(chan);
}
static void parker_parked_call_message_response(struct ast_parked_call_payload *message, struct parked_subscription_data *data,
struct stasis_subscription *sub)
{
const char *parkee_to_act_on = data->parkee_uuid;
char saynum_buf[16];
struct ast_channel_snapshot *parkee_snapshot = message->parkee;
RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
RAII_VAR(struct ast_bridge_channel *, bridge_channel, NULL, ao2_cleanup);
if (strcmp(parkee_to_act_on, parkee_snapshot->uniqueid)) {
return;
}
if (message->event_type != PARKED_CALL && message->event_type != PARKED_CALL_FAILED) {
/* We only care about these two event types */
return;
}
parker = ast_channel_get_by_name(data->parker_uuid);
if (!parker) {
return;
}
ast_channel_lock(parker);
bridge_channel = ast_channel_get_bridge_channel(parker);
ast_channel_unlock(parker);
if (!bridge_channel) {
return;
}
if (message->event_type == PARKED_CALL) {
/* queue the saynum on the bridge channel and hangup */
snprintf(saynum_buf, sizeof(saynum_buf), "%u %u", 1, message->parkingspace);
ast_bridge_channel_queue_playfile(bridge_channel, say_parking_space, saynum_buf, NULL);
wipe_subscription_datastore(bridge_channel->chan);
}
if (message->event_type == PARKED_CALL_FAILED) {
ast_bridge_channel_queue_playfile(bridge_channel, NULL, "pbx-parkingfailed", NULL);
wipe_subscription_datastore(bridge_channel->chan);
}
}
static void parker_update_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message)
{
if (stasis_subscription_final_message(sub, message)) {
ast_free(data);
return;
}
if (stasis_message_type(message) == ast_parked_call_type()) {
struct ast_parked_call_payload *parked_call_message = stasis_message_data(message);
parker_parked_call_message_response(parked_call_message, data, sub);
}
}
static int create_parked_subscription(struct ast_channel *chan, const char *parkee_uuid)
{
struct ast_datastore *datastore;
struct parked_subscription_datastore *parked_datastore;
struct parked_subscription_data *subscription_data;
char *parker_uuid = ast_strdupa(ast_channel_uniqueid(chan));
size_t parker_uuid_size = strlen(parker_uuid) + 1;
/* If there is already a subscription, get rid of it. */
wipe_subscription_datastore(chan);
if (!(datastore = ast_datastore_alloc(&parked_subscription_info, NULL))) {
return -1;
}
if (!(parked_datastore = ast_calloc(1, sizeof(*parked_datastore)))) {
ast_datastore_free(datastore);
return -1;
}
if (!(subscription_data = ast_calloc(1, sizeof(*subscription_data) + parker_uuid_size +
strlen(parkee_uuid) + 1))) {
ast_datastore_free(datastore);
ast_free(parked_datastore);
return -1;
}
subscription_data->parkee_uuid = subscription_data->parker_uuid + parker_uuid_size;
strcpy(subscription_data->parkee_uuid, parkee_uuid);
strcpy(subscription_data->parker_uuid, parker_uuid);
if (!(parked_datastore->parked_subscription = stasis_subscribe(ast_parking_topic(), parker_update_cb, subscription_data))) {
return -1;
}
datastore->data = parked_datastore;
ast_channel_lock(chan);
ast_channel_datastore_add(chan, datastore);
ast_channel_unlock(chan);
return 0;
}
/*!
* \internal
* \brief Helper function that creates an outgoing channel and returns it immediately. This function is nearly
* identical to the dial_transfer function in bridge_builtin_features.c, however it doesn't swap the
* local channel and the channel that instigated the park.
*/
static struct ast_channel *park_local_transfer(struct ast_channel *parker, const char *exten, const char *context)
{
RAII_VAR(struct ast_channel *, parkee_side_2, NULL, ao2_cleanup);
char destination[AST_MAX_EXTENSION + AST_MAX_CONTEXT + 1];
struct ast_channel *parkee;
int cause;
/* Used for side_2 hack */
char *parkee_name;
char *semi_pos;
/* Fill the variable with the extension and context we want to call */
snprintf(destination, sizeof(destination), "%s@%s", exten, context);
/* Now we request that chan_local prepare to call the destination */
parkee = ast_request("Local", ast_channel_nativeformats(parker), parker, destination,
&cause);
if (!parkee) {
return NULL;
}
/* Before we actually dial out let's inherit appropriate information. */
ast_channel_lock_both(parker, parkee);
ast_connected_line_copy_from_caller(ast_channel_connected(parkee), ast_channel_caller(parker));
ast_channel_inherit_variables(parker, parkee);
ast_channel_datastore_inherit(parker, parkee);
ast_channel_unlock(parkee);
ast_channel_unlock(parker);
/* BUGBUG Use Richard's unreal channel stuff here instead of this hack */
parkee_name = ast_strdupa(ast_channel_name(parkee));
semi_pos = strrchr(parkee_name, ';');
if (!semi_pos) {
/* There should always be a semicolon present in the string if is used since it's a local channel. */
ast_assert(0);
return NULL;
}
parkee_name[(semi_pos - parkee_name) + 1] = '2';
parkee_side_2 = ast_channel_get_by_name(parkee_name);
/* We need to have the parker subscribe to the new local channel before hand. */
create_parked_subscription(parker, ast_channel_uniqueid(parkee_side_2));
pbx_builtin_setvar_helper(parkee_side_2, "BLINDTRANSFER", ast_channel_name(parker));
/* Since the above worked fine now we actually call it and return the channel */
if (ast_call(parkee, destination, 0)) {
ast_hangup(parkee);
return NULL;
}
return parkee;
}
static int park_feature_helper(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_exten *park_exten)
{
RAII_VAR(struct ast_channel *, other, NULL, ao2_cleanup);
RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
RAII_VAR(struct parked_user *, pu, NULL, ao2_cleanup);
RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
RAII_VAR(struct ao2_container *, bridge_peers, NULL, ao2_cleanup);
struct ao2_iterator iter;
bridge_peers = ast_bridge_peers(bridge);
if (ao2_container_count(bridge_peers) < 2) {
/* There is nothing to do if there is no one to park. */
return 0;
}
if (ao2_container_count(bridge_peers) > 2) {
/* With a multiparty bridge, we need to do a regular blind transfer. We link the existing bridge to the parking lot with a
* local channel rather than transferring others. */
struct ast_channel *transfer_chan = NULL;
if (!park_exten) {
/* This simply doesn't work. The user attempted to one-touch park the parking lot and we can't originate a local channel
* without knowing an extension to transfer it to.
* XXX However, when parking lots are changed to be able to register extensions then this will be doable. */
ast_log(LOG_ERROR, "Can not one-touch park a multiparty bridge.\n");
return 0;
}
transfer_chan = park_local_transfer(bridge_channel->chan,
ast_get_extension_name(park_exten), ast_get_context_name(ast_get_extension_context(park_exten)));
if (!transfer_chan) {
return 0;
}
if (ast_bridge_impart(bridge_channel->bridge, transfer_chan, NULL, NULL, 1)) {
ast_hangup(transfer_chan);
}
return 0;
}
/* Since neither of the above cases were used, we are doing a simple park with a two party bridge. */
for (iter = ao2_iterator_init(bridge_peers, 0); (other = ao2_iterator_next(&iter)); ao2_ref(other, -1)) {
/* We need the channel that isn't the bridge_channel's channel. */
if (strcmp(ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan))) {
break;
}
}
ao2_iterator_destroy(&iter);
if (!other) {
ast_assert(0);
return -1;
}
/* Subscribe to park messages with the other channel entering */
if (create_parked_subscription(bridge_channel->chan, ast_channel_uniqueid(other))) {
return -1;
}
/* Write the park frame with the intended recipient and other data out to the bridge. */
ast_bridge_channel_write_park(bridge_channel, ast_channel_uniqueid(other), ast_channel_uniqueid(bridge_channel->chan), ast_get_extension_app_data(park_exten));
return 0;
}
static void park_bridge_channel(struct ast_bridge_channel *bridge_channel, const char *uuid_parkee, const char *uuid_parker, const char *app_data)
{
RAII_VAR(struct ast_bridge *, parking_bridge, NULL, ao2_cleanup);
RAII_VAR(struct ast_bridge *, original_bridge, NULL, ao2_cleanup);
RAII_VAR(struct ast_channel *, parker, NULL, ao2_cleanup);
if (strcmp(ast_channel_uniqueid(bridge_channel->chan), uuid_parkee)) {
/* We aren't the parkee, so ignore this action. */
return;
}
parker = ast_channel_get_by_name(uuid_parker);
if (!parker) {
ast_log(LOG_NOTICE, "Channel with uuid %s left before we could start parking the call. Parking canceled.\n", uuid_parker);
publish_parked_call_failure(bridge_channel->chan);
return;
}
if (!(parking_bridge = park_common_setup(bridge_channel->chan, parker, app_data, NULL))) {
publish_parked_call_failure(bridge_channel->chan);
return;
}
pbx_builtin_setvar_helper(bridge_channel->chan, "BLINDTRANSFER", ast_channel_name(parker));
/* bridge_channel must be locked so we can get a reference to the bridge it is currently on */
ao2_lock(bridge_channel);
original_bridge = bridge_channel->bridge;
if (!original_bridge) {
ao2_unlock(bridge_channel);
publish_parked_call_failure(bridge_channel->chan);
return;
}
ao2_ref(original_bridge, +1); /* Cleaned by RAII_VAR */
ao2_unlock(bridge_channel);
if (ast_bridge_move(parking_bridge, original_bridge, bridge_channel->chan, NULL, 1)) {
ast_log(LOG_ERROR, "Failed to move %s into the parking bridge.\n",
ast_channel_name(bridge_channel->chan));
}
}
static int feature_park(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
park_feature_helper(bridge, bridge_channel, NULL);
return 0;
}
static void parking_duration_cb_destroyer(void *hook_pvt)
{
struct parked_user *user = hook_pvt;
ao2_ref(user, -1);
}
/*! \internal
* \brief Interval hook. Pulls a parked call from the parking bridge after the timeout is passed and sets the resolution to timeout.
*
* \param bridge Which bridge the channel was parked in
* \param bridge_channel bridge channel this interval hook is being executed on
* \param hook_pvt A pointer to the parked_user struct associated with the channel is stuffed in here
*/
static int parking_duration_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt)
{
struct parked_user *user = hook_pvt;
struct ast_channel *chan = user->chan;
char *peername;
char parking_space[AST_MAX_EXTENSION];
/* We are still in the bridge, so it's possible for other stuff to mess with the parked call before we leave the bridge
to deal with this, lock the parked user, check and set resolution. */
ao2_lock(user);
if (user->resolution != PARK_UNSET) {
/* Abandon timeout since something else has resolved the parked user before we got to it. */
ao2_unlock(user);
return -1;
}
user->resolution = PARK_TIMEOUT;
ao2_unlock(user);
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
/* Set parking timeout channel variables */
snprintf(parking_space, sizeof(parking_space), "%d", user->parking_space);
pbx_builtin_setvar_helper(chan, "PARKING_SPACE", parking_space);
pbx_builtin_setvar_helper(chan, "PARKINGSLOT", parking_space); /* Deprecated version of PARKING_SPACE */
pbx_builtin_setvar_helper(chan, "PARKEDLOT", user->lot->name);
peername = ast_strdupa(user->parker->name);
flatten_peername(peername);
pbx_builtin_setvar_helper(chan, "PARKER", peername);
/* TODO Dialplan generation for park-dial extensions */
/* async_goto the proper PBX destination - this should happen when we come out of the bridge */
if (!ast_strlen_zero(user->comeback)) {
ast_async_parseable_goto(chan, user->comeback);
} else {
comeback_goto(user, user->lot);
}
return -1;
}
void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload)
{
int numeric_value;
int hangup_after;
if (sscanf(payload, "%u %u", &hangup_after, &numeric_value) != 2) {
/* If say_parking_space is called with a non-numeric string, we have a problem. */
ast_assert(0);
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
return;
}
ast_say_digits(bridge_channel->chan, numeric_value, "", ast_channel_language(bridge_channel->chan));
if (hangup_after) {
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_HANGUP);
}
}
void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user)
{
unsigned int time_limit;
time_limit = user->time_limit * 1000;
if (!time_limit) {
/* There is no duration limit that we need to apply. */
return;
}
/* If the time limit has already been passed, set a really low time limit so we can kick them out immediately. */
time_limit = ast_remaining_ms(user->start, time_limit);
if (time_limit <= 0) {
time_limit = 1;
}
/* The interval hook is going to need a reference to the parked_user */
ao2_ref(user, +1);
if (ast_bridge_interval_hook(features, time_limit,
parking_duration_callback, user, parking_duration_cb_destroyer, 1)) {
ast_log(LOG_ERROR, "Failed to apply duration limits to the parking call.\n");
}
}
void unload_parking_bridge_features(void)
{
ast_bridge_features_unregister(AST_BRIDGE_BUILTIN_PARKCALL);
ast_uninstall_park_blind_xfer_func();
ast_uninstall_bridge_channel_park_func();
}
int load_parking_bridge_features(void)
{
ast_bridge_features_register(AST_BRIDGE_BUILTIN_PARKCALL, feature_park, NULL);
ast_install_park_blind_xfer_func(park_feature_helper);
ast_install_bridge_channel_park_func(park_bridge_channel);
return 0;
}

View File

@@ -0,0 +1,292 @@
/*
* 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 Parking Entry, Exit, and other assorted controls.
*
* \author Jonathan Rose <jrose@digium.com>
*/
#include "asterisk.h"
#include "asterisk/logger.h"
#include "res_parking.h"
#include "asterisk/astobj2.h"
#include "asterisk/utils.h"
#include "asterisk/manager.h"
#include "asterisk/test.h"
#include "asterisk/features.h"
#include "asterisk/bridging_basic.h"
struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot)
{
struct ast_bridge *lot_bridge;
if (lot->parking_bridge) {
ao2_ref(lot->parking_bridge, +1);
return lot->parking_bridge;
}
lot_bridge = bridge_parking_new(lot);
if (!lot_bridge) {
return NULL;
}
/* The parking lot needs a reference to the bridge as well. */
lot->parking_bridge = lot_bridge;
ao2_ref(lot->parking_bridge, +1);
return lot_bridge;
}
void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing)
{
ast_channel_add_bridge_role(chan, "holding_participant");
if (force_ringing) {
ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "ringing");
} else {
ast_channel_set_bridge_role_option(chan, "holding_participant", "idle_mode", "musiconhold");
if (!ast_strlen_zero(lot->cfg->mohclass)) {
ast_channel_set_bridge_role_option(chan, "holding_participant", "moh_class", lot->cfg->mohclass);
}
}
}
struct parking_limits_pvt {
struct parked_user *user;
};
int unpark_parked_user(struct parked_user *pu)
{
if (pu->lot) {
ao2_unlink(pu->lot->parked_users, pu);
parking_lot_remove_if_unused(pu->lot);
return 0;
}
return -1;
}
int parking_lot_get_space(struct parking_lot *lot, int target_override)
{
int original_target;
int current_target;
struct ao2_iterator i;
struct parked_user *user;
int wrap;
if (lot->cfg->parkfindnext) {
/* Use next_space if the lot already has next_space set; otherwise use lot start. */
original_target = lot->next_space ? lot->next_space : lot->cfg->parking_start;
} else {
original_target = lot->cfg->parking_start;
}
if (target_override >= lot->cfg->parking_start && target_override <= lot->cfg->parking_stop) {
original_target = target_override;
}
current_target = original_target;
wrap = lot->cfg->parking_start;
i = ao2_iterator_init(lot->parked_users, 0);
while ((user = ao2_iterator_next(&i))) {
/* Increment the wrap on each pass until we find an empty space */
if (wrap == user->parking_space) {
wrap += 1;
}
if (user->parking_space < current_target) {
/* It's lower than the anticipated target, so we haven't reached the target yet. */
ao2_ref(user, -1);
continue;
}
if (user->parking_space > current_target) {
/* The current target is usable because all items below have been read and the next target is higher than the one we want. */
ao2_ref(user, -1);
break;
}
/* We found one already parked here. */
current_target += 1;
ao2_ref(user, -1);
}
ao2_iterator_destroy(&i);
if (current_target <= lot->cfg->parking_stop) {
return current_target;
}
if (wrap <= lot->cfg->parking_stop) {
return wrap;
}
return -1;
}
static int retrieve_parked_user_targeted(void *obj, void *arg, int flags)
{
int *target = arg;
struct parked_user *user = obj;
if (user->parking_space == *target) {
return CMP_MATCH;
}
return 0;
}
struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target)
{
RAII_VAR(struct parked_user *, user, NULL, ao2_cleanup);
if (target < 0) {
user = ao2_callback(lot->parked_users, 0, NULL, NULL);
} else {
user = ao2_callback(lot->parked_users, 0, retrieve_parked_user_targeted, &target);
}
if (!user) {
return NULL;
}
ao2_lock(user);
if (user->resolution != PARK_UNSET) {
/* Abandon. Something else has resolved the parked user before we got to it. */
ao2_unlock(user);
return NULL;
}
ao2_unlink(lot->parked_users, user);
user->resolution = PARK_ANSWERED;
ao2_unlock(user);
parking_lot_remove_if_unused(user->lot);
/* Bump the ref count by 1 since the RAII_VAR will eat the reference otherwise */
ao2_ref(user, +1);
return user;
}
void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode)
{
/* Enabling features here should be additive to features that are already on the channel. */
struct ast_flags feature_flags = { 0 };
struct ast_flags *existing_features;
ast_channel_lock(chan);
existing_features = ast_bridge_features_ds_get(chan);
if (existing_features) {
feature_flags = *existing_features;
}
if (lot->cfg->parkedcalltransfers & recipient_mode) {
ast_set_flag(&feature_flags, AST_FEATURE_REDIRECT);
ast_set_flag(&feature_flags, AST_FEATURE_ATXFER);
}
if (lot->cfg->parkedcallreparking & recipient_mode) {
ast_set_flag(&feature_flags, AST_FEATURE_PARKCALL);
}
if (lot->cfg->parkedcallhangup & recipient_mode) {
ast_set_flag(&feature_flags, AST_FEATURE_DISCONNECT);
}
if (lot->cfg->parkedcallrecording & recipient_mode) {
ast_set_flag(&feature_flags, AST_FEATURE_AUTOMIXMON);
}
ast_bridge_features_ds_set(chan, &feature_flags);
ast_channel_unlock(chan);
return;
}
void flatten_peername(char *peername)
{
int i;
char *dash;
/* Truncate after the dash */
dash = strrchr(peername, '-');
if (dash) {
*dash = '\0';
}
/* Replace slashes with underscores since slashes are reserved characters for extension matching */
for (i = 0; peername[i]; i++) {
if (peername[i] == '/') {
/* The underscore is the flattest character of all. */
peername[i] = '_';
}
}
}
int comeback_goto(struct parked_user *pu, struct parking_lot *lot)
{
struct ast_channel *chan = pu->chan;
char *peername;
const char *blindtransfer;
ast_channel_lock(chan);
if ((blindtransfer = pbx_builtin_getvar_helper(chan, "BLINDTRANSFER"))) {
blindtransfer = ast_strdupa(blindtransfer);
}
ast_channel_unlock(chan);
peername = blindtransfer ? ast_strdupa(blindtransfer) : ast_strdupa(pu->parker->name);
/* XXX Comeback to origin mode: Generate an extension in park-dial to Dial the peer */
/* Flatten the peername so that it can be used for performing the timeout PBX operations */
flatten_peername(peername);
if (lot->cfg->comebacktoorigin) {
if (ast_exists_extension(chan, PARK_DIAL_CONTEXT, peername, 1, NULL)) {
ast_async_goto(chan, PARK_DIAL_CONTEXT, peername, 1);
return 0;
} else {
ast_log(LOG_ERROR, "Can not start %s at %s,%s,1 because extension does not exist. Terminating call.\n",
ast_channel_name(chan), PARK_DIAL_CONTEXT, peername);
return -1;
}
}
if (ast_exists_extension(chan, lot->cfg->comebackcontext, peername, 1, NULL)) {
ast_async_goto(chan, lot->cfg->comebackcontext, peername, 1);
return 0;
}
if (ast_exists_extension(chan, lot->cfg->comebackcontext, "s", 1, NULL)) {
ast_verb(2, "Could not start %s at %s,%s,1. Using 's@%s' instead.\n", ast_channel_name(chan),
lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
ast_async_goto(chan, lot->cfg->comebackcontext, "s", 1);
return 0;
}
ast_verb(2, "Can not start %s at %s,%s,1 and exten 's@%s' does not exist. Using 's@default'\n",
ast_channel_name(chan),
lot->cfg->comebackcontext, peername, lot->cfg->comebackcontext);
ast_async_goto(chan, "default", "s", 1);
return 0;
}

View File

@@ -0,0 +1,610 @@
/*
* 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"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "res_parking.h"
#include "asterisk/config.h"
#include "asterisk/config_options.h"
#include "asterisk/event.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"
/*** 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>
<managerEvent language="en_US" name="ParkedCall">
<managerEventInstance class="EVENT_FLAG_CALL">
<synopsis>Raised when a channel is parked.</synopsis>
<syntax>
<parameter name="ChannelParkee">
</parameter>
<parameter name="ChannelStateParkee">
<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
</parameter>
<parameter name="ChannelStateDescParkee">
<enumlist>
<enum name="Down"/>
<enum name="Rsrvd"/>
<enum name="OffHook"/>
<enum name="Dialing"/>
<enum name="Ring"/>
<enum name="Ringing"/>
<enum name="Up"/>
<enum name="Busy"/>
<enum name="Dialing Offhook"/>
<enum name="Pre-ring"/>
<enum name="Unknown"/>
</enumlist>
</parameter>
<parameter name="CallerIDNumParkee">
</parameter>
<parameter name="CallerIDNameParkee">
</parameter>
<parameter name="ConnectedLineNumParkee">
</parameter>
<parameter name="ConnectedLineNameParkee">
</parameter>
<parameter name="AccountCodeParkee">
</parameter>
<parameter name="ContextParkee">
</parameter>
<parameter name="ExtenParkee">
</parameter>
<parameter name="PriorityParkee">
</parameter>
<parameter name="UniqueidParkee">
</parameter>
<parameter name="ChannelParker">
</parameter>
<parameter name="ChannelStateParker">
<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
</parameter>
<parameter name="ChannelStateDescParker">
<enumlist>
<enum name="Down"/>
<enum name="Rsrvd"/>
<enum name="OffHook"/>
<enum name="Dialing"/>
<enum name="Ring"/>
<enum name="Ringing"/>
<enum name="Up"/>
<enum name="Busy"/>
<enum name="Dialing Offhook"/>
<enum name="Pre-ring"/>
<enum name="Unknown"/>
</enumlist>
</parameter>
<parameter name="CallerIDNumParker">
</parameter>
<parameter name="CallerIDNameParker">
</parameter>
<parameter name="ConnectedLineNumParker">
</parameter>
<parameter name="ConnectedLineNameParker">
</parameter>
<parameter name="AccountCodeParker">
</parameter>
<parameter name="ContextParker">
</parameter>
<parameter name="ExtenParker">
</parameter>
<parameter name="PriorityParker">
</parameter>
<parameter name="UniqueidParker">
</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>
<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>
<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>
<xi:include xpointer="xpointer(/docs/managerEvent[@name='ParkedCall']/managerEventInstance/syntax/parameter)" />
<parameter name="ChannelRetriever">
</parameter>
<parameter name="ChannelStateRetriever">
<para>A numeric code for the channel's current state, related to ChannelStateDesc</para>
</parameter>
<parameter name="ChannelStateDescRetriever">
<enumlist>
<enum name="Down"/>
<enum name="Rsrvd"/>
<enum name="OffHook"/>
<enum name="Dialing"/>
<enum name="Ring"/>
<enum name="Ringing"/>
<enum name="Up"/>
<enum name="Busy"/>
<enum name="Dialing Offhook"/>
<enum name="Pre-ring"/>
<enum name="Unknown"/>
</enumlist>
</parameter>
<parameter name="CallerIDNumRetriever">
</parameter>
<parameter name="CallerIDNameRetriever">
</parameter>
<parameter name="ConnectedLineNumRetriever">
</parameter>
<parameter name="ConnectedLineNameRetriever">
</parameter>
<parameter name="AccountCodeRetriever">
</parameter>
<parameter name="ContextRetriever">
</parameter>
<parameter name="ExtenRetriever">
</parameter>
<parameter name="PriorityRetriever">
</parameter>
<parameter name="UniqueidRetriever">
</parameter>
</syntax>
</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_parked_call_payload *, payload, NULL, ao2_cleanup);
RAII_VAR(struct ast_channel_snapshot *, parkee_snapshot, NULL, ao2_cleanup);
parkee_snapshot = ast_channel_snapshot_create(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_parked_call_payload *, payload, NULL, ao2_cleanup);
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;
if (!pu->parker) {
return NULL;
}
parkee_snapshot = ast_channel_snapshot_create(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, 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 *, parker_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_suffix(payload->parkee, "Parkee");
if (payload->parker) {
parker_string = ast_manager_build_channel_state_string_suffix(payload->parker, "Parker");
}
if (payload->retriever) {
retriever_string = ast_manager_build_channel_state_string_suffix(payload->retriever, "Retriever");
}
ast_str_set(&out, 0,
"%s" /* parkee channel state */
"%s" /* parker channel state */
"%s" /* retriever channel state (when available) */
"Parkinglot: %s\r\n"
"ParkingSpace: %u\r\n"
"ParkingTimeout: %lu\r\n"
"ParkingDuration: %lu\r\n",
ast_str_buffer(parkee_string),
parker_string ? ast_str_buffer(parker_string) : "",
retriever_string ? ast_str_buffer(retriever_string) : "",
payload->parkinglot,
payload->parkingspace,
payload->timeout,
payload->duration);
return out;
}
static int 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 RESULT_SUCCESS;
}
astman_send_ack(s, m, "Parked calls will follow");
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) {
astman_send_error(s, m, "Failed to retrieve parking data about a parked user.");
return RESULT_FAILURE;
}
parked_call_string = manager_build_parked_call_string(payload);
if (!parked_call_string) {
astman_send_error(s, m, "Failed to retrieve parkingd ata about a parked user.");
return RESULT_FAILURE;
}
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_append(s,
"Event: ParkedCallsComplete\r\n"
"Total: %d\r\n"
"%s"
"\r\n",
total, id_text);
return RESULT_SUCCESS;
}
static int 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 RESULT_SUCCESS;
}
iter_lots = ao2_iterator_init(lot_container, 0);
astman_send_ack(s, m, "Parked calls will follow");
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) {
return RESULT_FAILURE;
}
parked_call_string = manager_build_parked_call_string(payload);
if (!payload) {
return RESULT_FAILURE;
}
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);
}
ao2_iterator_destroy(&iter_lots);
astman_append(s,
"Event: ParkedCallsComplete\r\n"
"Total: %d\r\n"
"%s"
"\r\n",
total, id_text);
return RESULT_SUCCESS;
}
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] = "";
if (!ast_strlen_zero(id)) {
snprintf(id_text, sizeof(id_text), "ActionID: %s\r\n", id);
}
if (!ast_strlen_zero(lot_name)) {
return manager_parking_status_single_lot(s, m, id_text, lot_name);
}
return manager_parking_status_all_lots(s, m, id_text);
}
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;
char *id_text = data;
astman_append(s, "Event: Parkinglot\r\n"
"Name: %s\r\n"
"StartSpace: %d\r\n"
"StopSpace: %d\r\n"
"Timeout: %d\r\n"
"%s" /* The Action ID */
"\r\n",
curlot->name,
curlot->cfg->parking_start,
curlot->cfg->parking_stop,
curlot->cfg->parkingtime,
id_text);
return 0;
}
static int manager_parking_lot_list(struct mansession *s, const struct message *m)
{
const char *id = astman_get_header(m, "ActionID");
char id_text[256] = "";
struct ao2_container *lot_container;
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 -1;
}
astman_send_ack(s, m, "Parking lots will follow");
ao2_callback_data(lot_container, OBJ_MULTIPLE | OBJ_NODATA, manager_append_event_parking_lot_data_cb, s, id_text);
astman_append(s,
"Event: ParkinglotsComplete\r\n"
"%s"
"\r\n",id_text);
return RESULT_SUCCESS;
}
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);
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);
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_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_topic *topic, 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)
{
ast_parking_stasis_init();
if (!parking_sub) {
parking_sub = stasis_subscribe(ast_parking_topic(), parking_event_cb, NULL);
}
}
int load_parking_manager(void)
{
int res;
res = ast_manager_register_xml_core("Parkinglots", 0, manager_parking_lot_list);
res |= ast_manager_register_xml_core("ParkedCalls", 0, manager_parking_status);
/* TODO Add a 'Park' manager action */
parking_manager_enable_stasis();
return res ? -1 : 0;
}
static void parking_manager_disable_stasis(void)
{
parking_sub = stasis_unsubscribe(parking_sub);
ast_parking_stasis_disable();
}
void unload_parking_manager(void)
{
ast_manager_unregister("Parkinglots");
ast_manager_unregister("ParkedCalls");
parking_manager_disable_stasis();
}

199
res/parking/parking_ui.c Normal file
View File

@@ -0,0 +1,199 @@
/*
* 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 CLI commands
*
* \author Jonathan Rose <jrose@digium.com>
*/
#include "asterisk.h"
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "res_parking.h"
#include "asterisk/config.h"
#include "asterisk/config_options.h"
#include "asterisk/event.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"
static void display_parked_call(struct parked_user *user, int fd)
{
ast_cli(fd, " Space: %d\n", user->parking_space);
ast_cli(fd, " Channel: %s\n", ast_channel_name(user->chan));
ast_cli(fd, " Parker: %s\n", user->parker ? user->parker->name : "<unknown>");
ast_cli(fd, "\n");
}
static int display_parked_users_cb(void *obj, void *arg, int flags)
{
int *fd = arg;
struct parked_user *user = obj;
display_parked_call(user, *fd);
return 0;
}
static void display_parking_lot(struct parking_lot *lot, int fd)
{
ast_cli(fd, "Parking Lot: %s\n--------------------------------------------------------------------------\n", lot->name);
ast_cli(fd, "Parking Extension : %s\n", lot->cfg->parkext);
ast_cli(fd, "Parking Context : %s\n", lot->cfg->parking_con);
ast_cli(fd, "Parking Spaces : %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop);
ast_cli(fd, "Parking Time : %u sec\n", lot->cfg->parkingtime);
ast_cli(fd, "Comeback to Origin : %s\n", lot->cfg->comebacktoorigin ? "yes" : "no");
ast_cli(fd, "Comeback Context : %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : "");
ast_cli(fd, "Comeback Dial Time : %u sec\n", lot->cfg->comebackdialtime);
ast_cli(fd, "MusicOnHold Class : %s\n", lot->cfg->mohclass);
ast_cli(fd, "Enabled : %s\n", (lot->mode == PARKINGLOT_DISABLED) ? "no" : "yes");
ast_cli(fd, "\n");
}
static int display_parking_lot_cb(void *obj, void *arg, int flags)
{
int *fd = arg;
struct parking_lot *lot = obj;
display_parking_lot(lot, *fd);
return 0;
}
static void cli_display_parking_lot(int fd, const char *name)
{
RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup);
lot = parking_lot_find_by_name(name);
/* If the parking lot couldn't be found with the search, also abort. */
if (!lot) {
ast_cli(fd, "Could not find parking lot '%s'\n\n", name);
return;
}
display_parking_lot(lot, fd);
ast_cli(fd, "Parked Calls\n------------\n");
if (!ao2_container_count(lot->parked_users)) {
ast_cli(fd, " (none)\n");
ast_cli(fd, "\n\n");
return;
}
ao2_callback(lot->parked_users, OBJ_MULTIPLE | OBJ_NODATA, display_parked_users_cb, &fd);
ast_cli(fd, "\n");
}
static void cli_display_parking_lot_list(int fd)
{
struct ao2_container *lot_container;
lot_container = get_parking_lot_container();
if (!lot_container) {
ast_cli(fd, "Failed to obtain parking lot list.\n\n");
return;
}
ao2_callback(lot_container, OBJ_MULTIPLE | OBJ_NODATA, display_parking_lot_cb, &fd);
ast_cli(fd, "\n");
}
struct parking_lot_complete {
int seeking; /*! Nth match to return. */
int which; /*! Which match currently on. */
};
static int complete_parking_lot_search(void *obj, void *arg, void *data, int flags)
{
struct parking_lot_complete *search = data;
if (++search->which > search->seeking) {
return CMP_MATCH;
}
return 0;
}
static char *complete_parking_lot(const char *word, int seeking)
{
char *ret = NULL;
struct parking_lot *lot;
struct ao2_container *global_lots = get_parking_lot_container();
struct parking_lot_complete search = {
.seeking = seeking,
};
lot = ao2_callback_data(global_lots, ast_strlen_zero(word) ? 0 : OBJ_PARTIAL_KEY,
complete_parking_lot_search, (char *) word, &search);
if (!lot) {
return NULL;
}
ret = ast_strdup(lot->name);
ao2_ref(lot, -1);
return ret;
}
/* \brief command parking show <name> */
static char *handle_show_parking_lot_cmd(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
switch (cmd) {
case CLI_INIT:
e->command = "parking show";
e->usage =
"Usage: parking show [name]\n"
" Shows a list of parking lots or details of a specific parking lot.";
return NULL;
case CLI_GENERATE:
if (a->pos == 2) {
return complete_parking_lot(a->word, a->n);
}
return NULL;
}
ast_cli(a->fd, "\n");
if (a->argc == 2) {
cli_display_parking_lot_list(a->fd);
return CLI_SUCCESS;
}
if (a->argc == 3) {
cli_display_parking_lot(a->fd, a->argv[2]);
return CLI_SUCCESS;
}
return CLI_SHOWUSAGE;
}
static struct ast_cli_entry cli_parking_lot[] = {
AST_CLI_DEFINE(handle_show_parking_lot_cmd, "Show a parking lot or a list of all parking lots."),
};
int load_parking_ui(void)
{
return ast_cli_register_multiple(cli_parking_lot, ARRAY_LEN(cli_parking_lot));
}
void unload_parking_ui(void)
{
ast_cli_unregister_multiple(cli_parking_lot, ARRAY_LEN(cli_parking_lot));
}

436
res/parking/res_parking.h Normal file
View File

@@ -0,0 +1,436 @@
/*
* 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 Resource Internal API
*
* \author Jonathan Rose <jrose@digium.com>
*/
#include "asterisk/pbx.h"
#include "asterisk/bridging.h"
#include "asterisk/parking.h"
#include "asterisk/stasis_channels.h"
#define DEFAULT_PARKING_LOT "default"
#define DEFAULT_PARKING_EXTEN "700"
#define PARK_DIAL_CONTEXT "park-dial"
enum park_call_resolution {
PARK_UNSET = 0, /*! Nothing set a resolution. This should never be observed in practice. */
PARK_ABANDON, /*! The channel for the parked call hung up */
PARK_TIMEOUT, /*! The parked call stayed parked until the parking lot timeout was reached and was removed */
PARK_FORCED, /*! The parked call was forcibly terminated by an unusual means in Asterisk */
PARK_ANSWERED, /*! The parked call was retrieved successfully */
};
enum parked_call_feature_options {
OPT_PARKEDPLAY = 0,
OPT_PARKEDTRANSFERS,
OPT_PARKEDREPARKING,
OPT_PARKEDHANGUP,
OPT_PARKEDRECORDING,
};
enum parking_lot_modes {
PARKINGLOT_NORMAL = 0, /*! The parking lot is configured normally and can accept new calls. Disable on reload if the config isn't replaced.
* valid transitions: PARKINGLOT_DISABLED */
PARKINGLOT_DYNAMIC, /*! The parking lot is a dynamically created parking lot. It can be parked to at any time. Disabled on last parked call leaving.
* valid transitions: PARKINGLOT_DISABLED */
PARKINGLOT_DISABLED, /*! The parking lot is no longer linked to a parking lot in configuration. It can no longer be parked to.
* and it can not be parked to. This mode has no transitions. */
};
struct parking_lot_cfg {
int parking_start; /*!< First space in the parking lot */
int parking_stop; /*!< Last space in the parking lot */
unsigned int parkingtime; /*!< Analogous to parkingtime config option */
unsigned int comebackdialtime; /*!< Analogous to comebackdialtime config option */
unsigned int parkfindnext; /*!< Analogous to parkfindnext config option */
unsigned int parkext_exclusive; /*!< Analogous to parkext_exclusive config option */
unsigned int parkaddhints; /*!< Analogous to parkaddhints config option */
unsigned int comebacktoorigin; /*!< Analogous to comebacktoorigin config option */
int parkedplay; /*!< Analogous to parkedplay config option */
int parkedcalltransfers; /*!< Analogous to parkedcalltransfers config option */
int parkedcallreparking; /*!< Analogous to parkedcallreparking config option */
int parkedcallhangup; /*!< Analogous to parkedcallhangup config option */
int parkedcallrecording; /*!< Analogous to parkedcallrecording config option */
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(name); /*!< Name of the parking lot configuration object */
AST_STRING_FIELD(mohclass); /*!< Analogous to mohclass config option */
AST_STRING_FIELD(parkext); /*!< Analogous to parkext config option */
AST_STRING_FIELD(parking_con); /*!< Analogous to context config option */
AST_STRING_FIELD(comebackcontext); /*!< Analogous to comebackcontext config option */
AST_STRING_FIELD(courtesytone); /*!< Analogous to courtesytone config option */
);
};
struct parking_lot {
int next_space; /*!< When using parkfindnext, which space we should start searching from next time we park */
struct ast_bridge *parking_bridge; /*!< Bridged where parked calls will rest until they are answered or otherwise leave */
struct ao2_container *parked_users; /*!< List of parked users rigidly ordered by their parking space */
struct parking_lot_cfg *cfg; /*!< Reference to configuration object for the parking lot */
enum parking_lot_modes mode; /*!< Whether a parking lot is operational, being reconfigured, primed for deletion, or dynamically created. */
int disable_mark; /*!< On reload, disable this parking lot if it doesn't receive a new configuration. */
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(name); /*!< Name of the parking lot object */
);
};
struct parked_user {
struct ast_channel *chan; /*!< Parked channel */
struct ast_channel_snapshot *parker; /*!< Snapshot of the channel that parked the call at the time of parking */
struct ast_channel_snapshot *retriever; /*!< Snapshot of the channel that retrieves a parked call */
struct timeval start; /*!< When the call was parked */
int parking_space; /*!< Which parking space is used */
char comeback[AST_MAX_CONTEXT]; /*!< Where to go on parking timeout */
unsigned int time_limit; /*!< How long this specific channel may remain in the parking lot before timing out */
struct parking_lot *lot; /*!< Which parking lot the user is parked to */
enum park_call_resolution resolution; /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */
};
/*!
* \since 12
* \brief If a parking lot exists in the parking lot list already, update its status to match the provided
* configuration and return a reference return a reference to it. Otherwise, create a parking lot
* struct based on a parking lot configuration and return a reference to the new one.
*
* \param cfg The configuration being used as a reference to build the parking lot from.
*
* \retval A reference to the new parking lot
* \retval NULL if it was not found and could not be be allocated
*
* \note The parking lot will need to be unreffed if it ever falls out of scope
* \note The parking lot will automatically be added to the parking lot container if needed as part of this process
*/
struct parking_lot *parking_lot_build_or_update(struct parking_lot_cfg *cfg);
/*!
* \since 12
* \brief Remove a parking lot from the usable lists if it is no longer involved in any calls and no configuration currently claims it
*
* \param lot Which parking lot is being checked for elimination
*
* \note This should generally be called when something is happening that could cause a parking lot to die such as a call being unparked or
* a parking lot no longer existing in configurations.
*/
void parking_lot_remove_if_unused(struct parking_lot *lot);
/*!
* \since 12
* \brief Create a new parking bridge
*
* \param bridge_lot Parking lot which the new bridge should be based on
*
* \retval NULL if the bridge can not be created
* \retval Newly created parking bridge
*/
struct ast_bridge *bridge_parking_new(struct parking_lot *bridge_lot);
/*!
* \since 12
* \brief Get a reference to a parking lot's bridge. If it doesn't exist, create it and get a reference.
*
* \param lot Which parking lot we need the bridge from. This parking lot must be locked before calling this function.
*
* \retval A reference to the ast_bridge associated with the parking lot
* \retval NULL if it didn't already have a bridge and one couldn't be created
*
* \note This bridge will need to be unreffed if it ever falls out of scope.
*/
struct ast_bridge *parking_lot_get_bridge(struct parking_lot *lot);
/*!
* \since 12
* \brief Get an available parking space within a parking lot.
*
* \param lot Which parking lot we are getting a space from
* \param target_override If there is a specific slot we want, provide it here and we'll start from that position
*
* \retval -1 if No slot can be found
* \retval integer value of parking space selected
*
* \note lot should be locked before this is called and unlocked only after a parked_user with the space
* returned has been added to the parking lot.
*/
int parking_lot_get_space(struct parking_lot *lot, int target_override);
/*!
* \since 12
* \brief Determine if there is a parked user in a parking space and pull it from the parking lot if there is.
*
* \param lot Parking lot being pulled from
* \param target If < 0 search for the first occupied space in the parking lot
* If >= 0 Only pull from the indicated target
*
* \retval NULL if no parked user could be pulled from the requested parking lot at the requested parking space
* \retval reference to the requested parked user
*
* \note The parked user will be removed from parking lot as part of this process
* \note Remove this reference with ao2_cleanup once it falls out of scope.
*/
struct parked_user *parking_lot_retrieve_parked_user(struct parking_lot *lot, int target);
/*!
* \since 12
* \brief Apply features based on the parking lot feature options
*
* \param chan Which channel's feature set is being modified
* \param lot parking lot which establishes the features used
* \param recipient_mode AST_FEATURE_FLAG_BYCALLER if the user is the retriever
* AST_FEATURE_FLAG_BYCALLEE if the user is the parkee
*/
void parked_call_retrieve_enable_features(struct ast_channel *chan, struct parking_lot *lot, int recipient_mode);
/*!
* \since 12
* \brief Set necessary bridge roles on a channel that is about to enter a parking lot
*
* \param chan Entering channel
* \param lot The parking lot the channel will be entering
* \param force_ringing Use ringing instead of music on hold
*/
void parking_channel_set_roles(struct ast_channel *chan, struct parking_lot *lot, int force_ringing);
/*!
* \since 12
* \brief custom callback function for ast_bridge_channel_queue_playfile which plays a parking space
* and optionally hangs up the call afterwards based on the payload in playfile.
*/
void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload);
/*!
* \since 12
* \brief Setup timeout interval feature on an ast_bridge_features for parking
*
* \param features The ast_bridge_features we are establishing the interval hook on
* \param user The parked_user receiving the timeout duration limits
*/
void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user);
/*!
* \since 12
* \brief Get a pointer to the parking lot container for purposes such as iteration
*
* \retval pointer to the parking lot container.
*/
struct ao2_container *get_parking_lot_container(void);
/*!
* \since 12
* \brief Find a parking lot based on its name
*
* \param lot_name Name of the parking lot sought
*
* \retval The parking lot if found
* \retval NULL if no parking lot with the name specified exists
*
* \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
*/
struct parking_lot *parking_lot_find_by_name(const char *lot_name);
/*!
* \since 12
* \brief Find parking lot name from channel
*
* \param chan The channel we want the parking lot name for
*
* \retval name of the channel's assigned parking lot if it is defined by the channel in some way
* \retval name of the default parking lot if it is not
*
* \note Channel needs to be locked while the returned string is in use.
*/
const char *find_channel_parking_lot_name(struct ast_channel *chan);
/*!
* \since 12
* \brief Flattens a peer name so that it can be written to/found from PBX extensions
*
* \param peername unflattened peer name. This will be flattened in place, so expect it to change.
*/
void flatten_peername(char *peername);
/*!
* \since 12
* \brief Set a channel's position in the PBX after timeout using the parking lot settings
*
* \param pu Parked user who is entering/reentering the PBX
* \param lot Parking lot the user was removed from.
*
* \retval 0 Position set successfully
* \retval -1 Failed to set the position
*/
int comeback_goto(struct parked_user *pu, struct parking_lot *lot);
/*!
* \since 12
* \brief Pull a parked user out of its parking lot. Use this when you don't want to use the parked user afterwards.
* \param user The parked user being pulled.
*
* \retval 0 on success
* \retval -1 if the user didn't have its parking lot set
*/
int unpark_parked_user(struct parked_user *user);
/*!
* \since 12
* \brief Publish a stasis parked call message for the channel indicating failure to park.
*
* \param parkee channel belonging to the failed parkee
*/
void publish_parked_call_failure(struct ast_channel *parkee);
/*!
* \since 12
* \brief Publish a stasis parked call message for a given parked user
*
* \param pu pointer to a parked_user that we are generating the message for
* \param event_type What parked call event type is provoking this message
*/
void publish_parked_call(struct parked_user *pu, enum ast_parked_call_event_type event_type);
/*!
* \since 12
* \brief Function to prepare a channel for parking by determining which parking bridge should
* be used, setting up a park common datastore so that the parking bridge will have access
* to necessary parking information when joining, and applying various bridge roles to the
* channel.
*
* \param parkee The channel being preparred for parking
* \param parker The channel initiating the park; may be the parkee as well
* \param app_data arguments supplied to the Park application. May be NULL.
* \param silence_announcements optional pointer to an integer where we want to store the silence option flag
* this value should be initialized to 0 prior to calling park_common_setup.
*
* \retval reference to a parking bridge if successful
* \retval NULL on failure
*
* \note ao2_cleanup this reference when you are done using it or you'll cause leaks.
*/
struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker,
const char *app_data, int *silence_announcements);
struct park_common_datastore {
char *parker_uuid; /*!< Unique ID of the channel parking the call. */
char *comeback_override; /*!< Optional goto string for where to send the call after we are done */
int randomize; /*!< Pick a parking space to enter on at random */
int time_limit; /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */
int silence_announce; /*!< Used when a call parks itself to keep it from hearing the parked call announcement */
};
/*!
* \since 12
* \brief Function that pulls data from the park common datastore on a channel in order to apply it to
* the parked user struct upon bridging.
*
* \param parkee The channel entering parking with the datastore we are checking
* \param parker_uuid pointer to a string pointer for placing the name of the channel that parked parkee
* \param comeback_override pointer to a string pointer for placing the comeback_override option
* \param randomize integer pointer to an integer for placing the randomize option
* \param time_limit integer pointer to an integer for placing the time limit option
* \param silence_announce pointer to an integer for placing the silence_announcements option
*/
void get_park_common_datastore_data(struct ast_channel *parkee,
char **parker_uuid, char **comeback_override,
int *randomize, int *time_limit, int *silence_announce);
/*!
* \since 12
* \brief Execution function for the parking application
*
* \param chan ast_channel entering the application
* \param data arguments to the application
*
* \retval 0 the application executed in such a way that the channel should proceed in the dial plan
* \retval -1 the channel should no longer proceed through the dial plan
*
* \note this function should only be used to register the parking application and not generally to park calls.
*/
int park_app_exec(struct ast_channel *chan, const char *data);
/*!
* \since 12
* \brief Execution function for the parked call application
*
* \param chan ast_channel entering the application
* \param data arguments to the application
*
* \retval 0 the application executed in such a way that the channel should proceed in the dial plan
* \retval -1 the channel should no longer proceed through the dial plan
*/
int parked_call_app_exec(struct ast_channel *chan, const char *data);
/*!
* \since 12
* \brief Execution function for the park and retrieve application
*
* \param chan ast_channel entering the application
* \param data arguments to the application
*
* \retval 0 the application executed in such a way that the channel should proceed in the dial plan
* \retval -1 the channel should no longer proceed through the dial plan
*
* \note this function should only be used to register the park and announce application and not generally to park and announce.
*/
int park_and_announce_app_exec(struct ast_channel *chan, const char *data);
/*!
* \since 12
* \brief Register CLI commands
*
* \retval 0 if successful
* \retval -1 on failure
*/
int load_parking_ui(void);
/*!
* \since 12
* \brief Unregister CLI commands
*/
void unload_parking_ui(void);
/*!
* \since 12
* \brief Register manager actions and setup subscriptions for stasis events
*/
int load_parking_manager(void);
/*!
* \since 12
* \brief Unregister manager actions and remove subscriptions for stasis events
*/
void unload_parking_manager(void);
/*!
* \since 12
* \brief Register bridge features for parking
*
* \retval 0 on success
* \retval -1 on failure
*/
int load_parking_bridge_features(void);
/*!
* \since 12
* \brief Unregister features registered by load_parking_bridge_features
*/
void unload_parking_bridge_features(void);