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