Add support for configuring named groups of custom call features in
authorRussell Bryant <russell@russellbryant.com>
Thu, 31 May 2007 18:21:47 +0000 (18:21 +0000)
committerRussell Bryant <russell@russellbryant.com>
Thu, 31 May 2007 18:21:47 +0000 (18:21 +0000)
features.conf.  This allows you to create a feature one time, and then map it
into groups for various different key mappings for the same feature, as well
as easy access control to groups of features.
(patch from bbryant)

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@66774 65c4cc65-6c06-0410-ace0-fbb531ad65f3

CHANGES
configs/features.conf.sample
res/res_features.c

diff --git a/CHANGES b/CHANGES
index d87c19e..0f2c29f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -209,3 +209,7 @@ Miscellaneous
      to indicate INUSE or NOT_INUSE when a Local channel is being used as opposed
      to just UNKNOWN if the extension exists.
   * Added support for the Hungarian language for saying numbers, dates, and times.
+  * Added support for configuring named groups of custom call features in
+     features.conf.  This means that features can be written a single time, and
+     then mapped into groups of features for different key mappings or easier
+     access control.
index 85a0d9d..966d008 100644 (file)
@@ -100,3 +100,20 @@ context => parkedcalls             ; Which context parked calls are in
 ;unpauseMonitor => #3,self/callee,UnPauseMonitor   ;Allow the callee to unpause monitoring
 ;                                                  ;on their channel
 ;
+; GROUPS
+;   Groups are groupings of features defined in [applicationmap]
+;   that can have their own key mappings.
+;
+;   Groups are defined as a configuration section,
+;   and can be set as part of DYNAMIC_FEATURES in
+;   the same way that a normal feature can... 
+;      etc:    
+;
+;        Set(DYNAMIC_FEATURES=myGroupName);
+;
+; example:
+; [myGroupName]        ; defines the group named myGroupName
+; testfeature => #9    ; associates testfeature with the group and the keycode #9
+; pauseMonitor         ; associates pauseMonitor with the group and the keycode
+;                      ; defined in [applicationmap]
+
index f67919c..28b41de 100644 (file)
@@ -78,6 +78,24 @@ enum {
        AST_FEATURE_FLAG_BYBOTH  =   (3 << 3),
 };
 
+struct feature_group_exten {
+       AST_LIST_ENTRY(feature_group_exten) entry;
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(exten);
+       );
+       struct ast_call_feature *feature;
+};
+
+struct feature_group {
+       AST_LIST_ENTRY(feature_group) entry;
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(gname);
+       );
+       AST_LIST_HEAD_NOLOCK(, feature_group_exten) features;
+};
+
+static AST_RWLIST_HEAD_STATIC(feature_groups, feature_group);
+
 static char *parkedcall = "ParkedCall";
 
 static int parkaddhints = 0;                               /*!< Add parking hints automatically */
@@ -1010,7 +1028,7 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
 AST_RWLOCK_DEFINE_STATIC(features_lock);
 
 static struct ast_call_feature builtin_features[] = 
- {
+{
        { AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
        { AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
        { AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
@@ -1037,6 +1055,69 @@ void ast_register_feature(struct ast_call_feature *feature)
                ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
 }
 
+/*! \brief This function must be called while feature_groups is locked... */
+static struct feature_group* register_group(const char *fgname)
+{
+       struct feature_group *fg;
+
+       if (!fgname) {
+               ast_log(LOG_NOTICE, "You didn't pass a new group name!\n");
+               return NULL;
+       }
+
+       if (!(fg = ast_calloc(1, sizeof(*fg))))
+               return NULL;
+
+       if (ast_string_field_init(fg, 128)) {
+               free(fg);
+               return NULL;
+       }
+
+       ast_string_field_set(fg, gname, fgname);
+
+       AST_LIST_INSERT_HEAD(&feature_groups, fg, entry);
+
+       if (option_verbose >= 2) 
+               ast_verbose(VERBOSE_PREFIX_2 "Registered group '%s'\n", fg->gname);
+
+       return fg;
+}
+
+/*! \brief This function must be called while feature_groups is locked... */
+
+static void register_group_feature(struct feature_group *fg, const char *exten, struct ast_call_feature *feature) 
+{
+       struct feature_group_exten *fge;
+
+       if (!(fge = ast_calloc(1, sizeof(*fge))))
+               return;
+
+       if (ast_string_field_init(fge, 128)) {
+               free(fge);
+               return;
+       }
+
+       if (!fg) {
+               ast_log(LOG_NOTICE, "You didn't pass a group!\n");
+               return;
+       }
+
+       if (!feature) {
+               ast_log(LOG_NOTICE, "You didn't pass a feature!\n");
+               return;
+       }
+
+       ast_string_field_set(fge, exten, (ast_strlen_zero(exten) ? feature->exten : exten));
+
+       fge->feature = feature;
+
+       AST_LIST_INSERT_HEAD(&fg->features, fge, entry);                
+
+       if (option_verbose >= 2)
+               ast_verbose(VERBOSE_PREFIX_2 "Registered feature '%s' for group '%s' at exten '%s'\n", 
+                                       feature->sname, fg->gname, exten);
+}
+
 /*! \brief unregister feature from feature_list */
 void ast_unregister_feature(struct ast_call_feature *feature)
 {
@@ -1073,6 +1154,48 @@ static struct ast_call_feature *find_dynamic_feature(const char *name)
        return tmp;
 }
 
+/*! \brief Remove all groups in the list */
+static void ast_unregister_groups(void)
+{
+       struct feature_group *fg;
+       struct feature_group_exten *fge;
+
+       AST_RWLIST_WRLOCK(&feature_groups);
+       while ((fg = AST_LIST_REMOVE_HEAD(&feature_groups, entry))) {
+               while ((fge = AST_LIST_REMOVE_HEAD(&fg->features, entry))) {
+                       ast_string_field_free_all(fge);
+                       free(fge);
+               }
+
+               ast_string_field_free_all(fg);
+               free(fg);
+       }
+       AST_RWLIST_UNLOCK(&feature_groups);
+}
+
+/*! \brief Find a group by name */
+static struct feature_group *find_group(const char *name) {
+       struct feature_group *fg = NULL;
+
+       AST_LIST_TRAVERSE(&feature_groups, fg, entry) {
+               if (!strcasecmp(fg->gname, name))
+                       break;
+       }
+
+       return fg;
+}
+
+static struct feature_group_exten *find_group_exten(struct feature_group *fg, const char *code) {
+       struct feature_group_exten *fge = NULL;
+
+       AST_LIST_TRAVERSE(&fg->features, fge, entry) {
+               if(!strcasecmp(fge->exten, code))
+                       break;
+       }
+
+       return fge;
+}
+
 void ast_rdlock_call_features(void)
 {
        ast_rwlock_rdlock(&features_lock);
@@ -1197,13 +1320,15 @@ static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *p
        struct ast_flags features;
        int res = FEATURE_RETURN_PASSDIGITS;
        struct ast_call_feature *feature;
+       struct feature_group *fg = NULL;
+       struct feature_group_exten *fge;
        const char *dynamic_features=pbx_builtin_getvar_helper(chan,"DYNAMIC_FEATURES");
        char *tmp, *tok;
 
        if (sense == FEATURE_SENSE_CHAN)
-               ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);   
+               ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
        else
-               ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);   
+               ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);
        if (option_debug > 2)
                ast_log(LOG_DEBUG, "Feature interpret: chan=%s, peer=%s, sense=%d, features=%d\n", chan->name, peer->name, sense, features.flags);
 
@@ -1229,9 +1354,23 @@ static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *p
        tmp = ast_strdupa(dynamic_features);
 
        while ((tok = strsep(&tmp, "#"))) {
-               AST_LIST_LOCK(&feature_list);   
-               if (!(feature = find_dynamic_feature(tok)))
+               AST_RWLIST_RDLOCK(&feature_groups);
+
+               fg = find_group(tok);
+
+               if (fg && (fge = find_group_exten(fg, code))) {
+                       res = fge->feature->operation(chan, peer, config, code, sense);
+                       AST_RWLIST_UNLOCK(&feature_groups);
                        continue;
+               }
+
+               AST_RWLIST_UNLOCK(&feature_groups);
+               AST_LIST_LOCK(&feature_list);
+
+               if(!(feature = find_dynamic_feature(tok))) {
+                       AST_LIST_UNLOCK(&feature_list);
+                       continue;
+               }
                        
                /* Feature is up for consideration */
                if (!strcmp(feature->exten, code)) {
@@ -2454,11 +2593,22 @@ static int load_config(void)
 {
        int start = 0, end = 0;
        int res;
+       int i;
        struct ast_context *con = NULL;
        struct ast_config *cfg = NULL;
        struct ast_variable *var = NULL;
+       struct feature_group *fg = NULL;
        char old_parking_ext[AST_MAX_EXTENSION];
        char old_parking_con[AST_MAX_EXTENSION] = "";
+       char *ctg; 
+       static const char *categories[] = { 
+               /* Categories in features.conf that are not
+                * to be parsed as group categories
+                */
+               "general",
+               "featuremap",
+               "applicationmap"
+       };
 
        if (!ast_strlen_zero(parking_con)) {
                strcpy(old_parking_ext, parking_ext);
@@ -2671,7 +2821,41 @@ static int load_config(void)
                        
                if (option_verbose >= 1)
                        ast_verbose(VERBOSE_PREFIX_2 "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten);  
-       }        
+       }
+
+       ast_unregister_groups();
+       AST_RWLIST_WRLOCK(&feature_groups);
+
+       ctg = NULL;
+       struct ast_call_feature *feature;
+       while ((ctg = ast_category_browse(cfg, ctg))) {
+               for (i = 0; i < ARRAY_LEN(categories); i++) {
+                       if (!strcasecmp(categories[i], ctg))
+                               break;
+               }
+
+               if (i < ARRAY_LEN(categories)) 
+                       continue;
+
+               if (!(fg = register_group(ctg)))
+                       continue;
+
+               for (var = ast_variable_browse(cfg, ctg); var; var = var->next) {
+                       AST_LIST_LOCK(&feature_list);
+                       if(!(feature = find_dynamic_feature(var->name)) && 
+                          !(feature = ast_find_call_feature(var->name))) {
+                               AST_LIST_UNLOCK(&feature_list);
+                               ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name);
+                               continue;
+                       }
+                       AST_LIST_UNLOCK(&feature_list);
+
+                       register_group_feature(fg, var->value, feature);
+               }
+       }
+
+       AST_RWLIST_UNLOCK(&feature_groups);
+
        ast_config_destroy(cfg);
 
        /* Remove the old parking extension */