[asterisk-commits] mmichelson: branch mmichelson/more_transfer r386936 - in /team/mmichelson/mor...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Tue Apr 30 09:04:51 CDT 2013
Author: mmichelson
Date: Tue Apr 30 09:04:47 2013
New Revision: 386936
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=386936
Log:
Add naive attended transfer implementation.
This is missing a couple of necessary items in order to function
properly:
1) I need the ability for a channel that is removed from a bridge
to run an arbitrary callback. Right now, I've faked this by queuing
a bridge action on a bridged channel.
2) I need the "virtual" channel construct. Right now, I need a local
channel and I need to be able to do operations with both ends of it
(place the ;1 in a bridge and masquerade the ;2 into a channel). Currently
I'm not able to do this.
Modified:
team/mmichelson/more_transfer/include/asterisk/bridging.h
team/mmichelson/more_transfer/include/asterisk/channel.h
team/mmichelson/more_transfer/main/bridging.c
team/mmichelson/more_transfer/main/channel.c
team/mmichelson/more_transfer/main/pbx.c
Modified: team/mmichelson/more_transfer/include/asterisk/bridging.h
URL: http://svnview.digium.com/svn/asterisk/team/mmichelson/more_transfer/include/asterisk/bridging.h?view=diff&rev=386936&r1=386935&r2=386936
==============================================================================
--- team/mmichelson/more_transfer/include/asterisk/bridging.h (original)
+++ team/mmichelson/more_transfer/include/asterisk/bridging.h Tue Apr 30 09:04:47 2013
@@ -223,6 +223,8 @@
AST_BRIDGE_ACTION_RUN_APP,
/*! Bridge channel is to execute a blind transfer. */
AST_BRIDGE_ACTION_BLIND_TRANSFER,
+ /*! Bridge channel is to execute an attended transfer */
+ AST_BRIDGE_ACTION_ATTENDED_TRANSFER,
/*
* Bridge actions put after this comment must never be put onto
Modified: team/mmichelson/more_transfer/include/asterisk/channel.h
URL: http://svnview.digium.com/svn/asterisk/team/mmichelson/more_transfer/include/asterisk/channel.h?view=diff&rev=386936&r1=386935&r2=386936
==============================================================================
--- team/mmichelson/more_transfer/include/asterisk/channel.h (original)
+++ team/mmichelson/more_transfer/include/asterisk/channel.h Tue Apr 30 09:04:47 2013
@@ -4166,4 +4166,51 @@
*/
int ast_channel_is_bridged(const struct ast_channel *chan);
+/*!
+ * \since 12
+ * \brief Gain control of a channel in the system
+ *
+ * The intention of this function is to take a channel that currently
+ * is running in one thread and gain control of it in the current thread.
+ * This can be used to redirect a channel to a different place in the dialplan,
+ * for instance.
+ *
+ * \note This function is NOT intended to be used on bridged channels. If you
+ * need to control a bridged channel, you can set a callback to be called
+ * once the channel exits the bridge, and run your controlling logic in that
+ * callback
+ *
+ * XXX Put name of callback-setting function in above paragraph once it is written
+ *
+ * \note When this function returns successfully, the yankee channel is in a state where
+ * it cannot be used any further. Always use the returned channel instead.
+ *
+ * \param yankee The channel to gain control of
+ * \retval NULL Could not gain control of the channel
+ * \retval non-NULL The channel
+ */
+struct ast_channel *ast_channel_yank(struct ast_channel *yankee);
+
+/*!
+ * \since 12
+ * \brief Move a channel from its current location to a new location
+ *
+ * The intention of this function is to take the source channel and have it
+ * take the place of the destination channel. Any application running on the
+ * destination channel will now be running on the source channel instead.
+ *
+ * \note This function is NOT intended to be used on bridged channels. If you
+ * wish to move a channel into a bridge, you can use ast_bridge_impart() and
+ * ast_bridge_join() and swap a channel out if desired.
+ *
+ * \note When this function returns succesfully, the source channel is in a
+ * state where its continued use is unreliable.
+ *
+ * \param dest The place to move the source channel
+ * \param source The channel to move
+ * \retval 0 Success
+ * \retval non-zero Failure
+ */
+int ast_channel_move(struct ast_channel *dest, struct ast_channel *source);
+
#endif /* _ASTERISK_CHANNEL_H */
Modified: team/mmichelson/more_transfer/main/bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/mmichelson/more_transfer/main/bridging.c?view=diff&rev=386936&r1=386935&r2=386936
==============================================================================
--- team/mmichelson/more_transfer/main/bridging.c (original)
+++ team/mmichelson/more_transfer/main/bridging.c Tue Apr 30 09:04:47 2013
@@ -1969,6 +1969,30 @@
{
ast_async_goto(bridge_channel->chan, blind_data->context, blind_data->exten, 1);
bridge_handle_hangup(bridge_channel);
+}
+
+static void bridge_channel_attended_transfer(struct ast_bridge_channel *bridge_channel,
+ const char *unbridged_chan_name)
+{
+ RAII_VAR(struct ast_channel *, chan_unbridged, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel *, chan_bridged, NULL, ao2_cleanup);
+
+ chan_unbridged = ast_channel_get_by_name(unbridged_chan_name);
+ if (!chan_unbridged) {
+ /* Dang, it disappeared somehow */
+ return;
+ }
+
+ {
+ SCOPED_CHANNELLOCK(lock, bridge_channel);
+ chan_bridged = bridge_channel->chan;
+ if (!chan_bridged) {
+ return;
+ }
+ ao2_ref(chan_bridged, +1);
+ }
+
+ ast_channel_move(chan_unbridged, chan_bridged);
}
/*!
@@ -2027,6 +2051,9 @@
case AST_BRIDGE_ACTION_BLIND_TRANSFER:
bridge_channel_blind_transfer(bridge_channel, action->data.ptr);
break;
+ case AST_BRIDGE_ACTION_ATTENDED_TRANSFER:
+ bridge_channel_attended_transfer(bridge_channel, action->data.ptr);
+ break;
default:
break;
}
@@ -4761,6 +4788,26 @@
return AST_BRIDGE_TRANSFER_SUCCESS;
}
+static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *chan_bridged,
+ struct ast_channel *chan_unbridged, struct ast_bridge *bridge)
+{
+ /* This needs to do the following:
+ * 1) Create a local channel
+ * 2) Masquerade the local ;2 channel into the chan_unbridged channel
+ * 3) Add the local ;1 channel to the bridge (swapping the chan_bridged out).
+ *
+ * The problem is that we've got a bit of a dicey situation with the local
+ * channel for now. When we request a local channel, all we have is the ;1 channel.
+ * We don't have the ability to masquerade the ;2 side the way we'd like. For now
+ * this function is a stub;
+ */
+
+ /* XXX For now, transferring an entire bridge to an unbridged channel is invalid until
+ * the local channel problem described above is cleared up
+ */
+ return AST_BRIDGE_TRANSFER_INVALID;
+}
+
/*!
* \internal
* \brief Get the transferee channel
@@ -4833,6 +4880,31 @@
/* XXX Why doesn't this function return success/failure? */
ast_bridge_channel_queue_action_data(transferee_bridge_channel,
AST_BRIDGE_ACTION_BLIND_TRANSFER, &blind_data, sizeof(blind_data));
+
+ return 0;
+}
+
+static int bridge_channel_queue_attended_transfer(struct ast_channel *transferee,
+ struct ast_channel *unbridged_chan)
+{
+ RAII_VAR(struct ast_bridge_channel *, transferee_bridge_channel, NULL, ao2_cleanup);
+ char unbridged_chan_name[AST_CHANNEL_NAME];
+
+ {
+ SCOPED_LOCK(lock, transferee, ast_channel_lock, ast_channel_unlock);
+ transferee_bridge_channel = ast_channel_internal_bridge_channel(transferee);
+ if (!transferee_bridge_channel) {
+ return -1;
+ }
+ ao2_ref(transferee_bridge_channel, +1);
+ }
+
+ ast_copy_string(unbridged_chan_name, ast_channel_name(unbridged_chan),
+ sizeof(unbridged_chan_name));
+
+ ast_bridge_channel_queue_action_data(transferee_bridge_channel,
+ AST_BRIDGE_ACTION_ATTENDED_TRANSFER, unbridged_chan_name,
+ sizeof(unbridged_chan_name));
return 0;
}
@@ -4945,24 +5017,81 @@
enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_transferee,
struct ast_channel *to_transfer_target, struct ast_framehook *hook)
{
- /* First, check the validity of scenario. If invalid, return AST_BRIDGE_TRANSFER_INVALID. The following are invalid:
- * 1) atxfer of an unbridged call to an unbridged call
- * 2) atxfer of an unbridged call to a multi-party (n > 2) bridge
- * 3) atxfer of a multi-party (n > 2) bridge to an unbridged call
- * Second, check that the bridge(s) involved allows transfers. If not, return AST_BRIDGE_TRANSFER_NOT_PERMITTED.
- * Third, break into different scenarios for different bridge situations:
- * If both channels are bridged, perform a bridge merge. Direction of the merge is TBD.
- * If channel A is bridged, and channel B is not (e.g. transferring to IVR or blond transfer)
- * Some manner of masquerading is necessary. Presumably, you'd want to move channel A's bridge peer
- * into where channel B is. However, it may be possible to do something a bit different, where a
- * local channel is created and put into channel A's bridge. The local channel's ;2 channel
- * is then masqueraded with channel B in some way.
- * If channel A is not bridged and channel B is, then:
- * This is similar to what is done in the previous scenario. Create a local channel and place it
- * into B's bridge. Then masquerade the ;2 leg of the local channel.
- */
-
- /* XXX STUB */
+ RAII_VAR(struct ast_bridge *, to_transferee_bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_bridge *, to_target_bridge, NULL, ao2_cleanup);
+ RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_channel *, transferee, NULL, ao2_cleanup);
+ struct ast_bridge *the_bridge;
+ struct ast_channel *chan_bridged;
+ struct ast_channel *chan_unbridged;
+ int transfer_prohibited;
+ int do_bridge_transfer;
+
+ ast_channel_lock(to_transferee);
+ to_transferee_bridge = ast_channel_get_bridge(to_transferee);
+ ast_channel_unlock(to_transferee);
+
+ ast_channel_lock(to_transfer_target);
+ to_target_bridge = ast_channel_get_bridge(to_transfer_target);
+ ast_channel_unlock(to_transfer_target);
+
+ /* They can't both be unbridged, you silly goose! */
+ if (!to_transferee_bridge && !to_target_bridge) {
+ return AST_BRIDGE_TRANSFER_INVALID;
+ }
+
+ /* Let's get the easy one out of the way first */
+ if (to_transferee_bridge && to_target_bridge) {
+ struct ast_channel *kick_chans[2] = {
+ to_transferee,
+ to_transfer_target,
+ };
+ return ast_bridge_merge(to_transferee_bridge, to_target_bridge,
+ 1, kick_chans, 2) == 0 ? AST_BRIDGE_TRANSFER_SUCCESS :
+ AST_BRIDGE_TRANSFER_FAIL;
+ }
+
+ the_bridge = to_transferee_bridge ?: to_target_bridge;
+ chan_bridged = to_transferee_bridge ? to_transferee : to_transfer_target;
+ chan_unbridged = to_transferee_bridge ? to_transfer_target : to_transferee;
+
+ {
+ int chan_count;
+ SCOPED_LOCK(lock, the_bridge, ast_bridge_lock, ast_bridge_unlock);
+
+ channels = ast_bridge_peers_nolock(the_bridge);
+ if (!channels) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+ chan_count = ao2_container_count(channels);
+ if (chan_count <= 1) {
+ return AST_BRIDGE_TRANSFER_INVALID;
+ }
+ transfer_prohibited = ast_test_flag(&the_bridge->feature_flags,
+ AST_BRIDGE_FLAG_TRANSFER_PROHIBITED);
+ do_bridge_transfer = ast_test_flag(&the_bridge->feature_flags,
+ AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY) ||
+ chan_count > 2;
+ }
+
+ if (transfer_prohibited) {
+ return AST_BRIDGE_TRANSFER_NOT_PERMITTED;
+ }
+
+ if (do_bridge_transfer) {
+ return attended_transfer_bridge(chan_bridged, chan_unbridged, the_bridge);
+ }
+
+ transferee = get_transferee(channels, chan_bridged);
+ if (!transferee) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+
+ if (bridge_channel_queue_attended_transfer(transferee, chan_unbridged)) {
+ return AST_BRIDGE_TRANSFER_FAIL;
+ }
+
+ ast_bridge_remove(the_bridge, chan_bridged);
return AST_BRIDGE_TRANSFER_SUCCESS;
}
Modified: team/mmichelson/more_transfer/main/channel.c
URL: http://svnview.digium.com/svn/asterisk/team/mmichelson/more_transfer/main/channel.c?view=diff&rev=386936&r1=386935&r2=386936
==============================================================================
--- team/mmichelson/more_transfer/main/channel.c (original)
+++ team/mmichelson/more_transfer/main/channel.c Tue Apr 30 09:04:47 2013
@@ -11315,3 +11315,70 @@
RAII_VAR(struct ast_bridge *, bridge, ast_channel_get_bridge(chan), ao2_cleanup);
return bridge != 0;
}
+
+struct ast_channel *ast_channel_yank(struct ast_channel *yankee)
+{
+ struct ast_channel *tmpchan;
+ struct {
+ char *accountcode;
+ char *exten;
+ char *context;
+ char *linkedid;
+ char *name;
+ struct ast_cdr *cdr;
+ int amaflags;
+ int state;
+ struct ast_format readformat;
+ struct ast_format writeformat;
+ } tmpvars = { 0, };
+
+ ast_channel_lock(yankee);
+ tmpvars.accountcode = ast_strdupa(ast_channel_accountcode(yankee));
+ tmpvars.exten = ast_strdupa(ast_channel_exten(yankee));
+ tmpvars.context = ast_strdupa(ast_channel_context(yankee));
+ tmpvars.linkedid = ast_strdupa(ast_channel_linkedid(yankee));
+ tmpvars.name = ast_strdupa(ast_channel_name(yankee));
+ tmpvars.amaflags = ast_channel_amaflags(yankee);
+ tmpvars.state = ast_channel_state(yankee);
+ ast_format_copy(&tmpvars.writeformat, ast_channel_writeformat(yankee));
+ ast_format_copy(&tmpvars.readformat, ast_channel_readformat(yankee));
+ tmpvars.cdr = ast_channel_cdr(yankee) ? ast_cdr_dup(ast_channel_cdr(yankee)) : NULL;
+ ast_channel_unlock(yankee);
+
+ /* Do not hold any channel locks while calling channel_alloc() since the function
+ * locks the channel container when linking the new channel in. */
+ if (!(tmpchan = ast_channel_alloc(0, tmpvars.state, 0, 0, tmpvars.accountcode,
+ tmpvars.exten, tmpvars.context, tmpvars.linkedid, tmpvars.amaflags,
+ "Surrogate/%s", tmpvars.name))) {
+ ast_cdr_discard(tmpvars.cdr);
+ return NULL;
+ }
+
+ /* copy the cdr info over */
+ if (tmpvars.cdr) {
+ ast_cdr_discard(ast_channel_cdr(tmpchan));
+ ast_channel_cdr_set(tmpchan, tmpvars.cdr);
+ tmpvars.cdr = NULL;
+ }
+
+ /* Make formats okay */
+ ast_format_copy(ast_channel_readformat(tmpchan), &tmpvars.readformat);
+ ast_format_copy(ast_channel_writeformat(tmpchan), &tmpvars.writeformat);
+
+ if (ast_channel_move(tmpchan, yankee)) {
+ ast_hangup(tmpchan);
+ return NULL;
+ }
+
+ return tmpchan;
+}
+
+int ast_channel_move(struct ast_channel *dest, struct ast_channel *source)
+{
+ if (ast_channel_masquerade(dest, source)) {
+ return -1;
+ }
+
+ ast_do_masquerade(dest);
+ return 0;
+}
Modified: team/mmichelson/more_transfer/main/pbx.c
URL: http://svnview.digium.com/svn/asterisk/team/mmichelson/more_transfer/main/pbx.c?view=diff&rev=386936&r1=386935&r2=386936
==============================================================================
--- team/mmichelson/more_transfer/main/pbx.c (original)
+++ team/mmichelson/more_transfer/main/pbx.c Tue Apr 30 09:04:47 2013
@@ -9378,20 +9378,7 @@
int ast_async_goto(struct ast_channel *chan, const char *context, const char *exten, int priority)
{
- int res = 0;
- struct ast_channel *tmpchan;
- struct {
- char *accountcode;
- char *exten;
- char *context;
- char *linkedid;
- char *name;
- struct ast_cdr *cdr;
- int amaflags;
- int state;
- struct ast_format readformat;
- struct ast_format writeformat;
- } tmpvars = { 0, };
+ struct ast_channel *newchan;
ast_channel_lock(chan);
/* Channels in a bridge or running a PBX can be sent directly to the specified destination */
@@ -9402,64 +9389,19 @@
ast_explicit_goto(chan, context, exten, priority);
ast_softhangup_nolock(chan, AST_SOFTHANGUP_ASYNCGOTO);
ast_channel_unlock(chan);
- return res;
- }
-
- /* In order to do it when the channel doesn't really exist within
- * the PBX, we have to make a new channel, masquerade, and start the PBX
- * at the new location */
- tmpvars.accountcode = ast_strdupa(ast_channel_accountcode(chan));
- tmpvars.exten = ast_strdupa(ast_channel_exten(chan));
- tmpvars.context = ast_strdupa(ast_channel_context(chan));
- tmpvars.linkedid = ast_strdupa(ast_channel_linkedid(chan));
- tmpvars.name = ast_strdupa(ast_channel_name(chan));
- tmpvars.amaflags = ast_channel_amaflags(chan);
- tmpvars.state = ast_channel_state(chan);
- ast_format_copy(&tmpvars.writeformat, ast_channel_writeformat(chan));
- ast_format_copy(&tmpvars.readformat, ast_channel_readformat(chan));
- tmpvars.cdr = ast_channel_cdr(chan) ? ast_cdr_dup(ast_channel_cdr(chan)) : NULL;
-
- ast_channel_unlock(chan);
-
- /* Do not hold any channel locks while calling channel_alloc() since the function
- * locks the channel container when linking the new channel in. */
- if (!(tmpchan = ast_channel_alloc(0, tmpvars.state, 0, 0, tmpvars.accountcode, tmpvars.exten, tmpvars.context, tmpvars.linkedid, tmpvars.amaflags, "AsyncGoto/%s", tmpvars.name))) {
- ast_cdr_discard(tmpvars.cdr);
+ return 0;
+ }
+
+ /* Otherwise, we need to gain control of the channel first */
+ newchan = ast_channel_yank(chan);
+ ast_explicit_goto(newchan, context, exten, priority);
+ if (ast_pbx_start(newchan)) {
+ ast_hangup(newchan);
+ ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(newchan));
return -1;
}
- /* copy the cdr info over */
- if (tmpvars.cdr) {
- ast_cdr_discard(ast_channel_cdr(tmpchan));
- ast_channel_cdr_set(tmpchan, tmpvars.cdr);
- tmpvars.cdr = NULL;
- }
-
- /* Make formats okay */
- ast_format_copy(ast_channel_readformat(tmpchan), &tmpvars.readformat);
- ast_format_copy(ast_channel_writeformat(tmpchan), &tmpvars.writeformat);
-
- /* Setup proper location. Never hold another channel lock while calling this function. */
- ast_explicit_goto(tmpchan, S_OR(context, tmpvars.context), S_OR(exten, tmpvars.exten), priority);
-
- /* Masquerade into tmp channel */
- if (ast_channel_masquerade(tmpchan, chan)) {
- /* Failed to set up the masquerade. It's probably chan_local
- * in the middle of optimizing itself out. Sad. :( */
- ast_hangup(tmpchan);
- tmpchan = NULL;
- res = -1;
- } else {
- ast_do_masquerade(tmpchan);
- /* Start the PBX going on our stolen channel */
- if (ast_pbx_start(tmpchan)) {
- ast_log(LOG_WARNING, "Unable to start PBX on %s\n", ast_channel_name(tmpchan));
- ast_hangup(tmpchan);
- res = -1;
- }
- }
-
- return res;
+ return 0;
}
int ast_async_goto_by_name(const char *channame, const char *context, const char *exten, int priority)
More information about the asterisk-commits
mailing list