mirror of
https://github.com/asterisk/asterisk.git
synced 2025-10-12 15:45:18 +00:00
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:
801
res/parking/parking_applications.c
Normal file
801
res/parking/parking_applications.c
Normal 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;
|
||||
}
|
421
res/parking/parking_bridge.c
Normal file
421
res/parking/parking_bridge.c
Normal 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;
|
||||
}
|
479
res/parking/parking_bridge_features.c
Normal file
479
res/parking/parking_bridge_features.c
Normal 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;
|
||||
}
|
292
res/parking/parking_controller.c
Normal file
292
res/parking/parking_controller.c
Normal 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;
|
||||
}
|
610
res/parking/parking_manager.c
Normal file
610
res/parking/parking_manager.c
Normal 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
199
res/parking/parking_ui.c
Normal 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
436
res/parking/res_parking.h
Normal 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);
|
Reference in New Issue
Block a user