app_confbridge: Make the CONFBRIDGE function be able to create dynamic menus

Also adds the ability to clear all profile items and makes behavior more
consistent with documentation as when choosing whether to use CONFBRIDGE
datastore profiles or the application arguments to the confbridge application.

(closes issue ASTERISK-22760)
Reported by: Matt Jordan
Review: https://reviewboard.asterisk.org/r/2971/


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@402397 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Jonathan Rose 2013-11-01 22:48:14 +00:00
parent 3b36687a56
commit 4b7ff87492
4 changed files with 268 additions and 99 deletions

13
CHANGES
View File

@ -8,6 +8,19 @@
===
==============================================================================
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 12 to Asterisk 13 --------------------
------------------------------------------------------------------------------
ConfBridge
--------------------------
* CONFBRIDGE dialplan function is now capable of creating/modifying dynamic
conference user menus.
* CONFBRIDGE dialplan function is now capable of removing dynamic conference
menus, bridge settings, and user settings that have been applied by the
CONFBRIDGE dialplan function.
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 11 to Asterisk 12 --------------------
------------------------------------------------------------------------------

View File

@ -100,8 +100,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</parameter>
<parameter name="menu">
<para>The name of the DTMF menu in confbridge.conf to be applied to
this channel. No menu is applied by default if this option is left
blank.</para>
this channel. When left blank, a dynamically built menu profile
created by the CONFBRIDGE dialplan function is searched for on
the channel and used. If no dynamic profile is present, the
'default_menu' profile found in confbridge.conf is used.</para>
</parameter>
</syntax>
<description>
@ -116,14 +118,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</application>
<function name="CONFBRIDGE" language="en_US">
<synopsis>
Set a custom dynamic bridge and user profile on a channel for the ConfBridge application using the same options defined in confbridge.conf.
Set a custom dynamic bridge, user, or menu profile on a channel for the ConfBridge application using the same options defined in confbridge.conf.
</synopsis>
<syntax>
<parameter name="type" required="true">
<para>Type refers to which type of profile the option belongs too. Type can be <literal>bridge</literal> or <literal>user</literal>.</para>
<para>Type refers to which type of profile the option belongs too. Type can be <literal>bridge</literal>, <literal>user</literal>, or
<literal>menu</literal>.</para>
</parameter>
<parameter name="option" required="true">
<para>Option refers to <filename>confbridge.conf</filename> option that is being set dynamically on this channel.</para>
<para>Option refers to <filename>confbridge.conf</filename> option that is being set dynamically on this channel, or
<literal>clear</literal> to remove already applied options from the channel.</para>
</parameter>
</syntax>
<description>
@ -1504,8 +1508,9 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
int res = 0, volume_adjustments[2];
int quiet = 0;
char *parse;
const char *b_profile_name = DEFAULT_BRIDGE_PROFILE;
const char *u_profile_name = DEFAULT_USER_PROFILE;
const char *b_profile_name = NULL;
const char *u_profile_name = NULL;
const char *menu_profile_name = NULL;
struct confbridge_conference *conference = NULL;
struct confbridge_user user = {
.chan = chan,
@ -1517,7 +1522,7 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
AST_APP_ARG(conf_name);
AST_APP_ARG(b_profile_name);
AST_APP_ARG(u_profile_name);
AST_APP_ARG(menu_name);
AST_APP_ARG(menu_profile_name);
);
if (ast_channel_state(chan) != AST_STATE_UP) {
@ -1545,7 +1550,8 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
b_profile_name = args.b_profile_name;
}
if (!conf_find_bridge_profile(chan, b_profile_name, &user.b_profile)) {
ast_log(LOG_WARNING, "Conference bridge profile %s does not exist\n", b_profile_name);
ast_log(LOG_WARNING, "Conference bridge profile %s does not exist\n", b_profile_name ?
b_profile_name : DEFAULT_BRIDGE_PROFILE);
res = -1;
goto confbridge_cleanup;
}
@ -1555,7 +1561,8 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
u_profile_name = args.u_profile_name;
}
if (!conf_find_user_profile(chan, u_profile_name, &user.u_profile)) {
ast_log(LOG_WARNING, "Conference user profile %s does not exist\n", u_profile_name);
ast_log(LOG_WARNING, "Conference user profile %s does not exist\n", u_profile_name ?
u_profile_name : DEFAULT_USER_PROFILE);
res = -1;
goto confbridge_cleanup;
}
@ -1577,14 +1584,15 @@ static int confbridge_exec(struct ast_channel *chan, const char *data)
}
/* menu name */
if (args.argc > 3 && !ast_strlen_zero(args.menu_name)) {
ast_copy_string(user.menu_name, args.menu_name, sizeof(user.menu_name));
if (conf_set_menu_to_user(user.menu_name, &user)) {
ast_log(LOG_WARNING, "Conference menu %s does not exist and can not be applied to confbridge user.\n",
args.menu_name);
res = -1; /* invalid PIN */
goto confbridge_cleanup;
}
if (args.argc > 3 && !ast_strlen_zero(args.menu_profile_name)) {
menu_profile_name = args.menu_profile_name;
}
if (conf_set_menu_to_user(chan, &user, menu_profile_name)) {
ast_log(LOG_WARNING, "Conference menu profile %s does not exist\n", menu_profile_name ?
menu_profile_name : DEFAULT_MENU_PROFILE);
res = -1;
goto confbridge_cleanup;
}
/* Set if DTMF should pass through for this user or not */

View File

@ -419,6 +419,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
</enumlist>
</description>
</configOption>
<configOption name="template">
<synopsis>When using the CONFBRIDGE dialplan function, use a menu profile as a template for creating a new temporary profile</synopsis>
</configOption>
<configOption name="^[0-9A-D*#]+$">
<synopsis>DTMF sequences to assign various confbridge actions to</synopsis>
<description>
@ -896,15 +899,25 @@ static int set_sound(const char *sound_name, const char *sound_file, struct brid
struct func_confbridge_data {
struct bridge_profile b_profile;
struct user_profile u_profile;
struct conf_menu *menu;
unsigned int b_usable:1; /*!< Tells if bridge profile is usable or not */
unsigned int u_usable:1; /*!< Tells if user profile is usable or not */
unsigned int m_usable:1; /*!< Tells if menu profile is usable or not */
};
static void func_confbridge_data_destructor(struct func_confbridge_data *b_data)
{
conf_bridge_profile_destroy(&b_data->b_profile);
ao2_cleanup(b_data->menu);
ast_free(b_data);
}
static void func_confbridge_destroy_cb(void *data)
{
struct func_confbridge_data *b_data = data;
conf_bridge_profile_destroy(&b_data->b_profile);
ast_free(b_data);
func_confbridge_data_destructor(b_data);
};
static const struct ast_datastore_info confbridge_datastore = {
.type = "confbridge",
.destroy = func_confbridge_destroy_cb
@ -944,14 +957,18 @@ int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data
ast_datastore_free(datastore);
return 0;
}
datastore->data = b_data;
b_data->b_profile.sounds = bridge_profile_sounds_alloc();
if (!b_data->b_profile.sounds) {
ast_channel_unlock(chan);
ast_datastore_free(datastore);
ast_free(b_data);
return 0;
}
datastore->data = b_data;
if (!(b_data->menu = menu_alloc("dialplan"))) {
ast_channel_unlock(chan);
ast_datastore_free(datastore);
return 0;
}
ast_channel_datastore_add(chan, datastore);
} else {
b_data = datastore->data;
@ -966,15 +983,48 @@ int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data
tmpvar.value = value;
tmpvar.file = "CONFBRIDGE";
if (!strcasecmp(args.type, "bridge")) {
if (!aco_process_var(&bridge_type, "dialplan", &tmpvar, &b_data->b_profile)) {
if (!strcasecmp(args.option, "clear")) {
b_data->b_usable = 0;
conf_bridge_profile_destroy(&b_data->b_profile);
memset(&b_data->b_profile, 0, sizeof(b_data->b_profile)) ;
if (!(b_data->b_profile.sounds = bridge_profile_sounds_alloc())) {
/* If this reallocation fails, the datastore has become unusable and must be destroyed. */
ast_channel_lock(chan);
ast_channel_datastore_remove(chan, datastore);
ast_channel_unlock(chan);
ast_datastore_free(datastore);
}
return 0;
} else if (!aco_process_var(&bridge_type, "dialplan", &tmpvar, &b_data->b_profile)) {
b_data->b_usable = 1;
return 0;
}
} else if (!strcasecmp(args.type, "user")) {
if (!aco_process_var(&user_type, "dialplan", &tmpvar, &b_data->u_profile)) {
if (!strcasecmp(args.option, "clear")) {
b_data->u_usable = 0;
user_profile_destructor(&b_data->u_profile);
memset(&b_data->u_profile, 0, sizeof(b_data->u_profile));
return 0;
} else if (!aco_process_var(&user_type, "dialplan", &tmpvar, &b_data->u_profile)) {
b_data->u_usable = 1;
return 0;
}
} else if (!strcasecmp(args.type, "menu")) {
if (!strcasecmp(args.option, "clear")) {
b_data->m_usable = 0;
ao2_cleanup(b_data->menu);
if (!(b_data->menu = menu_alloc("dialplan"))) {
/* If this reallocation fails, the datastore has become unusable and must be destroyed */
ast_channel_lock(chan);
ast_channel_datastore_remove(chan, datastore);
ast_channel_unlock(chan);
ast_datastore_free(datastore);
}
return 0;
} else if (!aco_process_var(&menu_type, "dialplan", &tmpvar, b_data->menu)) {
b_data->m_usable = 1;
return 0;
}
}
ast_log(LOG_WARNING, "%s(%s,%s) cannot be set to '%s'. Invalid type, option, or value.\n",
@ -1852,6 +1902,70 @@ static int bridge_template_handler(const struct aco_option *opt, struct ast_vari
return 0;
}
static int copy_menu_entry(struct conf_menu_entry *dst, struct conf_menu_entry *src)
{
struct conf_menu_action *menu_action;
struct conf_menu_action *new_menu_action;
ast_copy_string(dst->dtmf, src->dtmf, sizeof(dst->dtmf));
AST_LIST_HEAD_INIT_NOLOCK(&dst->actions);
AST_LIST_TRAVERSE(&src->actions, menu_action, action) {
if (!(new_menu_action = ast_calloc(1, sizeof(*new_menu_action)))) {
return -1;
}
memcpy(new_menu_action, menu_action, sizeof(*new_menu_action));
AST_LIST_NEXT(new_menu_action, action) = NULL;
AST_LIST_INSERT_TAIL(&dst->actions, new_menu_action, action);
}
return 0;
}
static int conf_menu_profile_copy(struct conf_menu *dst, struct conf_menu *src)
{
/* Copy each menu item to the dst struct */
struct conf_menu_entry *cur;
AST_LIST_TRAVERSE(&src->entries, cur, entry) {
struct conf_menu_entry *cpy;
if (!(cpy = ast_calloc(1, sizeof(*cpy)))) {
return -1;
}
if (copy_menu_entry(cpy, cur)) {
conf_menu_entry_destroy(cpy);
ast_free(cpy);
return -1;
}
AST_LIST_INSERT_TAIL(&dst->entries, cpy, entry);
}
return 0;
}
static int menu_template_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
struct conf_menu *dst_menu = obj;
struct confbridge_cfg *cfg = aco_pending_config(&cfg_info);
RAII_VAR(struct conf_menu *, src_menu, NULL, ao2_cleanup);
if (!cfg) {
return 0;
}
if (!(src_menu = ao2_find(cfg->menus, var->value, OBJ_KEY))) {
return -1;
}
if (conf_menu_profile_copy(dst_menu, src_menu)) {
return -1;
}
return 0;
}
static int sound_option_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
set_sound(var->name, var->value, obj);
@ -1868,6 +1982,7 @@ static int verify_default_profiles(void)
{
RAII_VAR(struct user_profile *, user_profile, NULL, ao2_cleanup);
RAII_VAR(struct bridge_profile *, bridge_profile, NULL, ao2_cleanup);
RAII_VAR(struct conf_menu *, menu_profile, NULL, ao2_cleanup);
struct confbridge_cfg *cfg = aco_pending_config(&cfg_info);
if (!cfg) {
@ -1896,6 +2011,17 @@ static int verify_default_profiles(void)
ao2_link(cfg->user_profiles, user_profile);
}
menu_profile = ao2_find(cfg->menus, DEFAULT_MENU_PROFILE, OBJ_KEY);
if (!menu_profile) {
menu_profile = menu_alloc(DEFAULT_MENU_PROFILE);
if (!menu_profile) {
return -1;
}
ast_log(AST_LOG_NOTICE, "Adding %s menu to app_confbridge\n", DEFAULT_MENU_PROFILE);
aco_set_defaults(&menu_type, DEFAULT_MENU_PROFILE, menu_profile);
ao2_link(cfg->menus, menu_profile);
}
return 0;
}
@ -1951,6 +2077,7 @@ int conf_load_config(void)
/* Menu options */
aco_option_register(&cfg_info, "type", ACO_EXACT, menu_types, NULL, OPT_NOOP_T, 0, 0);
aco_option_register_custom(&cfg_info, "template", ACO_EXACT, menu_types, NULL, menu_template_handler, 0);
aco_option_register_custom(&cfg_info, "^[0-9A-D*#]+$", ACO_REGEX, menu_types, NULL, menu_option_handler, 0);
if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
@ -1988,11 +2115,7 @@ const struct user_profile *conf_find_user_profile(struct ast_channel *chan, cons
struct func_confbridge_data *b_data = NULL;
RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup);
if (!cfg) {
return NULL;
}
if (chan) {
if (chan && ast_strlen_zero(user_profile_name)) {
ast_channel_lock(chan);
datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL);
ast_channel_unlock(chan);
@ -2005,6 +2128,9 @@ const struct user_profile *conf_find_user_profile(struct ast_channel *chan, cons
}
}
if (!cfg) {
return NULL;
}
if (ast_strlen_zero(user_profile_name)) {
user_profile_name = DEFAULT_USER_PROFILE;
}
@ -2042,11 +2168,7 @@ const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan,
struct func_confbridge_data *b_data = NULL;
RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup);
if (!cfg) {
return NULL;
}
if (chan) {
if (chan && ast_strlen_zero(bridge_profile_name)) {
ast_channel_lock(chan);
datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL);
ast_channel_unlock(chan);
@ -2058,6 +2180,10 @@ const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan,
}
}
}
if (!cfg) {
return NULL;
}
if (ast_strlen_zero(bridge_profile_name)) {
bridge_profile_name = DEFAULT_BRIDGE_PROFILE;
}
@ -2083,7 +2209,7 @@ static void menu_hook_destroy(void *hook_pvt)
struct dtmf_menu_hook_pvt *pvt = hook_pvt;
struct conf_menu_action *action = NULL;
ao2_ref(pvt->menu, -1);
ao2_cleanup(pvt->menu);
while ((action = AST_LIST_REMOVE_HEAD(&pvt->menu_entry.actions, action))) {
ast_free(action);
@ -2098,24 +2224,6 @@ static int menu_hook_callback(struct ast_bridge_channel *bridge_channel, void *h
return conf_handle_dtmf(bridge_channel, pvt->user, &pvt->menu_entry, pvt->menu);
}
static int copy_menu_entry(struct conf_menu_entry *dst, struct conf_menu_entry *src)
{
struct conf_menu_action *menu_action = NULL;
struct conf_menu_action *new_menu_action = NULL;
memcpy(dst, src, sizeof(*dst));
AST_LIST_HEAD_INIT_NOLOCK(&dst->actions);
AST_LIST_TRAVERSE(&src->actions, menu_action, action) {
if (!(new_menu_action = ast_calloc(1, sizeof(*new_menu_action)))) {
return -1;
}
memcpy(new_menu_action, menu_action, sizeof(*new_menu_action));
AST_LIST_INSERT_HEAD(&dst->actions, new_menu_action, action);
}
return 0;
}
void conf_menu_entry_destroy(struct conf_menu_entry *menu_entry)
{
struct conf_menu_action *menu_action = NULL;
@ -2141,37 +2249,24 @@ int conf_find_menu_entry_by_sequence(const char *dtmf_sequence, struct conf_menu
return 0;
}
int conf_set_menu_to_user(const char *menu_name, struct confbridge_user *user)
static int apply_menu_hooks(struct confbridge_user *user, struct conf_menu *menu)
{
struct conf_menu *menu;
struct conf_menu_entry *menu_entry = NULL;
RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup);
struct conf_menu_entry *menu_entry;
if (!cfg) {
return -1;
}
if (!(menu = menu_find(cfg->menus, menu_name))) {
return -1;
}
ao2_lock(menu);
SCOPED_AO2LOCK(menu_lock, menu);
AST_LIST_TRAVERSE(&menu->entries, menu_entry, entry) {
struct dtmf_menu_hook_pvt *pvt;
if (!(pvt = ast_calloc(1, sizeof(*pvt)))) {
ao2_unlock(menu);
ao2_ref(menu, -1);
return -1;
}
if (copy_menu_entry(&pvt->menu_entry, menu_entry)) {
ast_free(pvt);
ao2_unlock(menu);
ao2_ref(menu, -1);
return -1;
}
pvt->user = user;
ao2_ref(menu, +1);
pvt->menu = menu;
pvt->menu = ao2_bump(menu);
if (copy_menu_entry(&pvt->menu_entry, menu_entry)) {
menu_hook_destroy(pvt);
return -1;
}
if (ast_bridge_dtmf_hook(&user->features, pvt->menu_entry.dtmf,
menu_hook_callback, pvt, menu_hook_destroy, 0)) {
@ -2179,12 +2274,47 @@ int conf_set_menu_to_user(const char *menu_name, struct confbridge_user *user)
}
}
ao2_unlock(menu);
ao2_ref(menu, -1);
return 0;
}
int conf_set_menu_to_user(struct ast_channel *chan, struct confbridge_user *user, const char *menu_profile_name)
{
RAII_VAR(struct confbridge_cfg *, cfg, ao2_global_obj_ref(cfg_handle), ao2_cleanup);
RAII_VAR(struct conf_menu *, menu, NULL, ao2_cleanup);
if (chan && ast_strlen_zero(menu_profile_name)) {
struct ast_datastore *datastore;
struct func_confbridge_data *b_data;
ast_channel_lock(chan);
datastore = ast_channel_datastore_find(chan, &confbridge_datastore, NULL);
ast_channel_unlock(chan);
if (datastore) {
/* If a menu exists in the CONFBRIDGE function datastore, use it. */
b_data = datastore->data;
if (b_data->m_usable) {
menu = ao2_bump(b_data->menu);
return apply_menu_hooks(user, menu);
}
}
}
/* Otherwise, we need to get whatever menu profile is specified to use (or default). */
if (!cfg) {
return -1;
}
if (ast_strlen_zero(menu_profile_name)) {
menu_profile_name = DEFAULT_MENU_PROFILE;
}
if (!(menu = ao2_find(cfg->menus, menu_profile_name, OBJ_KEY))) {
return -1;
}
return apply_menu_hooks(user, menu);
}
void conf_destroy_config(void)
{
ast_cli_unregister_multiple(cli_confbridge_parser, ARRAY_LEN(cli_confbridge_parser));

View File

@ -37,6 +37,7 @@
#define DEFAULT_USER_PROFILE "default_user"
#define DEFAULT_BRIDGE_PROFILE "default_bridge"
#define DEFAULT_MENU_PROFILE "default_menu"
#define DEFAULT_TALKING_THRESHOLD 160
#define DEFAULT_SILENCE_THRESHOLD 2500
@ -260,31 +261,56 @@ void conf_destroy_config(void);
* \brief find a user profile given a user profile's name and store
* that profile in result structure.
*
* \details This function first attempts to find any custom user
* profile that might exist on a channel datastore, if that doesn't
* exist it looks up the provided user profile name, if that doesn't
* exist either the default_user profile is used.
* \param chan channel the user profile is requested for
* \param user_profile_name name of the profile requested (optional)
* \param result data contained by the user profile will be copied to this struct pointer
*
* \details If user_profile_name is not provided, this function will
* check for the presence of a user profile set by the CONFBRIDGE
* function on a channel datastore. If that doesn't exist, the
* default_user profile is used.
*
* \retval user profile on success
* \retval NULL on failure
*/
const struct user_profile *conf_find_user_profile(struct ast_channel *chan, const char *user_profile_name, struct user_profile *result);
/*!
* \brief Find a bridge profile
* \brief Find a bridge profile given a bridge profile's name and store
* that profile in result structure.
*
* \details Any bridge profile found using this function must be
* destroyed using conf_bridge_profile_destroy. This function first
* attempts to find any custom bridge profile that might exist on
* a channel datastore, if that doesn't exist it looks up the
* provided bridge profile name, if that doesn't exist either
* the default_bridge profile is used.
* \param chan channel the bridge profile is requested for
* \param bridge_profile_name name of the profile requested (optional)
* \param result data contained by the bridge profile will be copied to this struct pointer
*
* \retval Bridge profile on success
* \details If bridge_profile_name is not provided, this function will
* check for the presence of a bridge profile set by the CONFBRIDGE
* function on a channel datastore. If that doesn't exist, the
* default_bridge profile is used.
*
* \retval bridge profile on success
* \retval NULL on failure
*/
const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan, const char *bridge_profile_name, struct bridge_profile *result);
/*!
* \brief find a menu profile given a menu profile's name and apply
* the menu in DTMF hooks.
*
* \param chan channel the menu profile is requested for
* \param user user profile the menu is being applied to
* \param menu_profile_name name of the profile requested (optional)
*
* \details If menu_profile_name is not provided, this function will
* check for the presence of a menu profile set by the CONFBRIDGE
* function on a channel datastore. If that doesn't exist, the
* default_menu profile is used.
*
* \retval 0 on success
* \retval -1 on failure
*/
int conf_set_menu_to_user(struct ast_channel *chan, struct confbridge_user *user, const char *menu_profile_name);
/*!
* \brief Destroy a bridge profile found by 'conf_find_bridge_profile'
*/
@ -296,14 +322,6 @@ void conf_bridge_profile_destroy(struct bridge_profile *b_profile);
*/
void conf_bridge_profile_copy(struct bridge_profile *dst, struct bridge_profile *src);
/*!
* \brief Set a DTMF menu to a conference user by menu name.
*
* \retval 0 on success, menu was found and set
* \retval -1 on error, menu was not found
*/
int conf_set_menu_to_user(const char *menu_name, struct confbridge_user *user);
/*!
* \brief Finds a menu_entry in a menu structure matched by DTMF sequence.
*