[svn-commits] mjordan: branch mjordan/confbridgeactionexec r371177 - in /team/mjordan/confb...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Sun Aug 12 15:54:33 CDT 2012


Author: mjordan
Date: Sun Aug 12 15:54:27 2012
New Revision: 371177

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=371177
Log:
Create branch for confbridgeactionexec

Added:
    team/mjordan/confbridgeactionexec/
      - copied from r371170, trunk/
    team/mjordan/confbridgeactionexec/tests/test_bridging.c   (with props)
Modified:
    team/mjordan/confbridgeactionexec/CHANGES
    team/mjordan/confbridgeactionexec/apps/app_confbridge.c
    team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c
    team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h
    team/mjordan/confbridgeactionexec/include/asterisk/bridging.h
    team/mjordan/confbridgeactionexec/main/bridging.c

Modified: team/mjordan/confbridgeactionexec/CHANGES
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/CHANGES?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/CHANGES (original)
+++ team/mjordan/confbridgeactionexec/CHANGES Sun Aug 12 15:54:27 2012
@@ -58,6 +58,7 @@
    file also allows for the default sounds played to all conference users when
    this occurs to be overriden using sound_participants_unmuted and
    sound_participants_muted.
+ * Added AMI command ConfbridgeExecAction.  See the AMI changes for more details.
 
  * Added menu action participant_count.  This will playback the number of
    current participants in a conference.
@@ -633,7 +634,8 @@
 CDR Adaptive ODBC Driver
 -------------------
  * Added schema option for databases that support specifying a schema.
-
+ * ConfbridgeExecAction executes any confbridge action on a specified channel
+   in a conference.
 
 Resource Modules
 -------------------

Modified: team/mjordan/confbridgeactionexec/apps/app_confbridge.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/apps/app_confbridge.c?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/apps/app_confbridge.c (original)
+++ team/mjordan/confbridgeactionexec/apps/app_confbridge.c Sun Aug 12 15:54:27 2012
@@ -255,7 +255,31 @@
 		<description>
 		</description>
 	</manager>
-
+	<manager name="ConfbridgeExecAction" language="en_US">
+		<synopsis>
+			Execute a ConfBridge action on a particular channel.
+		</synopsis>
+		<syntax>
+			<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
+			<parameter name="Conference" required="true">
+				<para>Conference Number.</para>
+			</parameter>
+			<parameter name="Channel" required="true">
+				<para>The channel to execute the actions on.</para>
+			</parameter>
+			<parameter name="Actions" required="true">
+				<para>A comma delineated list of ConfBridge actions to execute on the particular channel.</para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>Executes a ConfBridge action on a specific channel.  The available actions are defined in
+			<literal>confbridge.conf</literal>.  The actions specified in the <literal>Actions</literal> parameter
+			are executed sequentially on the channel.</para>
+			<para>Executing ConfBridge actions through this mechanism does not lift any user profile
+			based restrictions.  For example, an action requiring a profile with the 'admin' flag will not be
+			able to be executed on a user's channel whose profile does not have that flag.</para>
+		</description>
+	</manager>
 ***/
 
 /*!
@@ -272,6 +296,18 @@
 
 /* Number of buckets our conference bridges container can have */
 #define CONFERENCE_BRIDGE_BUCKETS 53
+
+/*! \brief Object passed to the bridging layer during a deferred callback
+ */
+struct deferred_pvt {
+	/*! The user to perform an action for */
+	struct conference_bridge_user *conference_bridge_user;
+	/*! The menu entry defining the actions to execute */
+	struct conf_menu_entry menu_entry;
+	/*! The menu containing the menu entry */
+	struct conf_menu *menu;
+};
+
 
 /*! \brief Container to hold all conference bridges in progress */
 static struct ao2_container *conference_bridges;
@@ -2734,6 +2770,133 @@
 	return 0;
 }
 
+/*! \internal \brief A callback function passed to the bridging layer that
+ * performs an action upon a channel in the conference
+ */
+static void deferred_action_callback(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data)
+{
+	struct deferred_pvt *pvt = pvt_data;
+
+	/* Make sure the pvt has all the information required */
+	if (!pvt || !pvt->conference_bridge_user || !pvt->menu) {
+		return;
+	}
+	conf_handle_dtmf(bridge_channel, pvt->conference_bridge_user, &pvt->menu_entry, pvt->menu);
+}
+
+/*! \internal \brief Destructor for a deferred_pvt */
+static void deferred_action_private_destructor(void *pvt_data)
+{
+	struct deferred_pvt *pvt = pvt_data;
+
+	if (!pvt) {
+		return;
+	}
+
+	if (pvt->menu && pvt->menu->entries.first) {
+		conf_menu_entry_destroy(pvt->menu->entries.first);
+	}
+	ast_free(pvt->menu);
+	ast_free(pvt);
+}
+
+/*! \internal \brief Entry point for the ConfbridgeExecAction manager command */
+static int action_confbridgeexecaction(struct mansession *s, const struct message *m)
+{
+	struct conference_bridge_user *participant = NULL;
+	struct conference_bridge *conf_bridge = NULL;
+	struct conf_menu *menu = NULL;
+	struct deferred_pvt *pvt = NULL;
+	struct conference_bridge tmp;
+	const char *conference = astman_get_header(m, "Conference");
+	const char *channel = astman_get_header(m, "Channel");
+	const char *action = astman_get_header(m, "Actions");
+	int deferred_queued = 0;
+
+	if (ast_strlen_zero(conference)) {
+		astman_send_error(s, m, "No Conference name provided.");
+		return 0;
+	}
+	if (ast_strlen_zero(channel)) {
+		astman_send_error(s, m, "No channel name provided.");
+		return 0;
+	}
+	if (ast_strlen_zero(action)) {
+		astman_send_error(s, m, "No actions provided.");
+		return 0;
+	}
+	if (!ao2_container_count(conference_bridges)) {
+		astman_send_error(s, m, "No active conferences.");
+		return 0;
+	}
+
+	/* Build the dummy menu entry that will contain the actions */
+	if (!(menu = ast_calloc(1, sizeof(*menu)))) {
+		astman_send_error(s, m, "Internal error when building actions.");
+		goto action_exec_cleanup;
+	}
+	if (conf_add_menu_entry(menu, "0", action)) {
+		astman_send_error(s, m, "Cannot parse specified actions.");
+		goto action_exec_cleanup;
+	}
+	if (!(pvt = ast_calloc(1, sizeof(*pvt)))) {
+		astman_send_error(s, m, "Internal error when building actions.");
+		goto action_exec_cleanup;
+	}
+	pvt->menu = menu;
+	pvt->menu_entry = *(menu->entries.first);
+
+	/* Find the conference */
+	ast_copy_string(tmp.name, conference, sizeof(tmp.name));
+	conf_bridge = ao2_find(conference_bridges, &tmp, OBJ_POINTER);
+	if (!conf_bridge) {
+		astman_send_error(s, m, "No Conference by that name found.");
+		goto action_exec_cleanup;
+	}
+
+	/* Find the participant in the bridge and ask the bridge layer to execute
+	 * the menu on that participant */
+	ao2_lock(conf_bridge);
+	AST_LIST_TRAVERSE(&conf_bridge->users_list, participant, list) {
+		if (!strncmp(channel, ast_channel_name(participant->chan), strlen(channel))) {
+			pvt->conference_bridge_user = participant;
+			if (ast_bridge_issue_deferred(conf_bridge->bridge,
+				participant->chan,
+				deferred_action_callback,
+				pvt,
+				deferred_action_private_destructor)) {
+				ao2_unlock(conf_bridge);
+				astman_send_error(s, m, "Internal error initiating action on participant.");
+				goto action_exec_cleanup;
+			}
+			deferred_queued = 1;
+			break;
+		}
+	}
+	ao2_unlock(conf_bridge);
+
+	if (!participant) {
+		astman_send_error(s, m, "No channel by that name found in Conference.");
+		goto action_exec_cleanup;
+	}
+
+	astman_send_ack(s, m, "Conference action successfully requested for participant.");
+
+action_exec_cleanup:
+	/* Destroy the pvt we created if we failed to queue it up for action */
+	if (!deferred_queued) {
+		if (menu && menu->entries.first) {
+			conf_menu_entry_destroy(menu->entries.first);
+		}
+		ast_free(menu);
+		ast_free(pvt);
+	}
+	if (conf_bridge) {
+		ao2_ref(conf_bridge, -1);
+	}
+	return 0;
+}
+
 static int func_confbridge_info(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
 {
 	char *parse = NULL;
@@ -2823,6 +2986,7 @@
 	res |= ast_manager_unregister("ConfbridgeLock");
 	res |= ast_manager_unregister("ConfbridgeStartRecord");
 	res |= ast_manager_unregister("ConfbridgeStopRecord");
+	res |= ast_manager_unregister("ConfbridgeExecAction");
 
 	return res;
 }
@@ -2866,6 +3030,7 @@
 	res |= ast_manager_register_xml("ConfbridgeStartRecord", EVENT_FLAG_CALL, action_confbridgestartrecord);
 	res |= ast_manager_register_xml("ConfbridgeStopRecord", EVENT_FLAG_CALL, action_confbridgestoprecord);
 	res |= ast_manager_register_xml("ConfbridgeSetSingleVideoSrc", EVENT_FLAG_CALL, action_confbridgesetsinglevideosrc);
+	res |= ast_manager_register_xml("ConfbridgeExecAction", EVENT_FLAG_CALL, action_confbridgeexecaction);
 	if (res) {
 		return AST_MODULE_LOAD_FAILURE;
 	}

Modified: team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c (original)
+++ team/mjordan/confbridgeactionexec/apps/confbridge/conf_config_parser.c Sun Aug 12 15:54:27 2012
@@ -477,7 +477,7 @@
 	return 0;
 }
 
-static int add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *action_names)
+int conf_add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *action_names)
 {
 	struct conf_menu_entry *menu_entry = NULL, *cur = NULL;
 	int res = 0;
@@ -1249,7 +1249,7 @@
 
 static int menu_option_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
-	add_menu_entry(obj, var->name, var->value);
+	conf_add_menu_entry(obj, var->name, var->value);
 	return 0;
 }
 

Modified: team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h (original)
+++ team/mjordan/confbridgeactionexec/apps/confbridge/include/confbridge.h Sun Aug 12 15:54:27 2012
@@ -294,6 +294,21 @@
 int conf_find_menu_entry_by_sequence(const char *dtmf_sequence, struct conf_menu *menu, struct conf_menu_entry *result);
 
 /*!
+ * \brief Adds a sequence of actions to a DTMF key in a ConfBridge menu
+ *
+ * \param menu The menu to add an action to
+ * \param dtmf The DTMF key to associate with the menu actions
+ * \param action_names A comma delineated list of ConfBridge action names to execute
+ *
+ * \note If the menu can be accessed by a user when this method is called, the menu
+ * itself should be locked prior to calling this method.
+ *
+ * \retval 0 on success, actions were added to the menu under the associated DTMF key
+ * \retval -1 on error
+ */
+int conf_add_menu_entry(struct conf_menu *menu, const char *dtmf, const char *action_names);
+
+/*!
  * \brief Destroys and frees all the actions stored in a menu_entry structure
  */
 void conf_menu_entry_destroy(struct conf_menu_entry *menu_entry);

Modified: team/mjordan/confbridgeactionexec/include/asterisk/bridging.h
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/include/asterisk/bridging.h?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/include/asterisk/bridging.h (original)
+++ team/mjordan/confbridgeactionexec/include/asterisk/bridging.h Sun Aug 12 15:54:27 2012
@@ -101,6 +101,8 @@
 	AST_BRIDGE_CHANNEL_STATE_START_TALKING,
 	/*! Bridged channel has stopped talking */
 	AST_BRIDGE_CHANNEL_STATE_STOP_TALKING,
+	/*! Bridged channel would like a deferred callback executed */
+	AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED,
 };
 
 /*! \brief Return values for bridge technology write function */
@@ -132,6 +134,17 @@
 	unsigned int drop_silence:1;
 };
 
+struct bridge_deferred_callback;
+
+/*!
+ * \brief Structure that holds states for bridge_channel transitions
+ */
+struct requested_bridge_state {
+	/*! The state to transition to */
+	enum ast_bridge_channel_state state;
+	AST_LIST_ENTRY(requested_bridge_state) entry;
+};
+
 /*!
  * \brief Structure that contains information regarding a channel in a bridge
  */
@@ -140,6 +153,8 @@
 	ast_mutex_t lock;
 	/*! Condition, used if we want to wake up a thread waiting on the bridged channel */
 	ast_cond_t cond;
+	/*! The next requested state to put the bridge in */
+	AST_LIST_HEAD_NOLOCK(,requested_bridge_state) requested_states;
 	/*! Current bridged channel state */
 	enum ast_bridge_channel_state state;
 	/*! Asterisk channel participating in the bridge */
@@ -160,6 +175,8 @@
 	unsigned int allow_impart_hangup:1;
 	/*! Features structure for features that are specific to this channel */
 	struct ast_bridge_features *features;
+	/*! A list that acts as a FIFO queue of deferred callbacks to execute */
+	AST_LIST_HEAD_NOLOCK(,bridge_deferred_callback) deferreds;
 	/*! Technology optimization parameters used by bridging technologies capable of
 	 *  optimizing based upon talk detection. */
 	struct ast_bridge_tech_optimizations tech_args;
@@ -251,6 +268,31 @@
 	AST_LIST_HEAD_NOLOCK(, ast_bridge_channel) channels;
 	};
 
+/*!
+ * \brief General purpose deferred callback
+ *
+ * \details This represents a general purpose callback function
+ * that will be safely executed in the context of a particular
+ * channel's thread.
+ *
+ * \param bridge The bridge that the channel is part of
+ * \param bridge_channel Channel whose thread the function is executed on
+ * \param pvt_data General data optionally passed to the callback function
+ */
+typedef void (*ast_bridge_deferred_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt);
+
+/*!
+ * \brief Dispose of any general purpose data passed to a deferred callback
+ *
+ * \details If the pvt_data object is provided, an optional destructor can be
+ * provided that will be called after the callback has completed.  This allows
+ * for the memory allocated for the data to be disposed of.
+ *
+ * \param pvt_data General data that was optionally passed to the callback function
+ */
+typedef void (*ast_bridge_deferred_callback_destructor)(void *pvt_data);
+
+
 /*! \brief Create a new bridge
  *
  * \param capabilities The capabilities that we require to be used on the bridge
@@ -557,6 +599,33 @@
  */
 void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan);
 
+/*!
+ * \brief Ask the bridging layer to execute a callback function on a particular channel's
+ * bridge channel thread
+ * \param bridge The bridge to issue the deferred callback on
+ * \param chan The channel in the bridge whose thread the callback will be executed on
+ * \param deferred_cb The function to call
+ * \param pvt_data Optional data to pass to the function
+ * \param pvt_destructor Destructor for the private data.  Required if pvt_data is used.
+ *
+ * This method allows user's of the bridging API to request a callback function be executed
+ * in the context of a particular channel's bridge thread.  This safely removes the channel
+ * from the bridge, allowing actions to be executed on the underlying ast_channel.
+ *
+ * \note Since the callback function will typically execute on a separate thread then the
+ * initiator of the request, there is no guarantee when the callback will be executed, or if
+ * it will be executed (for example, the channel can be hung up prior to execution of the
+ * deferred callback).
+ *
+ * \retval 0 if the callback was successfully queued for later execution
+ * \retval -1 on error
+ */
+int ast_bridge_issue_deferred(struct ast_bridge *bridge,
+	struct ast_channel *chan,
+	ast_bridge_deferred_callback deferred_cb,
+	void *pvt_data,
+	ast_bridge_deferred_callback_destructor pvt_destructor);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif

Modified: team/mjordan/confbridgeactionexec/main/bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/main/bridging.c?view=diff&rev=371177&r1=371170&r2=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/main/bridging.c (original)
+++ team/mjordan/confbridgeactionexec/main/bridging.c Sun Aug 12 15:54:27 2012
@@ -55,6 +55,22 @@
 /* Grow rate of bridge array of channels */
 #define BRIDGE_ARRAY_GROW 32
 
+/*!
+ * \internal
+ * \brief An internally object used to hold information about deferred
+ * callbacks issued on a bridge channel
+ */
+struct bridge_deferred_callback {
+	/*! The callback function to execute on the bridge channel's thread */
+	ast_bridge_deferred_callback deferred_cb;
+	/*! Private data to pass to the deferred callback */
+	void *pvt_data;
+	/*! Optional destructor function to execute on the private data */
+	ast_bridge_deferred_callback_destructor pvt_destructor;
+	/* Deferreds exist in a FIFO queue on the bridge channel */
+	AST_LIST_ENTRY(bridge_deferred_callback) entry;
+};
+
 static void cleanup_video_mode(struct ast_bridge *bridge);
 
 /*! Default DTMF keys for built in features */
@@ -118,17 +134,41 @@
 	return current ? 0 : -1;
 }
 
+static void enqueue_bridge_state_change(struct ast_bridge_channel *bridge_channel, struct requested_bridge_state *state)
+{
+	switch (state->state) {
+	/* States that result in the bridge being removed go on the front */
+	case AST_BRIDGE_CHANNEL_STATE_END:
+	case AST_BRIDGE_CHANNEL_STATE_HANGUP:
+	case AST_BRIDGE_CHANNEL_STATE_DEPART:
+		AST_LIST_INSERT_HEAD(&bridge_channel->requested_states, state, entry);
+		break;
+	default:
+		/* Everyone else - get in the back! */
+		AST_LIST_INSERT_TAIL(&bridge_channel->requested_states, state, entry);
+		break;
+	}
+}
+
 void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state)
 {
-	/* Change the state on the bridge channel */
-	bridge_channel->state = new_state;
+	struct requested_bridge_state *state;
+
+	if (!(state = ast_calloc(1, sizeof(*state)))) {
+		ast_log(AST_LOG_ERROR, "Failed to allocate memory to enqueue state %d\n", new_state);
+		return;
+	}
+	state->state = new_state;
 
 	/* Only poke the channel's thread if it is not us */
 	if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
 		pthread_kill(bridge_channel->thread, SIGURG);
 		ao2_lock(bridge_channel);
+		enqueue_bridge_state_change(bridge_channel, state);
 		ast_cond_signal(&bridge_channel->cond);
 		ao2_unlock(bridge_channel);
+	} else {
+		enqueue_bridge_state_change(bridge_channel, state);
 	}
 
 	return;
@@ -733,6 +773,21 @@
 	return 0;
 }
 
+/*! \internal \brief Get the latest state change request off of the queue
+ * \note The bridge_channel should be locked prior to calling this method */
+static enum ast_bridge_channel_state refresh_bridge_channel_state(struct ast_bridge_channel *bridge_channel)
+{
+	struct requested_bridge_state *requested_state = NULL;
+
+	requested_state = AST_LIST_REMOVE_HEAD(&bridge_channel->requested_states, entry);
+	if (requested_state) {
+		bridge_channel->state = requested_state->state;
+	}
+	ast_free(requested_state);
+
+	return bridge_channel->state;
+}
+
 /*! \brief Run in a multithreaded model. Each joined channel does writing/reading in their own thread. TODO: Improve */
 static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct ast_bridge_channel *bridge_channel)
 {
@@ -763,10 +818,16 @@
 	}
 
 	ao2_lock(bridge_channel->bridge);
-
 	if (!bridge_channel->suspended) {
 		ast_bridge_handle_trip(bridge_channel->bridge, bridge_channel, chan, outfd);
 	}
+	ao2_unlock(bridge_channel->bridge);
+
+	ao2_lock(bridge_channel);
+	refresh_bridge_channel_state(bridge_channel);
+	ao2_unlock(bridge_channel);
+
+	ao2_lock(bridge_channel->bridge);
 
 	return bridge_channel->state;
 }
@@ -780,6 +841,7 @@
 		ast_debug(1, "Going into a single threaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
 		ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
 	}
+	refresh_bridge_channel_state(bridge_channel);
 	ao2_unlock(bridge_channel);
 	ao2_lock(bridge_channel->bridge);
 
@@ -817,6 +879,44 @@
 
 
 	return;
+}
+
+/*!
+ * \internal
+ * \brief Internal function that executes a callback on a bridge channel
+ * \note Neither the bridge nor the bridge_channel locks should be held when entering
+ * this function.
+ */
+static void bridge_channel_call_deferred(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	struct bridge_deferred_callback *deferred = NULL;
+
+	ao2_lock(bridge_channel);
+	deferred = AST_LIST_REMOVE_HEAD(&bridge_channel->deferreds, entry);
+	ao2_unlock(bridge_channel);
+
+	if (deferred) {
+		/* Execute the deferred */
+		deferred->deferred_cb(bridge, bridge_channel, deferred->pvt_data);
+
+		/* Clean up the private data */
+		if (deferred->pvt_destructor) {
+			deferred->pvt_destructor(deferred->pvt_data);
+		}
+		ast_free(deferred);
+
+		/* If we are handing the channel off to an external callback for ownership,
+		 * we are not guaranteed what kind of state it will come back in.  If
+		 * the channel hungup, we need to detect that here. */
+		if (bridge_channel->chan && ast_check_hangup_locked(bridge_channel->chan)) {
+			ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
+		}
+	}
+
+	/* if the channel is still in deferred state, revert it back to wait state */
+	if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED) {
+		ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
+	}
 }
 
 /*!
@@ -1019,9 +1119,19 @@
 			bridge_channel_talking(bridge_channel->bridge, bridge_channel);
 			ao2_lock(bridge_channel->bridge);
 			break;
+		case AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED:
+			bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
+			ao2_unlock(bridge_channel->bridge);
+			bridge_channel_call_deferred(bridge_channel->bridge, bridge_channel);
+			ao2_lock(bridge_channel->bridge);
+			bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
+			break;
 		default:
 			break;
 		}
+		ao2_lock(bridge_channel);
+		refresh_bridge_channel_state(bridge_channel);
+		ao2_unlock(bridge_channel);
 	}
 
 	ast_channel_internal_bridge_set(bridge_channel->chan, NULL);
@@ -1072,6 +1182,8 @@
 static void bridge_channel_destroy(void *obj)
 {
 	struct ast_bridge_channel *bridge_channel = obj;
+	struct bridge_deferred_callback *deferred;
+	struct requested_bridge_state *state;
 
 	if (bridge_channel->callid) {
 		bridge_channel->callid = ast_callid_unref(bridge_channel->callid);
@@ -1081,8 +1193,18 @@
 		ao2_ref(bridge_channel->bridge, -1);
 		bridge_channel->bridge = NULL;
 	}
+
 	/* Destroy elements of the bridge channel structure and the bridge channel structure itself */
 	ast_cond_destroy(&bridge_channel->cond);
+	while ((deferred = AST_LIST_REMOVE_HEAD(&bridge_channel->deferreds, entry))) {
+		if (deferred->pvt_destructor) {
+			deferred->pvt_destructor(deferred->pvt_data);
+		}
+		ast_free(deferred);
+	}
+	while ((state = AST_LIST_REMOVE_HEAD(&bridge_channel->requested_states, entry))) {
+		ast_free(state);
+	}
 }
 
 static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge)
@@ -1497,6 +1619,49 @@
 
 	return 0;
 }
+
+int ast_bridge_issue_deferred(struct ast_bridge *bridge,
+	struct ast_channel *chan,
+	ast_bridge_deferred_callback deferred_cb,
+	void *pvt_data,
+	ast_bridge_deferred_callback_destructor pvt_destructor)
+{
+	struct ast_bridge_channel *bridge_channel = NULL;
+	struct bridge_deferred_callback *deferred;
+
+	if (pvt_data && !pvt_destructor) {
+		ast_log(AST_LOG_WARNING, "Deferred callback being issued in bridge %p with private data but no destructor\n", bridge);
+		return -1;
+	}
+
+	if (!(deferred = ast_calloc(1, sizeof(*deferred)))) {
+		ast_log(AST_LOG_WARNING, "Failed to create deferred callback container\n");
+		return -1;
+	}
+	deferred->deferred_cb = deferred_cb;
+	deferred->pvt_data = pvt_data;
+	deferred->pvt_destructor = pvt_destructor;
+
+	ao2_lock(bridge);
+
+	if (!(bridge_channel = find_bridge_channel(bridge, chan))) {
+		ao2_unlock(bridge);
+		ast_debug(1, "Unable to find channel %s in bridge %p\n", ast_channel_name(chan), bridge);
+		ast_free(deferred);
+		return -1;
+	}
+
+	ao2_lock(bridge_channel);
+	AST_LIST_INSERT_TAIL(&bridge_channel->deferreds, deferred, entry);
+	ao2_unlock(bridge_channel);
+
+	ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED);
+
+	ao2_unlock(bridge);
+
+	return 0;
+}
+
 
 void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
 {

Added: team/mjordan/confbridgeactionexec/tests/test_bridging.c
URL: http://svnview.digium.com/svn/asterisk/team/mjordan/confbridgeactionexec/tests/test_bridging.c?view=auto&rev=371177
==============================================================================
--- team/mjordan/confbridgeactionexec/tests/test_bridging.c (added)
+++ team/mjordan/confbridgeactionexec/tests/test_bridging.c Sun Aug 12 15:54:27 2012
@@ -1,0 +1,781 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Matt Jordan
+ *
+ * Matt Jordan <mjordan at 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 Unit tests for jitterbuf.c
+ *
+ * \author\verbatim Matt Jordan <mjordan at digium.com> \endverbatim
+ *
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+	<depend>TEST_FRAMEWORK</depend>
+	<support_level>core</support_level>
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/channel.h"
+#include "asterisk/bridging.h"
+#include "asterisk/bridging_technology.h"
+
+/*! \internal \brief Verify the expected result from two integer values.  Note
+ * that it assumes the mailbox snapshot object is test_mbox_snapshot */
+#define ASSERT_EQUAL(expected, actual) do { \
+	if ((expected) == (actual)) { \
+		ast_test_status_update(test, "Test failed for %s: Expected [%d], Actual [%d]\n", #actual, expected, actual); \
+		return AST_TEST_FAIL; \
+	} } while (0)
+
+#define AST_BRIDGE_CAPABILITY_TEST (1 << 31)
+
+static int mock_bridge_create(struct ast_bridge *bridge);
+static int mock_bridge_destroy(struct ast_bridge *bridge);
+static int mock_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static int mock_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void mock_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static void mock_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel);
+static int mock_bridge_compatible(struct ast_bridge_channel *bridge_channel);
+static enum ast_bridge_write_result mock_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridged_channel, struct ast_frame *frame);
+
+static int mock_channel_write(struct ast_channel *ast, struct ast_frame *frame);
+static struct ast_frame *mock_channel_read(struct ast_channel *ast);
+
+struct bridge_callback_tracking {
+	unsigned int create_calls;
+	unsigned int destroy_calls;
+	unsigned int join_calls;
+	unsigned int leave_calls;
+	unsigned int suspend_calls;
+	unsigned int unsuspend_calls;
+	unsigned int compatible_calls;
+	unsigned int write_calls;
+};
+
+struct mock_channel_pvt {
+	struct ast_test *test;
+};
+
+static struct bridge_callback_tracking test_tracker;
+
+static struct ast_bridge_technology mock_bridge_tech = {
+	.name = "mock_bridge_tech",
+	.capabilities = 0,	/* This should be set by the specific test */
+	.preference = AST_BRIDGE_PREFERENCE_HIGH,
+	.create = mock_bridge_create,
+	.destroy = mock_bridge_destroy,
+	.join = mock_bridge_join,
+	.leave = mock_bridge_leave,
+	.suspend = mock_bridge_suspend,
+	.unsuspend = mock_bridge_unsuspend,
+	.compatible = mock_bridge_compatible,
+	.write = mock_bridge_write,
+};
+
+static struct ast_channel_tech mock_channel_tech = {
+	.type = "mock",
+	.read = mock_channel_read,
+	.write = mock_channel_write,
+};
+
+static void reset_test_tracking(void)
+{
+	test_tracker.create_calls = 0;
+	test_tracker.destroy_calls = 0;
+	test_tracker.join_calls = 0;
+	test_tracker.leave_calls = 0;
+	test_tracker.suspend_calls = 0;
+	test_tracker.unsuspend_calls = 0;
+	test_tracker.compatible_calls = 0;
+	test_tracker.write_calls = 0;
+}
+
+static int mock_bridge_create(struct ast_bridge *bridge)
+{
+	test_tracker.create_calls++;
+	return 0;
+}
+
+static int mock_bridge_destroy(struct ast_bridge *bridge)
+{
+	test_tracker.destroy_calls++;
+	return 0;
+}
+
+static int mock_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	test_tracker.join_calls++;
+	return 0;
+}
+
+static int mock_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	test_tracker.leave_calls++;
+	return 0;
+}
+
+static void mock_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	test_tracker.suspend_calls++;
+}
+
+static void mock_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+	test_tracker.unsuspend_calls++;
+}
+
+static int mock_bridge_compatible(struct ast_bridge_channel *bridge_channel)
+{
+	test_tracker.compatible_calls++;
+	return 0;
+}
+
+static enum ast_bridge_write_result mock_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridged_channel, struct ast_frame *frame)
+{
+	test_tracker.write_calls++;
+	return AST_BRIDGE_WRITE_SUCCESS;
+}
+
+static void mock_tech_cleanup(struct ast_bridge_technology *tech)
+{
+	if (!tech) {
+		return;
+	}
+
+	reset_test_tracking();
+
+	/* A test must set its capabilities - reset during cleanup */
+	tech->capabilities = 0;
+
+	ast_bridge_technology_unregister(tech);
+}
+
+static void bridge_destructor(struct ast_bridge *bridge)
+{
+	if (!bridge) {
+		return;
+	}
+	ast_bridge_destroy(bridge);
+}
+
+static struct ast_channel *create_mock_channel(struct ast_test *test)
+{
+	struct ast_channel *chan;
+	struct ast_format_cap *nativeformats;
+	struct mock_channel_pvt *pvt;
+
+	if (!(pvt = ast_calloc(1, sizeof(*pvt)))) {
+		return NULL;
+	}
+	pvt->test = test;
+
+	if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL,
+		NULL, NULL, 0, 0, "MockChannel"))) {
+		ast_free(pvt);
+		return NULL;
+	}
+	ast_channel_tech_set(chan, &mock_channel_tech);
+	ast_channel_tech_pvt_set(chan, pvt);
+
+	/*	ast_format_set(ast_channel_writeformat(chan), AST_FORMAT_GSM, 0);
+	nativeformats = ast_channel_nativeformats(chan);
+	ast_format_cap_add(nativeformats, ast_channel_writeformat(chan));
+	ast_format_set(ast_channel_rawwriteformat(chan), AST_FORMAT_GSM, 0);
+	ast_format_set(ast_channel_readformat(chan), AST_FORMAT_GSM, 0);
+	ast_format_set(ast_channel_rawreadformat(chan), AST_FORMAT_GSM, 0);*/
+
+
+
+	return chan;
+}
+
+static int mock_channel_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+	return 0;
+}
+
+static struct ast_frame *mock_channel_read(struct ast_channel *ast)
+{
+	struct ast_frame *frame;
+
+	frame = &ast_null_frame;
+
+	return frame;
+}
+
+/* Things to test
+ * ast_bridge_new - get back bridge that you expect based on capabilities.
+ * 	also test ast_bridge_destroy
+ * ast_bridge_check - check if bridge technologies exist.  Nominal / off-nominal
+ * ast_bridge_join: BLOCKING OPERATIONS.  Will need a specific way of doing this
+ *   that does the join and returns success/failure on separate threads of execution.
+ * 		Simple test - make sure you can join in
+ * 		Swap test - join a channel, make another new channel, and swap it out
+ * 		*** IGNORE FEATURES FOR NOW - probably belong in separate tests ***
+ * ast_bridge_impart - same tests.  Will also need to test ast_bridge_depart with this.
+ * ast_bridge_remove: test with:
+ * 		ast_bridge_join
+ * 		ast_bridge_impart
+ * ast_bridge_merge - test merging empty bridge into non-empty, non-empty into empty, and
+ *   two populated
+ * ast_bridge_suspend/unsuspend
+ * ast_bridge_issue_deferred: callback.  test with nominal / off-nominal data.
+
+	Note that more unit tests could be written, but blah.
+ */
+
+AST_TEST_DEFINE(bridging_issue_deferred_off_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_issue_deferred_off_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+	mock_channel_thread(NULL);
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_issue_deferred_chain)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_issue_deferred_chain";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_issue_deferred_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_issue_deferred_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_suspend_unsuspend_off_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_suspend_unsuspend_off_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_suspend_unsuspend_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_suspend_unsuspend_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_merge_off_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_merge_off_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_merge_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_merge_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_remove_off_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_remove_off_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_remove_imparted)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_remove_imparted";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_remove_joined)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_remove_joined";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_impart_off_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_impart_off_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_impart_swap)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_impart_swap";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_impart_simple)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_impart_simple";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_join_off_nominal)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_join_off_nominal";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+AST_TEST_DEFINE(bridging_join_swap)
+{
+	enum ast_test_result_state result = AST_TEST_FAIL;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_join_swap";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	return result;
+}
+
+static void bridging_join_simple_mock_channel_thread(void *data)
+{
+	struct ast_channel *mock_channel = data;
+	struct mock_channel_pvt *pvt;
+	RAII_VAR(struct ast_bridge *, bridge, ast_bridge_new(AST_BRIDGE_CAPABILITY_TEST, 0), bridge_destructor);
+	enum ast_bridge_channel_state final_state;
+
+	if (!mock_channel) {
+		return;
+	}
+	pvt = ast_channel_tech_pvt(mock_channel);
+
+	if (!bridge) {
+		ast_test_status_update(pvt->test, "Unable to obtain bridge for bridging_join_simple\n");
+		return;
+	}
+
+	final_state = ast_bridge_join(bridge, mock_channel, NULL, NULL, NULL);
+}
+
+AST_TEST_DEFINE(bridging_join_simple)
+{
+	RAII_VAR(struct ast_bridge_technology *, mock_tech, &mock_bridge_tech, mock_tech_cleanup);
+	RAII_VAR(struct ast_channel *, mock_channel, NULL, ao2_cleanup);
+	struct mock_channel_pvt *pvt;
+	pthread_t test_thread;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "bridging_join_simple";
+		info->category = "/main/bridging/";
+		info->summary = "";
+		info->description =
+			"";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	mock_tech->capabilities = AST_BRIDGE_CAPABILITY_TEST;
+	ast_bridge_technology_register(mock_tech);
+

[... 218 lines stripped ...]



More information about the svn-commits mailing list