diff options
author | Matthew Jordan <mjordan@digium.com> | 2013-08-02 02:32:44 +0000 |
---|---|---|
committer | Matthew Jordan <mjordan@digium.com> | 2013-08-02 02:32:44 +0000 |
commit | 38236e54a84c61194c30c92b923752f0dc816d57 (patch) | |
tree | b27d3b73accfe81703eb5f706d1447a661986f5e /main/pickup.c | |
parent | 63a229e3695514bbb15cd5ee4674b6b5bb580f0b (diff) |
Remove dead code from features.c; refactor pickup code into pickup.c
This patch does the following:
* It moves the pickup code out of features.c and into pickup.c
* It removes the vast majority of dead code out of features.c. In particular,
this includes the parking code.
(issue ASTERISK-22134)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@396060 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'main/pickup.c')
-rw-r--r-- | main/pickup.c | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/main/pickup.c b/main/pickup.c new file mode 100644 index 000000000..611126af1 --- /dev/null +++ b/main/pickup.c @@ -0,0 +1,423 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 1999 - 2013, Digium, Inc. + * Copyright (C) 2012, Russell Bryant + * + * Matt Jordan <mjordan@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 Routines implementing call pickup + * + * \author Matt Jordan <mjordan@digium.com> + */ + +/*! + * \li Call pickup uses the configuration file \ref features.conf + * \addtogroup configuration_file Configuration Files + */ + +/*** MODULEINFO + <support_level>core</support_level> + ***/ + +/*** DOCUMENTATION + <managerEvent language="en_US" name="Pickup"> + <managerEventInstance class="EVENT_FLAG_CALL"> + <synopsis>Raised when a call pickup occurs.</synopsis> + <syntax> + <xi:include xpointer="xpointer(/docs/managerEvent[@name='Newchannel']/managerEventInstance/syntax/parameter)" /> + <parameter name="TargetChannel"/> + <parameter name="TargetChannelState"><para>A numeric code for the channel's current state, related to TargetChannelStateDesc</para></parameter> + <parameter name="TargetChannelStateDesc"> + <enumlist> + <enum name="Down"/> + <enum name="Rsrvd"/> + <enum name="OffHook"/> + <enum name="Dialing"/> + <enum name="Ring"/> + <enum name="Ringing"/> + <enum name="Up"/> + <enum name="Busy"/> + <enum name="Dialing Offhook"/> + <enum name="Pre-ring"/> + <enum name="Unknown"/> + </enumlist> + </parameter> + <parameter name="TargetCallerIDNum"/> + <parameter name="TargetCallerIDName"/> + <parameter name="TargetConnectedLineNum"/> + <parameter name="TargetConnectedLineName"/> + <parameter name="TargetAccountCode"/> + <parameter name="TargetContext"/> + <parameter name="TargetExten"/> + <parameter name="TargetPriority"/> + <parameter name="TargetUniqueid"/> + </syntax> + </managerEventInstance> + </managerEvent> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/pickup.h" +#include "asterisk/channel.h" +#include "asterisk/pbx.h" +#include "asterisk/app.h" +#include "asterisk/callerid.h" +#include "asterisk/causes.h" +#include "asterisk/stasis.h" +#include "asterisk/stasis_channels.h" +#include "asterisk/features_config.h" + +static struct ast_manager_event_blob *call_pickup_to_ami(struct stasis_message *message); + +STASIS_MESSAGE_TYPE_DEFN( + ast_call_pickup_type, + .to_ami = call_pickup_to_ami); + + +/*! + * The presence of this datastore on the channel indicates that + * someone is attemting to pickup or has picked up the channel. + * The purpose is to prevent a race between two channels + * attempting to pickup the same channel. + */ +static const struct ast_datastore_info pickup_active = { + .type = "pickup-active", +}; + +int ast_can_pickup(struct ast_channel *chan) +{ + if (!ast_channel_pbx(chan) && !ast_channel_masq(chan) && !ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE) + && (ast_channel_state(chan) == AST_STATE_RINGING + || ast_channel_state(chan) == AST_STATE_RING + /* + * Check the down state as well because some SIP devices do not + * give 180 ringing when they can just give 183 session progress + * instead. Issue 14005. (Some ISDN switches as well for that + * matter.) + */ + || ast_channel_state(chan) == AST_STATE_DOWN) + && !ast_channel_datastore_find(chan, &pickup_active, NULL)) { + return 1; + } + return 0; +} + +static int find_channel_by_group(void *obj, void *arg, void *data, int flags) +{ + struct ast_channel *target = obj; /*!< Potential pickup target */ + struct ast_channel *chan = arg; /*!< Channel wanting to pickup call */ + + if (chan == target) { + return 0; + } + + ast_channel_lock(target); + if (ast_can_pickup(target)) { + /* Lock both channels. */ + while (ast_channel_trylock(chan)) { + ast_channel_unlock(target); + sched_yield(); + ast_channel_lock(target); + } + + /* + * Both callgroup and namedcallgroup pickup variants are + * matched independently. Checking for named group match is + * done last since it's a more expensive operation. + */ + if ((ast_channel_pickupgroup(chan) & ast_channel_callgroup(target)) + || (ast_namedgroups_intersect(ast_channel_named_pickupgroups(chan), + ast_channel_named_callgroups(target)))) { + struct ao2_container *candidates = data;/*!< Candidate channels found. */ + + /* This is a candidate to pickup */ + ao2_link(candidates, target); + } + ast_channel_unlock(chan); + } + ast_channel_unlock(target); + + return 0; +} + +struct ast_channel *ast_pickup_find_by_group(struct ast_channel *chan) +{ + struct ao2_container *candidates;/*!< Candidate channels found to pickup. */ + struct ast_channel *target;/*!< Potential pickup target */ + + candidates = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_NOLOCK, 1, NULL, NULL); + if (!candidates) { + return NULL; + } + + /* Find all candidate targets by group. */ + ast_channel_callback(find_channel_by_group, chan, candidates, 0); + + /* Find the oldest pickup target candidate */ + target = NULL; + for (;;) { + struct ast_channel *candidate;/*!< Potential new older target */ + struct ao2_iterator iter; + + iter = ao2_iterator_init(candidates, 0); + while ((candidate = ao2_iterator_next(&iter))) { + if (!target) { + /* First target. */ + target = candidate; + continue; + } + if (ast_tvcmp(ast_channel_creationtime(candidate), ast_channel_creationtime(target)) < 0) { + /* We have a new target. */ + ast_channel_unref(target); + target = candidate; + continue; + } + ast_channel_unref(candidate); + } + ao2_iterator_destroy(&iter); + if (!target) { + /* No candidates found. */ + break; + } + + /* The found channel must be locked and ref'd. */ + ast_channel_lock(target); + + /* Recheck pickup ability */ + if (ast_can_pickup(target)) { + /* This is the channel to pickup. */ + break; + } + + /* Someone else picked it up or the call went away. */ + ast_channel_unlock(target); + ao2_unlink(candidates, target); + target = ast_channel_unref(target); + } + ao2_ref(candidates, -1); + + return target; +} + +/*! + * \brief Pickup a call + * \param chan channel that initiated pickup. + * + * Walk list of channels, checking it is not itself, channel is pbx one, + * check that the callgroup for both channels are the same and the channel is ringing. + * Answer calling channel, flag channel as answered on queue, masq channels together. + */ +int ast_pickup_call(struct ast_channel *chan) +{ + struct ast_channel *target;/*!< Potential pickup target */ + int res = -1; + RAII_VAR(struct ast_features_pickup_config *, pickup_cfg, NULL, ao2_cleanup); + const char *pickup_sound; + const char *fail_sound; + + ast_debug(1, "Pickup attempt by %s\n", ast_channel_name(chan)); + ast_channel_lock(chan); + pickup_cfg = ast_get_chan_features_pickup_config(chan); + if (!pickup_cfg) { + ast_log(LOG_ERROR, "Unable to retrieve pickup configuration. Unable to play pickup sounds\n"); + } + pickup_sound = ast_strdupa(pickup_cfg ? pickup_cfg->pickupsound : ""); + fail_sound = ast_strdupa(pickup_cfg ? pickup_cfg->pickupfailsound : ""); + ast_channel_unlock(chan); + + /* The found channel is already locked. */ + target = ast_pickup_find_by_group(chan); + if (target) { + ast_log(LOG_NOTICE, "Pickup %s attempt by %s\n", ast_channel_name(target), ast_channel_name(chan)); + + res = ast_do_pickup(chan, target); + ast_channel_unlock(target); + if (!res) { + if (!ast_strlen_zero(pickup_sound)) { + pbx_builtin_setvar_helper(target, "BRIDGE_PLAY_SOUND", pickup_sound); + } + } else { + ast_log(LOG_WARNING, "Pickup %s failed by %s\n", ast_channel_name(target), ast_channel_name(chan)); + } + target = ast_channel_unref(target); + } + + if (res < 0) { + ast_debug(1, "No call pickup possible... for %s\n", ast_channel_name(chan)); + if (!ast_strlen_zero(fail_sound)) { + ast_answer(chan); + ast_stream_and_wait(chan, fail_sound, ""); + } + } + + return res; +} + +static struct ast_manager_event_blob *call_pickup_to_ami(struct stasis_message *message) +{ + struct ast_multi_channel_blob *contents = stasis_message_data(message); + struct ast_channel_snapshot *chan; + struct ast_channel_snapshot *target; + struct ast_manager_event_blob *res; + + RAII_VAR(struct ast_str *, channel_str, NULL, ast_free); + RAII_VAR(struct ast_str *, target_str, NULL, ast_free); + + chan = ast_multi_channel_blob_get_channel(contents, "channel"); + target = ast_multi_channel_blob_get_channel(contents, "target"); + + ast_assert(chan != NULL && target != NULL); + + if (!(channel_str = ast_manager_build_channel_state_string(chan))) { + return NULL; + } + + if (!(target_str = ast_manager_build_channel_state_string_prefix(target, "Target"))) { + return NULL; + } + + res = ast_manager_event_blob_create(EVENT_FLAG_CALL, "Pickup", + "%s" + "%s", + ast_str_buffer(channel_str), + ast_str_buffer(target_str)); + + return res; +} + +static int send_call_pickup_stasis_message(struct ast_channel *picking_up, struct ast_channel_snapshot *chan, struct ast_channel_snapshot *target) +{ + RAII_VAR(struct ast_multi_channel_blob *, pickup_payload, NULL, ao2_cleanup); + RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup); + + if (!(pickup_payload = ast_multi_channel_blob_create(ast_json_null()))) { + return -1; + } + + ast_multi_channel_blob_add_channel(pickup_payload, "channel", chan); + ast_multi_channel_blob_add_channel(pickup_payload, "target", target); + + if (!(msg = stasis_message_create(ast_call_pickup_type(), pickup_payload))) { + return -1; + } + + stasis_publish(ast_channel_topic(picking_up), msg); + return 0; +} + +int ast_do_pickup(struct ast_channel *chan, struct ast_channel *target) +{ + struct ast_party_connected_line connected_caller; + struct ast_datastore *ds_pickup; + const char *chan_name;/*!< A masquerade changes channel names. */ + const char *target_name;/*!< A masquerade changes channel names. */ + int res = -1; + + RAII_VAR(struct ast_channel_snapshot *, chan_snapshot, NULL, ao2_cleanup); + RAII_VAR(struct ast_channel_snapshot *, target_snapshot, NULL, ao2_cleanup); + + target_name = ast_strdupa(ast_channel_name(target)); + ast_debug(1, "Call pickup on '%s' by '%s'\n", target_name, ast_channel_name(chan)); + + /* Mark the target to block any call pickup race. */ + ds_pickup = ast_datastore_alloc(&pickup_active, NULL); + if (!ds_pickup) { + ast_log(LOG_WARNING, + "Unable to create channel datastore on '%s' for call pickup\n", target_name); + return -1; + } + ast_channel_datastore_add(target, ds_pickup); + + ast_party_connected_line_init(&connected_caller); + ast_party_connected_line_copy(&connected_caller, ast_channel_connected(target)); + ast_channel_unlock(target);/* The pickup race is avoided so we do not need the lock anymore. */ + /* Reset any earlier private connected id representation */ + ast_party_id_reset(&connected_caller.priv); + + connected_caller.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; + if (ast_channel_connected_line_sub(NULL, chan, &connected_caller, 0) && + ast_channel_connected_line_macro(NULL, chan, &connected_caller, 0, 0)) { + ast_channel_update_connected_line(chan, &connected_caller, NULL); + } + ast_party_connected_line_free(&connected_caller); + + ast_channel_lock(chan); + chan_name = ast_strdupa(ast_channel_name(chan)); + ast_connected_line_copy_from_caller(&connected_caller, ast_channel_caller(chan)); + ast_channel_unlock(chan); + connected_caller.source = AST_CONNECTED_LINE_UPDATE_SOURCE_ANSWER; + + if (ast_answer(chan)) { + ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan_name); + goto pickup_failed; + } + + if (ast_queue_control(chan, AST_CONTROL_ANSWER)) { + ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan_name); + goto pickup_failed; + } + + ast_channel_queue_connected_line_update(chan, &connected_caller, NULL); + + /* setting the HANGUPCAUSE so the ringing channel knows this call was not a missed call */ + ast_channel_hangupcause_set(chan, AST_CAUSE_ANSWERED_ELSEWHERE); + + if (!(chan_snapshot = ast_channel_snapshot_create(chan))) { + goto pickup_failed; + } + + if (!(target_snapshot = ast_channel_snapshot_create(target))) { + goto pickup_failed; + } + + if (ast_channel_move(target, chan)) { + ast_log(LOG_WARNING, "Unable to complete call pickup of '%s' with '%s'\n", + chan_name, target_name); + goto pickup_failed; + } + + /* target points to the channel that did the pickup at this point, so use that channel's topic instead of chan */ + send_call_pickup_stasis_message(target, chan_snapshot, target_snapshot); + + res = 0; + +pickup_failed: + ast_channel_lock(target); + if (!ast_channel_datastore_remove(target, ds_pickup)) { + ast_datastore_free(ds_pickup); + } + ast_party_connected_line_free(&connected_caller); + + return res; +} + +/*! \internal \brief Clean up resources on Asterisk shutdown */ +static void pickup_shutdown(void) +{ + STASIS_MESSAGE_TYPE_CLEANUP(ast_call_pickup_type); +} + +int ast_pickup_init(void) +{ + STASIS_MESSAGE_TYPE_INIT(ast_call_pickup_type); + ast_register_atexit(pickup_shutdown); + + return 0; +} |