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