Index: /trunk/CHANGES
===================================================================
--- /trunk/CHANGES (revision 364579)
+++ /trunk/CHANGES (working copy)
@@ -48,6 +48,7 @@
participants in a conference.
* Added announcement configuration option to user profile. If set the sound file will
be played to the user, and only the user, upon joining the conference bridge.
+ * Added AMI command ConfbridgeActionExec. See the AMI changes for more details.
Voicemail
------------------
@@ -184,6 +185,9 @@
behavior to hanging up a single channel is unchanged, but if you pass a regex,
the manager will send you a list of channels back that were hung up.
+ * ConfbridgeActionExec executes any confbridge action on a specified channel
+ in a conference.
+
FAX changes
-----------
* FAXOPT(faxdetect) will enable a generic fax detect framehook for dialplan
Index: /trunk/apps/app_confbridge.c
===================================================================
--- /trunk/apps/app_confbridge.c (revision 364788)
+++ /trunk/apps/app_confbridge.c (working copy)
@@ -255,7 +255,31 @@
-
+
+
+ Execute a ConfBridge action on a particular channel.
+
+
+
+
+ Conference Number.
+
+
+ The channel to execute the actions on.
+
+
+ A comma delineated list of ConfBridge actions to execute on the particular channel.
+
+
+
+ Executes a ConfBridge action on a specific channel. The available actions are defined in
+ confbridge.conf. The actions specified in the Actions parameter
+ are executed sequentially on the channel.
+ 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.
+
+
***/
/*!
@@ -273,6 +297,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;
@@ -2672,6 +2708,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 ConfbridgeActionExec manager command */
+static int action_confbridgeactionexec(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;
@@ -2761,6 +2924,7 @@
res |= ast_manager_unregister("ConfbridgeLock");
res |= ast_manager_unregister("ConfbridgeStartRecord");
res |= ast_manager_unregister("ConfbridgeStopRecord");
+ res |= ast_manager_unregister("ConfbridgeActionExec");
return res;
}
@@ -2803,6 +2967,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("ConfbridgeActionExec", EVENT_FLAG_CALL, action_confbridgeactionexec);
conf_load_config(0);
return res;
Index: /trunk/main/bridging.c
===================================================================
--- /trunk/main/bridging.c (revision 364579)
+++ /trunk/main/bridging.c (working copy)
@@ -51,6 +51,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;
+ /* Deferds 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 */
@@ -806,6 +822,45 @@
}
/*!
+ * \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_FIRST(&bridge_channel->deferds);
+ AST_LIST_REMOVE_HEAD(&bridge_channel->deferds, 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);
+ }
+}
+
+/*!
* \brief Internal function that executes a feature on a bridge channel
* \note Neither the bridge nor the bridge_channel locks should be held when entering
* this function.
@@ -1001,6 +1056,13 @@
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;
}
@@ -1054,13 +1116,23 @@
static void bridge_channel_destroy(void *obj)
{
struct ast_bridge_channel *bridge_channel = obj;
+ struct bridge_deferred_callback *deferred;
if (bridge_channel->bridge) {
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);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&bridge_channel->deferds, deferred, entry) {
+ AST_LIST_REMOVE_CURRENT(entry);
+ if (deferred->pvt_destructor) {
+ deferred->pvt_destructor(deferred->pvt_data);
+ }
+ ast_free(deferred);
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
}
static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge)
@@ -1471,6 +1543,48 @@
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;
+ int res = -1;
+
+ if (!(deferred = ast_calloc(1, sizeof(*deferred)))) {
+ ast_log(AST_LOG_WARNING, "Failed to create deferred callback container\n");
+ return res;
+ }
+ deferred->deferred_cb = deferred_cb;
+ deferred->pvt_data = pvt_data;
+ deferred->pvt_destructor = pvt_destructor;
+
+ ao2_lock(bridge);
+
+ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
+ if (bridge_channel->chan == chan) {
+ ao2_lock(bridge_channel);
+ AST_LIST_INSERT_TAIL(&bridge_channel->deferds, deferred, entry);
+ ao2_unlock(bridge_channel);
+ ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_EXECUTE_DEFERRED);
+ res = 0;
+ break;
+ }
+ }
+
+ ao2_unlock(bridge);
+
+ if (res) {
+ ast_debug(1, "Unable to find channel %s in bridge\n", ast_channel_name(chan));
+ ast_free(deferred);
+ }
+
+ return res;
+}
+
+
void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
{
ao2_lock(bridge);
Index: /trunk/apps/confbridge/conf_config_parser.c
===================================================================
--- /trunk/apps/confbridge/conf_config_parser.c (revision 364579)
+++ /trunk/apps/confbridge/conf_config_parser.c (working copy)
@@ -606,7 +606,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;
@@ -761,7 +761,7 @@
for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
if (!strcasecmp(var->name, "type")) {
continue;
- } else if (add_menu_entry(menu, var->name, var->value)) {
+ } else if (conf_add_menu_entry(menu, var->name, var->value)) {
ast_log(LOG_WARNING, "Unknown option '%s' at line %d of %s is not supported.\n",
var->name, var->lineno, CONFBRIDGE_CONFIG);
}
Index: /trunk/apps/confbridge/include/confbridge.h
===================================================================
--- /trunk/apps/confbridge/include/confbridge.h (revision 364579)
+++ /trunk/apps/confbridge/include/confbridge.h (working copy)
@@ -297,6 +297,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);
Index: /trunk/include/asterisk/bridging.h
===================================================================
--- /trunk/include/asterisk/bridging.h (revision 364579)
+++ /trunk/include/asterisk/bridging.h (working copy)
@@ -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,8 @@
unsigned int drop_silence:1;
};
+struct bridge_deferred_callback;
+
/*!
* \brief Structure that contains information regarding a channel in a bridge
*/
@@ -160,6 +164,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) deferds;
/*! Technology optimization parameters used by bridging technologies capable of
* optimizing based upon talk detection. */
struct ast_bridge_tech_optimizations tech_args;
@@ -251,6 +257,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 +588,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 Optional destructor for the private data
+ *
+ * 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