summaryrefslogtreecommitdiff
path: root/res/parking/parking_applications.c
diff options
context:
space:
mode:
Diffstat (limited to 'res/parking/parking_applications.c')
-rw-r--r--res/parking/parking_applications.c801
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;
+}