From 3d63833bd6c869b7efa383e8dea14be1a6eff998 Mon Sep 17 00:00:00 2001 From: Richard Mudgett Date: Tue, 21 May 2013 18:00:22 +0000 Subject: 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 --- res/parking/parking_applications.c | 801 ++++++++++++++++++++++++++++++++++ res/parking/parking_bridge.c | 421 ++++++++++++++++++ res/parking/parking_bridge_features.c | 479 ++++++++++++++++++++ res/parking/parking_controller.c | 292 +++++++++++++ res/parking/parking_manager.c | 610 ++++++++++++++++++++++++++ res/parking/parking_ui.c | 199 +++++++++ res/parking/res_parking.h | 436 ++++++++++++++++++ 7 files changed, 3238 insertions(+) create mode 100644 res/parking/parking_applications.c create mode 100644 res/parking/parking_bridge.c create mode 100644 res/parking/parking_bridge_features.c create mode 100644 res/parking/parking_controller.c create mode 100644 res/parking/parking_manager.c create mode 100644 res/parking/parking_ui.c create mode 100644 res/parking/res_parking.h (limited to 'res/parking') diff --git a/res/parking/parking_applications.c b/res/parking/parking_applications.c new file mode 100644 index 000000000..097329b93 --- /dev/null +++ b/res/parking/parking_applications.c @@ -0,0 +1,801 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * 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 + */ + +#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 + + + Park yourself. + + + + Specify in which parking lot to park a call. + The parking lot used is selected in the following order: + 1) parking_lot_name option to this application + 2) PARKINGLOT variable + 3) CHANNEL(parkinglot) function + (Possibly preset by the channel driver.) + 4) Default parking lot. + + + A list of options for this parked call. + + + + + + + + + + + Used to park yourself (typically in combination with an attended + transfer to know the parking space). + If you set the PARKINGEXTEN 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. + + + + ParkedCall + + + + + + Retrieve a parked call. + + + + Specify from which parking lot to retrieve a parked call. + The parking lot used is selected in the following order: + 1) parking_lot_name option + 2) PARKINGLOT variable + 3) CHANNEL(parkinglot) function + (Possibly preset by the channel driver.) + 4) Default parking lot. + + + Parking space to retrieve a parked call from. + If not provided then the first available parked call in the + parking lot will be retrieved. + + + + Used to retrieve a parked call from a parking lot. + + 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. + + + + Park + + + + + + Park and Announce. + + + + Specify in which parking lot to park a call. + The parking lot used is selected in the following order: + 1) parking_lot_name option to this application + 2) PARKINGLOT variable + 3) CHANNEL(parkinglot) function + (Possibly preset by the channel driver.) + 4) Default parking lot. + + + A list of options for this parked call. + + + + + + + + + + Colon-separated list of files to announce. The word + PARKED will be replaced by a say_digits of the extension in which + the call is parked. + + + + + The app_dial style resource to call to make the + announcement. Console/dsp calls the console. + + + + Park a call into the parkinglot and announce the call to another channel. + The variable PARKEDAT 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. + + + Park + ParkedCall + + + ***/ + +/* 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; +} diff --git a/res/parking/parking_bridge.c b/res/parking/parking_bridge.c new file mode 100644 index 000000000..b9ceb5203 --- /dev/null +++ b/res/parking/parking_bridge.c @@ -0,0 +1,421 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * 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 + */ + +#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; +} diff --git a/res/parking/parking_bridge_features.c b/res/parking/parking_bridge_features.c new file mode 100644 index 000000000..ddb5e7ff2 --- /dev/null +++ b/res/parking/parking_bridge_features.c @@ -0,0 +1,479 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * 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 + */ + +#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; +} diff --git a/res/parking/parking_controller.c b/res/parking/parking_controller.c new file mode 100644 index 000000000..03d7b8861 --- /dev/null +++ b/res/parking/parking_controller.c @@ -0,0 +1,292 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * 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 + */ +#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; +} diff --git a/res/parking/parking_manager.c b/res/parking/parking_manager.c new file mode 100644 index 000000000..d6a05573f --- /dev/null +++ b/res/parking/parking_manager.c @@ -0,0 +1,610 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * 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 + */ + +#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 + + + Get a list of parking lots + + + + + + List all parking lots as a series of AMI events + + + + + List parked calls. + + + + + If specified, only show parked calls from the parking lot with this name. + + + + List parked calls. + + + + + Raised when a channel is parked. + + + + + A numeric code for the channel's current state, related to ChannelStateDesc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A numeric code for the channel's current state, related to ChannelStateDesc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the parking lot that the parkee is parked in + + + Parking Space that the parkee is parked in + + + Time remaining until the parkee is forcefully removed from parking in seconds + + + Time the parkee has been in the parking bridge (in seconds) + + + + + + + Raised when a channel leaves a parking lot due to reaching the time limit of being parked. + + + + + + + + Raised when a channel leaves a parking lot because it hung up without being answered. + + + + + + + + Raised when a channel leaves a parking lot because it was retrieved from the parking lot and reconnected. + + + + + + A numeric code for the channel's current state, related to ChannelStateDesc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ***/ + +/*! \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(); +} diff --git a/res/parking/parking_ui.c b/res/parking/parking_ui.c new file mode 100644 index 000000000..5b432b689 --- /dev/null +++ b/res/parking/parking_ui.c @@ -0,0 +1,199 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * 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 + */ + +#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 : ""); + 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 */ +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)); +} diff --git a/res/parking/res_parking.h b/res/parking/res_parking.h new file mode 100644 index 000000000..7f66d6e8f --- /dev/null +++ b/res/parking/res_parking.h @@ -0,0 +1,436 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * Jonathan Rose + * + * 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 + */ + +#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); -- cgit v1.2.3