summaryrefslogtreecommitdiff
path: root/main/pickup.c
diff options
context:
space:
mode:
authorMatthew Jordan <mjordan@digium.com>2013-08-02 02:32:44 +0000
committerMatthew Jordan <mjordan@digium.com>2013-08-02 02:32:44 +0000
commit38236e54a84c61194c30c92b923752f0dc816d57 (patch)
treeb27d3b73accfe81703eb5f706d1447a661986f5e /main/pickup.c
parent63a229e3695514bbb15cd5ee4674b6b5bb580f0b (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.c423
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;
+}