manager/config: Support templates and non-unique category names via AMI
authorGeorge Joseph <george.joseph@fairview5.com>
Mon, 13 Oct 2014 16:12:17 +0000 (16:12 +0000)
committerGeorge Joseph <george.joseph@fairview5.com>
Mon, 13 Oct 2014 16:12:17 +0000 (16:12 +0000)
This patch provides the capability to manipulate templates and categories
with non-unique names via AMI.

Summary of changes:

GetConfig and GetConfigJSON: Added "Filter" parameter:  A comma separated list
of name_regex=value_regex expressions which will cause only categories whose
variables match all expressions to be considered.  The special variable name
TEMPLATES can be used to control whether templates are included.  Passing
'include' as the value will include templates along with normal categories.
Passing 'restrict' as the value will restrict the operation to ONLY templates.
Not specifying a TEMPLATES expression results in the current default behavior
which is to not include templates.

UpdateConfig: NewCat now includes options for allowing duplicate category
names, indicating if the category should be created as a template, and
specifying templates the category should inherit from.  The rest of the
actions now accept a filter string as defined above.  If there are non-unique
category names, you can now update specific ones based on variable values.

To facilitate the new capabilities in manager, corresponding changes had to be
made to config, most notably the addition of filter criteria to many of the
APIs.  In some cases it was easy to change the references to use the new
prototype but others would have required touching too many files for this
patch so a wrapper with the original prototype was created.  Macros couldn't
be used in this case because it would break binary compatibility with modules
such as res_digium_phone that are linked to real symbols.

Tested-by: George Joseph

Review: https://reviewboard.asterisk.org/r/4033/
........

Merged revisions 425383 from http://svn.asterisk.org/svn/asterisk/branches/12
........

Merged revisions 425384 from http://svn.asterisk.org/svn/asterisk/branches/13

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

apps/app_directory.c
apps/app_voicemail.c
include/asterisk/config.h
main/config.c
main/manager.c
pbx/pbx_realtime.c
res/res_sorcery_config.c
res/res_sorcery_realtime.c
tests/test_config.c
tests/test_sorcery.c
tests/test_sorcery_realtime.c

index 350401e..267e2e8 100644 (file)
@@ -528,7 +528,7 @@ static struct ast_config *realtime_directory(char *context)
                }
 
                /* Does the context exist within the config file? If not, make one */
-               if (!(cat = ast_category_get(cfg, ctx))) {
+               if (!(cat = ast_category_get(cfg, ctx, NULL))) {
                        if (!(cat = ast_category_new(ctx, "", 99999))) {
                                ast_log(LOG_WARNING, "Out of memory\n");
                                ast_config_destroy(cfg);
index 45f9ebf..c881e49 100644 (file)
@@ -1821,7 +1821,7 @@ static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
                                                new = ast_alloca((strlen(value) + strlen(newpassword) + 1));
                                                sprintf(new, "%s%s", newpassword, value);
                                        }
-                                       if (!(cat = ast_category_get(cfg, category))) {
+                                       if (!(cat = ast_category_get(cfg, category, NULL))) {
                                                ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
                                                break;
                                        }
@@ -1858,7 +1858,7 @@ static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
                                        }
                                        new = ast_alloca(strlen(newpassword) + 1);
                                        sprintf(new, "%s", newpassword);
-                                       if (!(cat = ast_category_get(cfg, category))) {
+                                       if (!(cat = ast_category_get(cfg, category, NULL))) {
                                                ast_debug(4, "failed to get category!\n");
                                                ast_free(var);
                                                break;
@@ -7791,7 +7791,7 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu,
                                char duration_buf[12];
 
                                *duration += prepend_duration;
-                               msg_cat = ast_category_get(msg_cfg, "message");
+                               msg_cat = ast_category_get(msg_cfg, "message", NULL);
                                snprintf(duration_buf, 11, "%ld", *duration);
                                if (!ast_variable_update(msg_cat, "duration", duration_buf, NULL, 0)) {
                                        ast_config_text_file_save(textfile, msg_cfg, "app_voicemail");
@@ -11930,7 +11930,7 @@ static int add_message_id(struct ast_config *msg_cfg, char *dir, int msg, char *
                return -1;
        }
 
-       cat = ast_category_get(msg_cfg, "message");
+       cat = ast_category_get(msg_cfg, "message", NULL);
        if (!cat) {
                ast_log(LOG_ERROR, "Voicemail data file %s/%d.txt has no [message] category?\n", dir, msg);
                ast_variables_destroy(var);
index 541fea4..98cd714 100644 (file)
@@ -203,10 +203,30 @@ void ast_config_sort_categories(struct ast_config *config, int descending,
                                                                int (*comparator)(struct ast_category *p, struct ast_category *q));
 
 /*!
- * \brief Goes through categories
+ * \brief Browse categories with filters
  *
  * \param config Which config structure you wish to "browse"
- * \param prev A pointer to a previous category.
+ * \param category_name An optional category name.
+ * Pass NULL to not restrict by category name.
+ * \param prev A pointer to the starting category structure.
+ * Pass NULL to start at the beginning.
+ * \param filter An optional comma-separated list of <name_regex>=<value_regex>
+ * pairs.  Only categories with matching variables will be returned.
+ * The special name 'TEMPLATES' can be used with the special values
+ * 'include' or 'restrict' to include templates in the result or
+ * restrict the result to only templates.
+ *
+ * \retval a category on success
+ * \retval NULL on failure/no-more-categories
+ */
+struct ast_category *ast_category_browse_filtered(struct ast_config *config,
+       const char *category_name, struct ast_category *prev, const char *filter);
+
+/*!
+ * \brief Browse categories
+ *
+ * \param config Which config structure you wish to "browse"
+ * \param prev_name A pointer to a previous category name.
  *
  * \details
  * This function is kind of non-intuitive in it's use.
@@ -216,13 +236,20 @@ void ast_config_sort_categories(struct ast_config *config, int descending,
  * as the second pointer, and it will return a pointer to the category name
  * afterwards.
  *
- * \retval a category on success
+ * \retval a category name on success
  * \retval NULL on failure/no-more-categories
  */
-char *ast_category_browse(struct ast_config *config, const char *prev);
+char *ast_category_browse(struct ast_config *config, const char *prev_name);
 
 /*!
- * \brief Goes through variables
+ * \brief Browse variables
+ * \param config Which config structure you wish to "browse"
+ * \param category_name Which category to "browse"
+ * \param filter an optional comma-separated list of <name_regex>=<value_regex>
+ * pairs.  Only categories with matching variables will be browsed.
+ * The special name 'TEMPLATES' can be used with the special values
+ * 'include' or 'restrict' to include templates in the result or
+ * restrict the result to only templates.
  *
  * \details
  * Somewhat similar in intent as the ast_category_browse.
@@ -231,7 +258,10 @@ char *ast_category_browse(struct ast_config *config, const char *prev);
  * \retval ast_variable list on success
  * \retval NULL on failure
  */
-struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category);
+struct ast_variable *ast_variable_browse_filtered(const struct ast_config *config,
+       const char *category_name, const char *filter);
+struct ast_variable *ast_variable_browse(const struct ast_config *config,
+       const char *category_name);
 
 /*!
  * \brief given a pointer to a category, return the root variable.
@@ -243,25 +273,50 @@ struct ast_variable *ast_variable_browse(const struct ast_config *config, const
 struct ast_variable *ast_category_first(struct ast_category *cat);
 
 /*!
- * \brief Gets a variable
+ * \brief Gets a variable by context and variable names
  *
  * \param config which (opened) config to use
  * \param category category under which the variable lies
  * \param variable which variable you wish to get the data for
+ * \param filter an optional comma-separated list of <name_regex>=<value_regex>
+ * pairs.  Only categories with matching variables will be searched.
+ * The special name 'TEMPLATES' can be used with the special values
+ * 'include' or 'restrict' to include templates in the result or
+ * restrict the result to only templates.
+ *
+ * \retval The variable value on success
+ * \retval NULL if unable to find it.
+ */
+const char *ast_variable_retrieve_filtered(struct ast_config *config,
+       const char *category, const char *variable, const char *filter);
+const char *ast_variable_retrieve(struct ast_config *config,
+       const char *category, const char *variable);
+
+/*!
+ * \brief Gets a variable from a specific category structure
+ *
+ * \param category category structure under which the variable lies
+ * \param variable which variable you wish to get the data for
  *
  * \details
- * Goes through a given config file in the given category and searches for the given variable
+ * Goes through a given category and searches for the given variable
  *
  * \retval The variable value on success
  * \retval NULL if unable to find it.
  */
-const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable);
+const char *ast_variable_find(const struct ast_category *category, const char *variable);
 
 /*!
  * \brief Retrieve a category if it exists
  *
  * \param config which config to use
  * \param category_name name of the category you're looking for
+ * \param filter If a config contains more than 1 category with the same name,
+ * you can specify a filter to narrow the search.  The filter is a comma-separated
+ * list of <name_regex>=<value_regex> pairs.  Only a category with matching
+ * variables will be returned. The special name 'TEMPLATES' can be used with the
+ * special values 'include' or 'restrict' to include templates in the result or
+ * restrict the result to only templates.
  *
  * \details
  * This will search through the categories within a given config file for a match.
@@ -269,20 +324,57 @@ const char *ast_variable_retrieve(const struct ast_config *config, const char *c
  * \retval pointer to category if found
  * \retval NULL if not.
  */
-struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name);
+struct ast_category *ast_category_get(const struct ast_config *config,
+       const char *category_name, const char *filter);
+
+/*!
+ * \brief Return the name of the category
+ *
+ * \param category category structure
+ *
+ * \retval pointer to category name if found
+ * \retval NULL if not.
+ */
+const char *ast_category_get_name(const struct ast_category *category);
+
+/*!
+ * \brief Check if category is a template
+ *
+ * \param category category structure
+ *
+ * \retval 1 if a template.
+ * \retval 0 if not.
+ */
+int ast_category_is_template(const struct ast_category *category);
+
+/*!
+ * \brief Return the template names this category inherits from
+ *
+ * \param category category structure
+ *
+ * \return an ast_str (which must be freed after use) with a comma
+ * separated list of templates names or NULL if there were no templates.
+ */
+struct ast_str *ast_category_get_templates(const struct ast_category *category);
 
 /*!
  * \brief Check for category duplicates
  *
  * \param config which config to use
  * \param category_name name of the category you're looking for
+ * \param filter an optional comma-separated list of <name_regex>=<value_regex>
+ * pairs.  Only categories with matching variables will be returned.
+ * The special name 'TEMPLATES' can be used with the special values
+ * 'include' or 'restrict' to include templates in the result or
+ * restrict the result to only templates.
  *
  * \details
  * This will search through the categories within a given config file for a match.
  *
  * \return non-zero if found
  */
-int ast_category_exist(const struct ast_config *config, const char *category_name);
+int ast_category_exist(const struct ast_config *config, const char *category_name,
+       const char *filter);
 
 /*!
  * \brief Retrieve realtime configuration
@@ -661,9 +753,23 @@ void ast_config_set_current_category(struct ast_config *cfg, const struct ast_ca
  */
 const char *ast_config_option(struct ast_config *cfg, const char *cat, const char *var);
 
-/*! \brief Create a category structure */
+/*!
+ * \brief Create a category
+ *
+ * \param name name of new category
+ * \param in_file filename which contained the new config
+ * \param lineno line number
+ */
 struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno);
-void ast_category_append(struct ast_config *config, struct ast_category *cat);
+
+/*!
+ * \brief Create a category making it a template
+ *
+ * \param name name of new template
+ * \param in_file filename which contained the new config
+ * \param lineno line number
+ */
+struct ast_category *ast_category_new_template(const char *name, const char *in_file, int lineno);
 
 /*!
  * \brief Inserts new category
@@ -677,17 +783,49 @@ void ast_category_append(struct ast_config *config, struct ast_category *cat);
  * matching the match parameter.
  *
  * \retval 0 if succeeded
- * \retval -1 if NULL parameters or match category was not found
+ * \retval -1 if the specified match category wasn't found
  */
 int ast_category_insert(struct ast_config *config, struct ast_category *cat, const char *match);
-int ast_category_delete(struct ast_config *cfg, const char *category);
 
 /*!
- * \brief Removes and destroys all variables within a category
- * \retval 0 if the category was found and emptied
- * \retval -1 if the category was not found
+ * \brief Delete a category
+ *
+ * \param config which config to use
+ * \param category category to delete
+ *
+ * \return the category after the deleted one which could be NULL.
+ */
+struct ast_category *ast_category_delete(struct ast_config *cfg, struct ast_category *category);
+
+/*!
+ * \brief Appends a category to a config
+ *
+ * \param config which config to use
+ * \param cat category to insert
+ */
+void ast_category_append(struct ast_config *config, struct ast_category *cat);
+
+/*!
+ * \brief Applies base (template) to category.
+ *
+ * \param existing existing category
+ * \param base base category
+ *
+ * \details
+ * This function is used to apply a base (template) to an existing category
+ */
+void ast_category_inherit(struct ast_category *existing, const struct ast_category *base);
+
+/*!
+ * \brief Removes and destroys all variables in a category
+ *
+ * \param category category to empty
+ *
+ * \retval 0 if succeeded
+ * \retval -1 if categopry is NULL
  */
-int ast_category_empty(struct ast_config *cfg, const char *category);
+int ast_category_empty(struct ast_category *category);
+
 void ast_category_destroy(struct ast_category *cat);
 struct ast_variable *ast_category_detach_variables(struct ast_category *cat);
 void ast_category_rename(struct ast_category *cat, const char *name);
index 55c40a9..7009e7d 100644 (file)
@@ -40,6 +40,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include <sys/stat.h>
 
 #include <math.h>      /* HUGE_VAL */
+#include <regex.h>
 
 #define AST_INCLUDE_GLOB 1
 
@@ -71,6 +72,7 @@ static char *extconfig_conf = "extconfig.conf";
 static struct ao2_container *cfg_hooks;
 static void config_hook_exec(const char *filename, const char *module, const struct ast_config *cfg);
 inline struct ast_variable *variable_list_switch(struct ast_variable *l1, struct ast_variable *l2);
+static int does_category_match(struct ast_category *cat, const char *category_name, const char *match);
 
 /*! \brief Structure to keep comments for rewriting configuration files */
 struct ast_comment {
@@ -232,6 +234,8 @@ struct ast_category {
        struct ast_variable *root;
        /*! Last category variable in the list. */
        struct ast_variable *last;
+       /*! Previous node in the list. */
+       struct ast_category *prev;
        /*! Next node in the list. */
        struct ast_category *next;
 };
@@ -599,16 +603,12 @@ void ast_variables_destroy(struct ast_variable *v)
 
 struct ast_variable *ast_variable_browse(const struct ast_config *config, const char *category)
 {
-       struct ast_category *cat = NULL;
-
-       if (!category) {
-               return NULL;
-       }
+       struct ast_category *cat;
 
        if (config->last_browse && (config->last_browse->name == category)) {
                cat = config->last_browse;
        } else {
-               cat = ast_category_get(config, category);
+               cat = ast_category_get(config, category, NULL);
        }
 
        return (cat) ? cat->root : NULL;
@@ -677,29 +677,37 @@ const char *ast_config_option(struct ast_config *cfg, const char *cat, const cha
        return tmp;
 }
 
+const char *ast_variable_retrieve(struct ast_config *config,
+       const char *category, const char *variable)
+{
+       return ast_variable_retrieve_filtered(config, category, variable, NULL);
+}
 
-const char *ast_variable_retrieve(const struct ast_config *config, const char *category, const char *variable)
+const char *ast_variable_retrieve_filtered(struct ast_config *config,
+       const char *category, const char *variable, const char *filter)
 {
-       struct ast_variable *v;
+       struct ast_category *cat = NULL;
+       const char *value;
 
-       if (category) {
-               for (v = ast_variable_browse(config, category); v; v = v->next) {
-                       if (!strcasecmp(variable, v->name)) {
-                               return v->value;
-                       }
+       while ((cat = ast_category_browse_filtered(config, category, cat, filter))) {
+               value = ast_variable_find(cat, variable);
+               if (value) {
+                       return value;
                }
-       } else {
-               struct ast_category *cat;
+       }
 
-               for (cat = config->root; cat; cat = cat->next) {
-                       for (v = cat->root; v; v = v->next) {
-                               if (!strcasecmp(variable, v->name)) {
-                                       return v->value;
-                               }
-                       }
+       return NULL;
+}
+
+const char *ast_variable_find(const struct ast_category *category, const char *variable)
+{
+       struct ast_variable *v;
+
+       for (v = category->root; v; v = v->next) {
+               if (!strcasecmp(variable, v->name)) {
+                       return v->value;
                }
        }
-
        return NULL;
 }
 
@@ -726,7 +734,99 @@ static void move_variables(struct ast_category *old, struct ast_category *new)
        ast_variable_append(new, var);
 }
 
-struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
+/*! \brief Returns true if ALL of the regex expressions and category name match.
+ * Both can be NULL (I.E. no predicate) which results in a true return;
+ */
+static int does_category_match(struct ast_category *cat, const char *category_name, const char *match)
+{
+       char *dupmatch;
+       char *nvp = NULL;
+       int match_found = 0, match_expressions = 0;
+       int template_ok = 0;
+
+       /* Only match on category name if it's not a NULL or empty string */
+       if (!ast_strlen_zero(category_name) && strcasecmp(cat->name, category_name)) {
+               return 0;
+       }
+
+       /* If match is NULL or empty, automatically match if not a template */
+       if (ast_strlen_zero(match)) {
+               return !cat->ignored;
+       }
+
+       dupmatch = ast_strdupa(match);
+
+       while ((nvp = ast_strsep(&dupmatch, ',', AST_STRSEP_STRIP))) {
+               struct ast_variable *v;
+               char *match_name;
+               char *match_value = NULL;
+               char *regerr;
+               int rc;
+               regex_t r_name, r_value;
+
+               match_expressions++;
+
+               match_name = ast_strsep(&nvp, '=', AST_STRSEP_STRIP);
+               match_value = ast_strsep(&nvp, '=', AST_STRSEP_STRIP);
+
+               /* an empty match value is OK.  A NULL match value (no =) is NOT. */
+               if (match_value == NULL) {
+                       break;
+               }
+
+               if (!strcmp("TEMPLATES", match_name)) {
+                       if (!strcasecmp("include", match_value)) {
+                               if (cat->ignored) {
+                                       template_ok = 1;
+                               }
+                               match_found++;
+                       } else if (!strcasecmp("restrict", match_value)) {
+                               if (cat->ignored) {
+                                       match_found++;
+                                       template_ok = 1;
+                               } else {
+                                       break;
+                               }
+                       }
+                       continue;
+               }
+
+               if ((rc = regcomp(&r_name, match_name, REG_EXTENDED | REG_NOSUB))) {
+                       regerr = ast_alloca(128);
+                       regerror(rc, &r_name, regerr, 128);
+                       ast_log(LOG_ERROR, "Regular expression '%s' failed to compile: %s\n",
+                               match_name, regerr);
+                       regfree(&r_name);
+                       return 0;
+               }
+               if ((rc = regcomp(&r_value, match_value, REG_EXTENDED | REG_NOSUB))) {
+                       regerr = ast_alloca(128);
+                       regerror(rc, &r_value, regerr, 128);
+                       ast_log(LOG_ERROR, "Regular expression '%s' failed to compile: %s\n",
+                               match_value, regerr);
+                       regfree(&r_name);
+                       regfree(&r_value);
+                       return 0;
+               }
+
+               for (v = cat->root; v; v = v->next) {
+                       if (!regexec(&r_name, v->name, 0, NULL, 0)
+                               && !regexec(&r_value, v->value, 0, NULL, 0)) {
+                               match_found++;
+                               break;
+                       }
+               }
+               regfree(&r_name);
+               regfree(&r_value);
+       }
+       if (match_found == match_expressions && (!cat->ignored || template_ok)) {
+               return 1;
+       }
+       return 0;
+}
+
+
+static struct ast_category *new_category(const char *name, const char *in_file, int lineno, int template)
 {
        struct ast_category *category;
 
@@ -741,44 +841,85 @@ struct ast_category *ast_category_new(const char *name, const char *in_file, int
        }
        ast_copy_string(category->name, name, sizeof(category->name));
        category->lineno = lineno; /* if you don't know the lineno, set it to 999999 or something real big */
+       category->ignored = template;
        return category;
 }
 
-static struct ast_category *category_get(const struct ast_config *config, const char *category_name, int ignored)
+struct ast_category *ast_category_new(const char *name, const char *in_file, int lineno)
 {
-       struct ast_category *cat;
+       return new_category(name, in_file, lineno, 0);
+}
 
-       /* try exact match first, then case-insensitive match */
-       for (cat = config->root; cat; cat = cat->next) {
-               if (cat->name == category_name && (ignored || !cat->ignored))
-                       return cat;
-       }
+struct ast_category *ast_category_new_template(const char *name, const char *in_file, int lineno)
+{
+       return new_category(name, in_file, lineno, 1);
+}
+
+struct ast_category *ast_category_get(const struct ast_config *config,
+       const char *category_name, const char *filter)
+{
+       struct ast_category *cat;
 
        for (cat = config->root; cat; cat = cat->next) {
-               if (!strcasecmp(cat->name, category_name) && (ignored || !cat->ignored))
+               if (does_category_match(cat, category_name, filter)) {
                        return cat;
+               }
        }
 
        return NULL;
 }
 
-struct ast_category *ast_category_get(const struct ast_config *config, const char *category_name)
+const char *ast_category_get_name(const struct ast_category *category)
 {
-       return category_get(config, category_name, 0);
+       return category->name;
 }
 
-int ast_category_exist(const struct ast_config *config, const char *category_name)
+int ast_category_is_template(const struct ast_category *category)
 {
-       return !!ast_category_get(config, category_name);
+       return category->ignored;
+}
+
+struct ast_str *ast_category_get_templates(const struct ast_category *category)
+{
+       struct ast_category_template_instance *template;
+       struct ast_str *str;
+       int first = 1;
+
+       if (AST_LIST_EMPTY(&category->template_instances)) {
+               return NULL;
+       }
+
+       str = ast_str_create(128);
+       if (!str) {
+               return NULL;
+       }
+
+       AST_LIST_TRAVERSE(&category->template_instances, template, next) {
+               ast_str_append(&str, 0, "%s%s", first ? "" : ",", template->name);
+               first = 0;
+       }
+
+       return str;
+}
+
+int ast_category_exist(const struct ast_config *config, const char *category_name,
+       const char *filter)
+{
+       return !!ast_category_get(config, category_name, filter);
 }
 
 void ast_category_append(struct ast_config *config, struct ast_category *category)
 {
-       if (config->last)
+       if (config->last) {
                config->last->next = category;
-       else
+               category->prev = config->last;
+       } else {
                config->root = category;
+               category->prev = NULL;
+       }
+       category->next = NULL;
        category->include_level = config->include_level;
+
        config->last = category;
        config->current = category;
 }
@@ -787,19 +928,26 @@ int ast_category_insert(struct ast_config *config, struct ast_category *cat, con
 {
        struct ast_category *cur_category;
 
-       if (!config || !cat || !match) {
+       if (!config || !config->root || !cat || !match) {
                return -1;
        }
+
        if (!strcasecmp(config->root->name, match)) {
                cat->next = config->root;
+               cat->prev = NULL;
+               config->root->prev = cat;
                config->root = cat;
                return 0;
        }
-       for (cur_category = config->root; cur_category && cur_category->next;
-               cur_category = cur_category->next) {
-               if (!strcasecmp(cur_category->next->name, match)) {
-                       cat->next = cur_category->next;
-                       cur_category->next = cat;
+
+       for (cur_category = config->root->next; cur_category; cur_category = cur_category->next) {
+               if (!strcasecmp(cur_category->name, match)) {
+                       cat->prev = cur_category->prev;
+                       cat->prev->next = cat;
+
+                       cat->next = cur_category;
+                       cur_category->prev = cat;
+
                        return 0;
                }
        }
@@ -841,9 +989,10 @@ static void ast_includes_destroy(struct ast_config_include *incls)
        }
 }
 
-static struct ast_category *next_available_category(struct ast_category *cat)
+static struct ast_category *next_available_category(struct ast_category *cat,
+       const char *name, const char *filter)
 {
-       for (; cat && cat->ignored; cat = cat->next);
+       for (; cat && !does_category_match(cat, name, filter); cat = cat->next);
 
        return cat;
 }
@@ -856,7 +1005,7 @@ struct ast_variable *ast_category_first(struct ast_category *cat)
 
 struct ast_variable *ast_category_root(struct ast_config *config, char *cat)
 {
-       struct ast_category *category = ast_category_get(config, cat);
+       struct ast_category *category = ast_category_get(config, cat, NULL);
 
        if (category)
                return category->root;
@@ -1021,12 +1170,29 @@ char *ast_category_browse(struct ast_config *config, const char *prev)
        }
 
        if (cat)
-               cat = next_available_category(cat);
+               cat = next_available_category(cat, NULL, NULL);
 
        config->last_browse = cat;
        return (cat) ? cat->name : NULL;
 }
 
+struct ast_category *ast_category_browse_filtered(struct ast_config *config,
+       const char *category_name, struct ast_category *prev, const char *filter)
+{
+       struct ast_category *cat;
+
+       if (!prev) {
+               prev = config->root;
+       } else {
+               prev = prev->next;
+       }
+
+       cat = next_available_category(prev, category_name, filter);
+       config->last_browse = cat;
+
+       return cat;
+}
+
 struct ast_variable *ast_category_detach_variables(struct ast_category *cat)
 {
        struct ast_variable *v;
@@ -1043,7 +1209,7 @@ void ast_category_rename(struct ast_category *cat, const char *name)
        ast_copy_string(cat->name, name, sizeof(cat->name));
 }
 
-static void inherit_category(struct ast_category *new, const struct ast_category *base)
+void ast_category_inherit(struct ast_category *new, const struct ast_category *base)
 {
        struct ast_variable *var;
        struct ast_category_template_instance *x;
@@ -1147,65 +1313,45 @@ int ast_variable_update(struct ast_category *category, const char *variable,
        return -1;
 }
 
-int ast_category_delete(struct ast_config *cfg, const char *category)
+struct ast_category *ast_category_delete(struct ast_config *config,
+       struct ast_category *category)
 {
-       struct ast_category *prev=NULL, *cat;
+       struct ast_category *prev;
 
-       cat = cfg->root;
-       while (cat) {
-               if (cat->name == category) {
-                       if (prev) {
-                               prev->next = cat->next;
-                               if (cat == cfg->last)
-                                       cfg->last = prev;
-                       } else {
-                               cfg->root = cat->next;
-                               if (cat == cfg->last)
-                                       cfg->last = NULL;
-                       }
-                       ast_category_destroy(cat);
-                       return 0;
-               }
-               prev = cat;
-               cat = cat->next;
+       if (!config || !category) {
+               return NULL;
        }
 
-       prev = NULL;
-       cat = cfg->root;
-       while (cat) {
-               if (!strcasecmp(cat->name, category)) {
-                       if (prev) {
-                               prev->next = cat->next;
-                               if (cat == cfg->last)
-                                       cfg->last = prev;
-                       } else {
-                               cfg->root = cat->next;
-                               if (cat == cfg->last)
-                                       cfg->last = NULL;
-                       }
-                       ast_category_destroy(cat);
-                       return 0;
-               }
-               prev = cat;
-               cat = cat->next;
+       if (category->prev) {
+               category->prev->next = category->next;
+       } else {
+               config->root = category->next;
        }
-       return -1;
+
+       if (category->next) {
+               category->next->prev = category->prev;
+       } else {
+               config->last = category->prev;
+       }
+
+       prev = category->prev;
+
+       ast_category_destroy(category);
+
+       return prev;
 }
 
-int ast_category_empty(struct ast_config *cfg, const char *category)
+int ast_category_empty(struct ast_category *category)
 {
-       struct ast_category *cat;
-
-       for (cat = cfg->root; cat; cat = cat->next) {
-               if (strcasecmp(cat->name, category))
-                       continue;
-               ast_variables_destroy(cat->root);
-               cat->root = NULL;
-               cat->last = NULL;
-               return 0;
+       if (!category) {
+               return -1;
        }
 
-       return -1;
+       ast_variables_destroy(category->root);
+       category->root = NULL;
+       category->last = NULL;
+
+       return 0;
 }
 
 void ast_config_destroy(struct ast_config *cfg)
@@ -1489,7 +1635,7 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                                if (!strcasecmp(cur, "!")) {
                                        (*cat)->ignored = 1;
                                } else if (!strcasecmp(cur, "+")) {
-                                       *cat = category_get(cfg, catname, 1);
+                                       *cat = ast_category_get(cfg, catname, NULL);
                                        if (!(*cat)) {
                                                if (newcat)
                                                        ast_category_destroy(newcat);
@@ -1504,12 +1650,12 @@ static int process_text_line(struct ast_config *cfg, struct ast_category **cat,
                                } else {
                                        struct ast_category *base;
 
-                                       base = category_get(cfg, cur, 1);
+                                       base = ast_category_get(cfg, cur, "TEMPLATES=restrict");
                                        if (!base) {
                                                ast_log(LOG_WARNING, "Inheritance requested, but category '%s' does not exist, line %d of %s\n", cur, lineno, configfile);
                                                return -1;
                                        }
-                                       inherit_category(*cat, base);
+                                       ast_category_inherit(*cat, base);
                                }
                        }
                }
index 1bf1f3c..f180991 100644 (file)
@@ -362,10 +362,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="Category">
                                <para>Category in configuration file.</para>
                        </parameter>
+                       <parameter name="Filter">
+                               <para>A comma separated list of
+                               <replaceable>name_regex</replaceable>=<replaceable>value_regex</replaceable>
+                               expressions which will cause only categories whose variables match all expressions
+                               to be considered.  The special variable name <literal>TEMPLATES</literal>
+                               can be used to control whether templates are included.  Passing
+                               <literal>include</literal> as the value will include templates
+                               along with normal categories. Passing
+                               <literal>restrict</literal> as the value will restrict the operation to
+                               ONLY templates.  Not specifying a <literal>TEMPLATES</literal> expression
+                               results in the default behavior which is to not include templates.</para>
+                       </parameter>
                </syntax>
                <description>
                        <para>This action will dump the contents of a configuration
-                       file by category and contents or optionally by specified category only.</para>
+                       file by category and contents or optionally by specified category only.
+                       In the case where a category name is non-unique, a filter may be specified
+                       to match only categories with matching variable values.</para>
                </description>
        </manager>
        <manager name="GetConfigJSON" language="en_US">
@@ -377,11 +391,19 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="Filename" required="true">
                                <para>Configuration filename (e.g. <filename>foo.conf</filename>).</para>
                        </parameter>
+                       <parameter name="Category">
+                               <para>Category in configuration file.</para>
+                       </parameter>
+                       <parameter name="Filter">
+                               <xi:include xpointer="xpointer(/docs/manager[@name='GetConfig']/syntax/parameter[@name='Filter']/para[1])" />
+                       </parameter>
                </syntax>
                <description>
                        <para>This action will dump the contents of a configuration file by category
-                       and contents in JSON format. This only makes sense to be used using rawman over
-                       the HTTP interface.</para>
+                       and contents in JSON format or optionally by specified category only.
+                       This only makes sense to be used using rawman over the HTTP interface.
+                       In the case where a category name is non-unique, a filter may be specified
+                       to match only categories with matching variable values.</para>
                </description>
        </manager>
        <manager name="UpdateConfig" language="en_US">
@@ -399,9 +421,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                        <parameter name="Reload">
                                <para>Whether or not a reload should take place (or name of specific module).</para>
                        </parameter>
-                       <parameter name="Action-XXXXXX">
+                       <parameter name="Action-000000">
                                <para>Action to take.</para>
-                               <para>X's represent 6 digit number beginning with 000000.</para>
+                               <para>0's represent 6 digit number beginning with 000000.</para>
                                <enumlist>
                                        <enum name="NewCat" />
                                        <enum name="RenameCat" />
@@ -413,25 +435,58 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
                                        <enum name="Insert" />
                                </enumlist>
                        </parameter>
-                       <parameter name="Cat-XXXXXX">
+                       <parameter name="Cat-000000">
                                <para>Category to operate on.</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
-                       <parameter name="Var-XXXXXX">
+                       <parameter name="Var-000000">
                                <para>Variable to work on.</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
-                       <parameter name="Value-XXXXXX">
+                       <parameter name="Value-000000">
                                <para>Value to work on.</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
-                       <parameter name="Match-XXXXXX">
+                       <parameter name="Match-000000">
                                <para>Extra match required to match line.</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
-                       <parameter name="Line-XXXXXX">
+                       <parameter name="Line-000000">
                                <para>Line in category to operate on (used with delete and insert actions).</para>
-                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-XXXXXX']/para[2])" />
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
+                       </parameter>
+                       <parameter name="Options-000000">
+                               <para>A comma separated list of action-specific options.</para>
+                                       <enumlist>
+                                               <enum name="NewCat"><para>One or more of the following... </para>
+                                                       <enumlist>
+                                                               <enum name="allowdups"><para>Allow duplicate category names.</para></enum>
+                                                               <enum name="template"><para>This category is a template.</para></enum>
+                                                               <enum name="inherit=&quot;template[,...]&quot;"><para>Templates from which to inherit.</para></enum>
+                                                       </enumlist>
+                                               </enum>
+                                       </enumlist>
+                                       <para> </para>
+                                               <para>The following actions share the same options...</para>
+                                       <enumlist>
+                                               <enum name="RenameCat"/>
+                                               <enum name="DelCat"/>
+                                               <enum name="EmptyCat"/>
+                                               <enum name="Update"/>
+                                               <enum name="Delete"/>
+                                               <enum name="Append"/>
+                                               <enum name="Insert"><para> </para>
+                                                       <enumlist>
+                                                               <enum name="catfilter=&quot;&lt;expression&gt;[,...]&quot;"><para> </para>
+                                                                       <xi:include xpointer="xpointer(/docs/manager[@name='GetConfig']/syntax/parameter[@name='Filter']/para[1])" />
+                                                                       <para><literal>catfilter</literal> is most useful when a file
+                                                                       contains multiple categories with the same name and you wish to
+                                                                       operate on specific ones instead of all of them.</para>
+                                                               </enum>
+                                                       </enumlist>
+                                               </enum>
+                                       </enumlist>
+                               <xi:include xpointer="xpointer(/docs/manager[@name='UpdateConfig']/syntax/parameter[@name='Action-000000']/para[2])" />
                        </parameter>
                </syntax>
                <description>
@@ -1183,7 +1238,8 @@ enum error_type {
        FAILURE_EMPTYCAT,
        FAILURE_UPDATE,
        FAILURE_DELETE,
-       FAILURE_APPEND
+       FAILURE_APPEND,
+       FAILURE_TEMPLATE
 };
 
 enum add_filter_result {
@@ -3165,9 +3221,11 @@ static int action_getconfig(struct mansession *s, const struct message *m)
        struct ast_config *cfg;
        const char *fn = astman_get_header(m, "Filename");
        const char *category = astman_get_header(m, "Category");
+       const char *filter = astman_get_header(m, "Filter");
+       const char *category_name;
        int catcount = 0;
        int lineno = 0;
-       char *cur_category = NULL;
+       struct ast_category *cur_category = NULL;
        struct ast_variable *v;
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
 
@@ -3175,6 +3233,7 @@ static int action_getconfig(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Filename not specified");
                return 0;
        }
+
        cfg = ast_config_load2(fn, "manager", config_flags);
        if (cfg == CONFIG_STATUS_FILEMISSING) {
                astman_send_error(s, m, "Config file not found");
@@ -3185,19 +3244,34 @@ static int action_getconfig(struct mansession *s, const struct message *m)
        }
 
        astman_start_ack(s, m);
-       while ((cur_category = ast_category_browse(cfg, cur_category))) {
-               if (ast_strlen_zero(category) || (!ast_strlen_zero(category) && !strcmp(category, cur_category))) {
-                       lineno = 0;
-                       astman_append(s, "Category-%06d: %s\r\n", catcount, cur_category);
-                       for (v = ast_variable_browse(cfg, cur_category); v; v = v->next) {
-                               astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
-                       }
-                       catcount++;
+       while ((cur_category = ast_category_browse_filtered(cfg, category, cur_category, filter))) {
+               struct ast_str *templates;
+
+               category_name = ast_category_get_name(cur_category);
+               lineno = 0;
+               astman_append(s, "Category-%06d: %s\r\n", catcount, category_name);
+
+               if (ast_category_is_template(cur_category)) {
+                       astman_append(s, "IsTemplate-%06d: %d\r\n", catcount, 1);
+               }
+
+               if ((templates = ast_category_get_templates(cur_category))
+                       && ast_str_strlen(templates) > 0) {
+                       astman_append(s, "Templates-%06d: %s\r\n", catcount, ast_str_buffer(templates));
+                       ast_free(templates);
                }
+
+               for (v = ast_category_first(cur_category); v; v = v->next) {
+                       astman_append(s, "Line-%06d-%06d: %s=%s\r\n", catcount, lineno++, v->name, v->value);
+               }
+
+               catcount++;
        }
+
        if (!ast_strlen_zero(category) && catcount == 0) { /* TODO: actually, a config with no categories doesn't even get loaded */
                astman_append(s, "No categories found\r\n");
        }
+
        ast_config_destroy(cfg);
        astman_append(s, "\r\n");
 
@@ -3208,7 +3282,8 @@ static int action_listcategories(struct mansession *s, const struct message *m)
 {
        struct ast_config *cfg;
        const char *fn = astman_get_header(m, "Filename");
-       char *category = NULL;
+       const char *match = astman_get_header(m, "Match");
+       struct ast_category *category = NULL;
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
        int catcount = 0;
 
@@ -3216,6 +3291,7 @@ static int action_listcategories(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Filename not specified");
                return 0;
        }
+
        if (!(cfg = ast_config_load2(fn, "manager", config_flags))) {
                astman_send_error(s, m, "Config file not found");
                return 0;
@@ -3223,23 +3299,23 @@ static int action_listcategories(struct mansession *s, const struct message *m)
                astman_send_error(s, m, "Config file has invalid format");
                return 0;
        }
+
        astman_start_ack(s, m);
-       while ((category = ast_category_browse(cfg, category))) {
-               astman_append(s, "Category-%06d: %s\r\n", catcount, category);
+       while ((category = ast_category_browse_filtered(cfg, NULL, category, match))) {
+               astman_append(s, "Category-%06d: %s\r\n", catcount, ast_category_get_name(category));
                catcount++;
        }
+
        if (catcount == 0) { /* TODO: actually, a config with no categories doesn't even get loaded */
                astman_append(s, "Error: no categories found\r\n");
        }
+
        ast_config_destroy(cfg);
        astman_append(s, "\r\n");
 
        return 0;
 }
 
-
-
-
 /*! The amount of space in out must be at least ( 2 * strlen(in) + 1 ) */
 static void json_escape(char *out, const char *in)
 {
@@ -3274,7 +3350,10 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
 {
        struct ast_config *cfg;
        const char *fn = astman_get_header(m, "Filename");
-       char *category = NULL;
+       const char *filter = astman_get_header(m, "Filter");
+       const char *category = astman_get_header(m, "Category");
+       struct ast_category *cur_category = NULL;
+       const char *category_name;
        struct ast_variable *v;
        int comma1 = 0;
        struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS | CONFIG_FLAG_NOCACHE };
@@ -3294,14 +3373,30 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
 
        astman_start_ack(s, m);
        astman_append(s, "JSON: {");
-       while ((category = ast_category_browse(cfg, category))) {
+       while ((cur_category = ast_category_browse_filtered(cfg, category, cur_category, filter))) {
                int comma2 = 0;
+               struct ast_str *templates;
 
+               category_name = ast_category_get_name(cur_category);
                astman_append(s, "%s\"", comma1 ? "," : "");
-               astman_append_json(s, category);
+               astman_append_json(s, category_name);
                astman_append(s, "\":[");
                comma1 = 1;
-               for (v = ast_variable_browse(cfg, category); v; v = v->next) {
+
+               if (ast_category_is_template(cur_category)) {
+                       astman_append(s, "istemplate:1");
+                       comma2 = 1;
+               }
+
+               if ((templates = ast_category_get_templates(cur_category))
+                       && ast_str_strlen(templates) > 0) {
+                       astman_append(s, "%s", comma2 ? "," : "");
+                       astman_append(s, "templates:\"%s\"", ast_str_buffer(templates));
+                       ast_free(templates);
+                       comma2 = 1;
+               }
+
+               for (v = ast_category_first(cur_category); v; v = v->next) {
                        astman_append(s, "%s\"", comma2 ? "," : "");
                        astman_append_json(s, v->name);
                        astman_append(s, "\":\"");
@@ -3309,6 +3404,7 @@ static int action_getconfigjson(struct mansession *s, const struct message *m)
                        astman_append(s, "\"");
                        comma2 = 1;
                }
+
                astman_append(s, "]");
        }
        astman_append(s, "}\r\n\r\n");
@@ -3323,19 +3419,28 @@ static enum error_type handle_updates(struct mansession *s, const struct message
 {
        int x;
        char hdr[40];
-       const char *action, *cat, *var, *value, *match, *line;
-       struct ast_category *category;
+       const char *action, *cat, *var, *value, *match, *line, *options;
        struct ast_variable *v;
        struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
        enum error_type result = 0;
 
        for (x = 0; x < 100000; x++) {  /* 100000 = the max number of allowed updates + 1 */
                unsigned int object = 0;
+               char *dupoptions;
+               int allowdups = 0;
+               int istemplate = 0;
+               int ignoreerror = 0;
+               char *inherit = NULL;
+               char *catfilter = NULL;
+               char *token;
+               int foundvar = 0;
+               int foundcat = 0;
+               struct ast_category *category = NULL;
 
                snprintf(hdr, sizeof(hdr), "Action-%06d", x);
                action = astman_get_header(m, hdr);
                if (ast_strlen_zero(action))            /* breaks the for loop if no action header */
-                       break;                          /* this could cause problems if actions come in misnumbered */
+                       break;                                                  /* this could cause problems if actions come in misnumbered */
 
                snprintf(hdr, sizeof(hdr), "Cat-%06d", x);
                cat = astman_get_header(m, hdr);
@@ -3361,22 +3466,90 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                snprintf(hdr, sizeof(hdr), "Line-%06d", x);
                line = astman_get_header(m, hdr);
 
+               snprintf(hdr, sizeof(hdr), "Options-%06d", x);
+               options = astman_get_header(m, hdr);
+               if (!ast_strlen_zero(options)) {
+                       dupoptions = ast_strdupa(options);
+                       while ((token = ast_strsep(&dupoptions, ',', AST_STRSEP_STRIP))) {
+                               if (!strcasecmp("allowdups", token)) {
+                                       allowdups = 1;
+                                       continue;
+                               }
+                               if (!strcasecmp("template", token)) {
+                                       istemplate = 1;
+                                       continue;
+                               }
+                               if (!strcasecmp("ignoreerror", token)) {
+                                       ignoreerror = 1;
+                                       continue;
+                               }
+                               if (ast_begins_with(token, "inherit")) {
+                                       char *c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
+                                       c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
+                                       if (c) {
+                                               inherit = ast_strdupa(c);
+                                       }
+                                       continue;
+                               }
+                               if (ast_begins_with(token, "catfilter")) {
+                                       char *c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
+                                       c = ast_strsep(&token, '=', AST_STRSEP_STRIP);
+                                       if (c) {
+                                               catfilter = ast_strdupa(c);
+                                       }
+                                       continue;
+                               }
+                       }
+               }
+
                if (!strcasecmp(action, "newcat")) {
-                       if (ast_category_get(cfg,cat)) {        /* check to make sure the cat doesn't */
-                               result = FAILURE_NEWCAT;        /* already exist */
-                               break;
+                       struct ast_category *template;
+                       char *tmpl_name = NULL;
+
+                       if (!allowdups) {
+                               if (ast_category_get(cfg, cat, "TEMPLATES=include")) {
+                                       if (ignoreerror) {
+                                               continue;
+                                       } else {
+                                               result = FAILURE_NEWCAT;        /* already exist */
+                                               break;
+                                       }
+                               }
+                       }
+
+                       if (istemplate) {
+                               category = ast_category_new_template(cat, dfn, -1);
+                       } else {
+                               category = ast_category_new(cat, dfn, -1);
                        }
-                       if (!(category = ast_category_new(cat, dfn, -1))) {
+
+                       if (!category) {
                                result = FAILURE_ALLOCATION;
                                break;
                        }
-                       if (ast_strlen_zero(match)) {
-                               ast_category_append(cfg, category);
-                       } else {
-                               if (ast_category_insert(cfg, category, match)) {
-                                       result = FAILURE_NEWCAT;
-                                       ast_category_destroy(category);
-                                       break;
+
+                       if (inherit) {
+                               while ((tmpl_name = ast_strsep(&inherit, ',', AST_STRSEP_STRIP))) {
+                                       if ((template = ast_category_get(cfg, tmpl_name, "TEMPLATES=restrict"))) {
+                                               ast_category_inherit(category, template);
+                                       } else {
+                                               ast_category_destroy(category);
+                                               category = NULL;
+                                               result = FAILURE_TEMPLATE;      /* template not found */
+                                               break;
+                                       }
+                               }
+                       }
+
+                       if (category != NULL) {
+                               if (ast_strlen_zero(match)) {
+                                       ast_category_append(cfg, category);
+                               } else {
+                                       if (ast_category_insert(cfg, category, match)) {
+                                               ast_category_destroy(category);
+                                               result = FAILURE_NEWCAT;
+                                               break;
+                                       }
                                }
                        }
                } else if (!strcasecmp(action, "renamecat")) {
@@ -3384,19 +3557,37 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg, cat))) {
+
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               ast_category_rename(category, value);
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat) {
                                result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       ast_category_rename(category, value);
                } else if (!strcasecmp(action, "delcat")) {
-                       if (ast_category_delete(cfg, cat)) {
-                               result = FAILURE_DELCAT;
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               category = ast_category_delete(cfg, category);
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat && !ignoreerror) {
+                               result = UNKNOWN_CATEGORY;
                                break;
                        }
                } else if (!strcasecmp(action, "emptycat")) {
-                       if (ast_category_empty(cfg, cat)) {
-                               result = FAILURE_EMPTYCAT;
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               ast_category_empty(category);
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat) {
+                               result = UNKNOWN_CATEGORY;
                                break;
                        }
                } else if (!strcasecmp(action, "update")) {
@@ -3404,11 +3595,22 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg,cat))) {
+
+                       foundcat = 0;
+                       foundvar = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               if (!ast_variable_update(category, var, value, match, object)) {
+                                       foundvar = 1;
+                               }
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat) {
                                result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       if (ast_variable_update(category, var, value, match, object)) {
+
+                       if (!foundvar) {
                                result = FAILURE_UPDATE;
                                break;
                        }
@@ -3417,12 +3619,23 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg, cat))) {
+
+                       foundcat = 0;
+                       foundvar = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               if (!ast_variable_delete(category, var, match, line)) {
+                                       foundvar = 1;
+                               }
+                               foundcat = 1;
+                       }
+
+                       if (!foundcat) {
                                result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       if (ast_variable_delete(category, var, match, line)) {
-                               result = FAILURE_DELETE;
+
+                       if (!foundvar && !ignoreerror) {
+                               result = FAILURE_UPDATE;
                                break;
                        }
                } else if (!strcasecmp(action, "append")) {
@@ -3430,32 +3643,44 @@ static enum error_type handle_updates(struct mansession *s, const struct message
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg, cat))) {
-                               result = UNKNOWN_CATEGORY;
-                               break;
+
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               if (!(v = ast_variable_new(var, value, dfn))) {
+                                       result = FAILURE_ALLOCATION;
+                                       break;
+                               }
+                               if (object || (match && !strcasecmp(match, "object"))) {
+                                       v->object = 1;
+                               }
+                               ast_variable_append(category, v);
+                               foundcat = 1;
                        }
-                       if (!(v = ast_variable_new(var, value, dfn))) {
-                               result = FAILURE_ALLOCATION;
+
+                       if (!foundcat) {
+                               result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       if (object || (match && !strcasecmp(match, "object"))) {
-                               v->object = 1;
-                       }
-                       ast_variable_append(category, v);
                } else if (!strcasecmp(action, "insert")) {
                        if (ast_strlen_zero(var) || ast_strlen_zero(line)) {
                                result = UNSPECIFIED_ARGUMENT;
                                break;
                        }
-                       if (!(category = ast_category_get(cfg, cat))) {
-                               result = UNKNOWN_CATEGORY;
-                               break;
+
+                       foundcat = 0;
+                       while ((category = ast_category_browse_filtered(cfg, cat, category, catfilter))) {
+                               if (!(v = ast_variable_new(var, value, dfn))) {
+                                       result = FAILURE_ALLOCATION;
+                                       break;
+                               }
+                               ast_variable_insert(category, v, line);
+                               foundcat = 1;
                        }
-                       if (!(v = ast_variable_new(var, value, dfn))) {
-                               result = FAILURE_ALLOCATION;
+
+                       if (!foundcat) {
+                               result = UNKNOWN_CATEGORY;
                                break;
                        }
-                       ast_variable_insert(category, v, line);
                }
                else {
                        ast_log(LOG_WARNING, "Action-%06d: %s not handled\n", x, action);
@@ -3541,6 +3766,9 @@ static int action_updateconfig(struct mansession *s, const struct message *m)
                case FAILURE_APPEND:
                        astman_send_error(s, m, "Append did not complete successfully");
                        break;
+               case FAILURE_TEMPLATE:
+                       astman_send_error(s, m, "Template category not found");
+                       break;
                }
        }
        return 0;
index de62851..6c8e671 100644 (file)
@@ -206,7 +206,7 @@ static struct ast_variable *realtime_switch_common(const char *table, const char
                                        match = ast_extension_match(cat, exten);
                                }
                                if (match) {
-                                       var = ast_category_detach_variables(ast_category_get(cfg, cat));
+                                       var = ast_category_detach_variables(ast_category_get(cfg, cat, NULL));
                                        break;
                                }
                                cat = ast_category_browse(cfg, cat);
index d43a495..176c7fe 100644 (file)
@@ -234,6 +234,7 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s
        struct sorcery_config *config = data;
        struct ast_flags flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
        struct ast_config *cfg = ast_config_load2(config->filename, config->uuid, flags);
+       struct ast_category *category = NULL;
        RAII_VAR(struct ao2_container *, objects, NULL, ao2_cleanup);
        const char *id = NULL;
 
@@ -255,16 +256,17 @@ static void sorcery_config_internal_load(void *data, const struct ast_sorcery *s
                return;
        }
 
-       while ((id = ast_category_browse(cfg, id))) {
+       while ((category = ast_category_browse_filtered(cfg, NULL, category, NULL))) {
                RAII_VAR(void *, obj, NULL, ao2_cleanup);
+               id = ast_category_get_name(category);
 
                /* If given criteria has not been met skip the category, it is not applicable */
-               if (!sorcery_is_criteria_met(ast_variable_browse(cfg, id), config->criteria)) {
+               if (!sorcery_is_criteria_met(ast_category_first(category), config->criteria)) {
                        continue;
                }
 
                if (!(obj = ast_sorcery_alloc(sorcery, type, id)) ||
-                   ast_sorcery_objectset_apply(sorcery, obj, ast_variable_browse(cfg, id))) {
+                   ast_sorcery_objectset_apply(sorcery, obj, ast_category_first(category))) {
 
                        if (config->file_integrity) {
                                ast_log(LOG_ERROR, "Config file '%s' could not be loaded due to error with object '%s' of type '%s'\n",
index 47cd736..52b097f 100644 (file)
@@ -177,7 +177,7 @@ static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery
        const char *family = data;
        RAII_VAR(struct ast_config *, rows, NULL, ast_config_destroy);
        RAII_VAR(struct ast_variable *, all, NULL, ast_variables_destroy);
-       char *row = NULL;
+       struct ast_category *row = NULL;
 
        if (!fields) {
                char field[strlen(UUID_FIELD) + 6], value[2];
@@ -197,9 +197,8 @@ static void sorcery_realtime_retrieve_multiple(const struct ast_sorcery *sorcery
                return;
        }
 
-       while ((row = ast_category_browse(rows, row))) {
-               struct ast_category *cat = ast_category_get(rows, row);
-               struct ast_variable *objectset = ast_category_detach_variables(cat);
+       while ((row = ast_category_browse_filtered(rows, NULL, row, NULL))) {
+               struct ast_variable *objectset = ast_category_detach_variables(row);
                RAII_VAR(struct ast_variable *, id, NULL, ast_variables_destroy);
                RAII_VAR(void *, object, NULL, ao2_cleanup);
 
index a9a281d..b1f55c7 100644 (file)
@@ -227,6 +227,533 @@ out:
        return res;
 }
 
+AST_TEST_DEFINE(config_basic_ops)
+{
+       enum ast_test_result_state res = AST_TEST_FAIL;
+       struct ast_config *cfg = NULL;
+       struct ast_category *cat = NULL;
+       struct ast_variable *var;
+       char temp[32];
+       const char *cat_name;
+       const char *var_value;
+       int i;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "config_basic_ops";
+               info->category = "/main/config/";
+               info->summary = "Test basic config ops";
+               info->description =     "Test basic config ops";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       cfg = ast_config_new();
+       if (!cfg) {
+               return res;
+       }
+
+       /* load the config */
+       for(i = 0; i < 5; i++) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               ast_category_append(cfg, ast_category_new(temp, "dummy", -1));
+       }
+
+       /* test0 test1 test2 test3 test4 */
+       /* check the config has 5 elements */
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               if (strcmp(ast_category_get_name(cat), temp)) {
+                       ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
+                       goto out;
+               }
+               i++;
+       }
+       if (i != 5) {
+               ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
+               goto out;
+       }
+
+       /* search for test2 */
+       cat = ast_category_get(cfg, "test2", NULL);
+       if (!cat || strcmp(ast_category_get_name(cat), "test2")) {
+               ast_test_status_update(test, "Get failed %s != %s\n", ast_category_get_name(cat), "test2");
+               goto out;
+       }
+
+       /* delete test2 */
+       cat = ast_category_delete(cfg, cat);
+
+       /* Now: test0 test1 test3 test4 */
+       /* make sure the curr category is test1 */
+       if (!cat || strcmp(ast_category_get_name(cat), "test1")) {
+               ast_test_status_update(test, "Delete failed %s != %s\n", ast_category_get_name(cat), "test1");
+               goto out;
+       }
+
+       /* Now: test0 test1 test3 test4 */
+       /* make sure the test2 is not found */
+       cat = ast_category_get(cfg, "test2", NULL);
+       if (cat) {
+               ast_test_status_update(test, "Should not have found test2\n");
+               goto out;
+       }
+
+       /* Now: test0 test1 test3 test4 */
+       /* make sure the sequence is correctly missing test2 */
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               if (strcmp(ast_category_get_name(cat), temp)) {
+                       ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
+                       goto out;
+               }
+               i++;
+               if (i == 2) {
+                       i++;
+               }
+       }
+       if (i != 5) {
+               ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
+               goto out;
+       }
+
+       /* insert test2 back in before test3 */
+       ast_category_insert(cfg, ast_category_new("test2", "dummy", -1), "test3");
+
+       /* Now: test0 test1 test2 test3 test4 */
+       /* make sure the sequence is correct again */
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               if (strcmp(ast_category_get_name(cat), temp)) {
+                       ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
+                       goto out;
+               }
+               i++;
+       }
+       if (i != 5) {
+               ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
+               goto out;
+       }
+
+       /* Now: test0 test1 test2 test3 test4 */
+       /* make sure non filtered browse still works */
+       i = 0;
+       cat_name = NULL;
+       while ((cat_name = ast_category_browse(cfg, cat_name))) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               if (strcmp(cat_name, temp)) {
+                       ast_test_status_update(test, "%s != %s\n", cat_name, temp);
+                       goto out;
+               }
+               i++;
+       }
+       if (i != 5) {
+               ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
+               goto out;
+       }
+
+       /* append another test2 */
+       ast_category_append(cfg, ast_category_new("test2", "dummy", -1));
+       /* Now: test0 test1 test2 test3 test4 test2*/
+       /* make sure only test2's are returned */
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, "test2", cat, NULL))) {
+               if (strcmp(ast_category_get_name(cat), "test2")) {
+                       ast_test_status_update(test, "Should have returned test2 instead of %s\n", ast_category_get_name(cat));
+                       goto out;
+               }
+               i++;
+       }
+       /* make sure 2 test2's were found */
+       if (i != 2) {
+               ast_test_status_update(test, "Should have found 2 test2's %d\n", i);
+               goto out;
+       }
+
+       /* Test in-flight deletion using ast_category_browse_filtered */
+       /* Now: test0 test1 test2 test3 test4 test2 */
+       /* Delete the middle test2 and continue */
+       cat = NULL;
+       for(i = 0; i < 5; i++) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
+               cat_name = ast_category_get_name(cat);
+               if (strcmp(cat_name, temp)) {
+                       ast_test_status_update(test, "Should have returned %s instead of %s: %d\n", temp, cat_name, i);
+                       goto out;
+               }
+               if (i == 2) {
+                       cat = ast_category_delete(cfg, cat);
+               }
+       }
+
+       /* Now: test0 test1 test3 test4 test2 */
+       /* Test in-flight deletion using ast_category_browse */
+       /* Delete test1 and continue */
+       cat_name = NULL;
+       for(i = 0; i < 5; i++) {
+               if (i == 2) {  /* 2 was already deleted above */
+                       continue;
+               }
+               snprintf(temp, sizeof(temp), "test%d", i);
+               cat_name = ast_category_browse(cfg, cat_name);
+               cat = ast_category_get(cfg, cat_name, NULL);
+               if (strcmp(cat_name, temp)) {
+                       ast_test_status_update(test, "Should have returned %s instead of %s: %d\n", temp, cat_name, i);
+                       goto out;
+               }
+               if (i == 1) {
+                       ast_category_delete(cfg, cat);
+               }
+       }
+
+       /* Now: test0 test3 test4 test2 */
+       /* delete the head item */
+       cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL);
+       cat_name = ast_category_get_name(cat);
+       if (strcmp(cat_name, "test0")) {
+               ast_test_status_update(test, "Should have returned test0 instead of %s\n", cat_name);
+               goto out;
+       }
+       ast_category_delete(cfg, cat);
+       /* Now: test3 test4 test2 */
+
+       /* make sure head got updated to the new first element */
+       cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL);
+       cat_name = ast_category_get_name(cat);
+       if (strcmp(cat_name, "test3")) {
+               ast_test_status_update(test, "Should have returned test3 instead of %s\n", cat_name);
+               goto out;
+       }
+
+       /* delete the tail item */
+       cat = ast_category_get(cfg, "test2", NULL);
+       cat_name = ast_category_get_name(cat);
+       if (strcmp(cat_name, "test2")) {
+               ast_test_status_update(test, "Should have returned test2 instead of %s\n", cat_name);
+               goto out;
+       }
+       ast_category_delete(cfg, cat);
+       /* Now: test3 test4 */
+
+       /* There should now only be 2 elements in the list */
+       cat = NULL;
+       cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
+       cat_name = ast_category_get_name(cat);
+       if (strcmp(cat_name, "test3")) {
+               ast_test_status_update(test, "Should have returned test3 instead of %s\n", cat_name);
+               goto out;
+       }
+
+       cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
+       cat_name = ast_category_get_name(cat);
+       if (strcmp(cat_name, "test4")) {
+               ast_test_status_update(test, "Should have returned test4 instead of %s\n", cat_name);
+               goto out;
+       }
+
+       /* There should be nothing more */
+       cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
+       if (cat) {
+               ast_test_status_update(test, "Should not have returned anything\n");
+               goto out;
+       }
+
+       /* Test ast_variable retrieve.
+        * Get the second category.
+        */
+       cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL);
+       cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
+       cat_name = ast_category_get_name(cat);
+       var = ast_variable_new("aaa", "bbb", "dummy");
+       if (!var) {
+               ast_test_status_update(test, "Couldn't allocate variable.\n");
+               goto out;
+       }
+       ast_variable_append(cat, var);
+
+       /* Make sure we can retrieve with specific category name */
+       var_value = ast_variable_retrieve(cfg, cat_name, "aaa");
+       if (!var_value || strcmp(var_value, "bbb")) {
+               ast_test_status_update(test, "Variable not found or wrong value.\n");
+               goto out;
+       }
+
+       /* Make sure we can retrieve with NULL category name */
+       var_value = ast_variable_retrieve(cfg, NULL, "aaa");
+       if (!var_value || strcmp(var_value, "bbb")) {
+               ast_test_status_update(test, "Variable not found or wrong value.\n");
+               goto out;
+       }
+
+       res = AST_TEST_PASS;
+
+out:
+       ast_config_destroy(cfg);
+       return res;
+}
+
+AST_TEST_DEFINE(config_filtered_ops)
+{
+       enum ast_test_result_state res = AST_TEST_FAIL;
+       struct ast_config *cfg = NULL;
+       struct ast_category *cat = NULL;
+       char temp[32];
+       const char *value;
+       int i;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "config_filtered_ops";
+               info->category = "/main/config/";
+               info->summary = "Test filtered config ops";
+               info->description =     "Test filtered config ops";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       cfg = ast_config_new();
+       if (!cfg) {
+               return res;
+       }
+
+       /* load the config */
+       for(i = 0; i < 5; i++) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               cat = ast_category_new(temp, "dummy", -1);
+               ast_variable_insert(cat, ast_variable_new("type", "a", "dummy"), "0");
+               ast_category_append(cfg, cat);
+       }
+
+       for(i = 0; i < 5; i++) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               cat = ast_category_new(temp, "dummy", -1);
+               ast_variable_insert(cat, ast_variable_new("type", "b", "dummy"), "0");
+               ast_category_append(cfg, cat);
+       }
+
+       /* check the config has 5 elements for each type*/
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "type=a"))) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               if (strcmp(ast_category_get_name(cat), temp)) {
+                       ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
+                       goto out;
+               }
+               value = ast_variable_find(cat, "type");
+               if (!value || strcmp(value, "a")) {
+                       ast_test_status_update(test, "Type %s != %s\n", "a", value);
+                       goto out;
+               }
+               i++;
+       }
+       if (i != 5) {
+               ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
+               goto out;
+       }
+
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "type=b"))) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               if (!cat || strcmp(ast_category_get_name(cat), temp)) {
+                       ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
+                       goto out;
+               }
+               value = ast_variable_find(cat, "type");
+               if (!value || strcmp(value, "b")) {
+                       ast_test_status_update(test, "Type %s != %s\n", "b", value);
+                       goto out;
+               }
+               i++;
+       }
+       if (i != 5) {
+               ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
+               goto out;
+       }
+
+       /* Delete b3 and make sure it's gone and a3 is still there.
+        * Really this is a test of get since delete takes a specific category structure.
+        */
+       cat = ast_category_get(cfg, "test3", "type=b");
+       value = ast_variable_find(cat, "type");
+       if (strcmp(value, "b")) {
+               ast_test_status_update(test, "Type %s != %s\n", "b", value);
+               goto out;
+       }
+       ast_category_delete(cfg, cat);
+
+       cat = ast_category_get(cfg, "test3", "type=b");
+       if (cat) {
+               ast_test_status_update(test, "Category b was not deleted.\n");
+               goto out;
+       }
+
+       cat = ast_category_get(cfg, "test3", "type=a");
+       if (!cat) {
+               ast_test_status_update(test, "Category a was deleted.\n");
+               goto out;
+       }
+
+       value = ast_variable_find(cat, "type");
+       if (strcmp(value, "a")) {
+               ast_test_status_update(test, "Type %s != %s\n", value, "a");
+               goto out;
+       }
+
+       /* Basic regex stuff is handled by regcomp/regexec so not testing here.
+        * Still need to test multiple name/value pairs though.
+        */
+       ast_category_empty(cat);
+       ast_variable_insert(cat, ast_variable_new("type", "bx", "dummy"), "0");
+       ast_variable_insert(cat, ast_variable_new("e", "z", "dummy"), "0");
+
+       cat = ast_category_get(cfg, "test3", "type=.,e=z");
+       if (!cat) {
+               ast_test_status_update(test, "Category not found.\n");
+               goto out;
+       }
+
+       cat = ast_category_get(cfg, "test3", "type=.,e=zX");
+       if (cat) {
+               ast_test_status_update(test, "Category found.\n");
+               goto out;
+       }
+
+       cat = ast_category_get(cfg, "test3", "TEMPLATE=restrict,type=.,e=z");
+       if (cat) {
+               ast_test_status_update(test, "Category found.\n");
+               goto out;
+       }
+
+       res = AST_TEST_PASS;
+
+out:
+       ast_config_destroy(cfg);
+       return res;
+}
+
+AST_TEST_DEFINE(config_template_ops)
+{
+       enum ast_test_result_state res = AST_TEST_FAIL;
+       struct ast_config *cfg = NULL;
+       struct ast_category *cat = NULL;
+       char temp[32];
+       const char *value;
+       int i;
+
+       switch (cmd) {
+       case TEST_INIT:
+               info->name = "config_template_ops";
+               info->category = "/main/config/";
+               info->summary = "Test template config ops";
+               info->description =     "Test template config ops";
+               return AST_TEST_NOT_RUN;
+       case TEST_EXECUTE:
+               break;
+       }
+
+       cfg = ast_config_new();
+       if (!cfg) {
+               return res;
+       }
+
+       /* load the config with 5 templates and 5 regular */
+       for(i = 0; i < 5; i++) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               cat = ast_category_new_template(temp, "dummy", -1);
+               ast_variable_insert(cat, ast_variable_new("type", "a", "dummy"), "0");
+               ast_category_append(cfg, cat);
+       }
+
+       for(i = 0; i < 5; i++) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               cat = ast_category_new(temp, "dummy", -1);
+               ast_variable_insert(cat, ast_variable_new("type", "b", "dummy"), "0");
+               ast_category_append(cfg, cat);
+       }
+
+       /* check the config has 5 template elements of type a */
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=restrict,type=a"))) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               if (strcmp(ast_category_get_name(cat), temp)) {
+                       ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
+                       goto out;
+               }
+               value = ast_variable_find(cat, "type");
+               if (!value || strcmp(value, "a")) {
+                       ast_test_status_update(test, "Type %s != %s\n", value, "a");
+                       goto out;
+               }
+               i++;
+       }
+       if (i != 5) {
+               ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
+               goto out;
+       }
+
+       /* Test again with 'include'.  There should still only be 5 (type a) */
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=include,type=a"))) {
+               snprintf(temp, sizeof(temp), "test%d", i);
+               if (strcmp(ast_category_get_name(cat), temp)) {
+                       ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
+                       goto out;
+               }
+               value = ast_variable_find(cat, "type");
+               if (!value || strcmp(value, "a")) {
+                       ast_test_status_update(test, "Type %s != %s\n", value, "a");
+                       goto out;
+               }
+               i++;
+       }
+       if (i != 5) {
+               ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
+               goto out;
+       }
+
+       /* Test again with 'include' but no type.  There should now be 10 (type a and type b) */
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=include"))) {
+               i++;
+       }
+       if (i != 10) {
+               ast_test_status_update(test, "There were %d matches instead of 10.\n", i);
+               goto out;
+       }
+
+       /* Test again with 'restrict' and type b.  There should 0 */
+       i = 0;
+       cat = NULL;
+       while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=restrict,type=b"))) {
+               i++;
+       }
+       if (i != 0) {
+               ast_test_status_update(test, "There were %d matches instead of 0.\n", i);
+               goto out;
+       }
+
+       res = AST_TEST_PASS;
+
+out:
+       ast_config_destroy(cfg);
+       return res;
+}
+
 /*!
  * \brief Write the config file to disk
  *
@@ -937,6 +1464,9 @@ AST_TEST_DEFINE(config_options_test)
 
 static int unload_module(void)
 {
+       AST_TEST_UNREGISTER(config_basic_ops);
+       AST_TEST_UNREGISTER(config_filtered_ops);
+       AST_TEST_UNREGISTER(config_template_ops);
        AST_TEST_UNREGISTER(copy_config);
        AST_TEST_UNREGISTER(config_hook);
        AST_TEST_UNREGISTER(ast_parse_arg_test);
@@ -946,6 +1476,9 @@ static int unload_module(void)
 
 static int load_module(void)
 {
+       AST_TEST_REGISTER(config_basic_ops);
+       AST_TEST_REGISTER(config_filtered_ops);
+       AST_TEST_REGISTER(config_template_ops);
        AST_TEST_REGISTER(copy_config);
        AST_TEST_REGISTER(config_hook);
        AST_TEST_REGISTER(ast_parse_arg_test);
index 2d811b1..cbdec0c 100644 (file)
@@ -493,7 +493,7 @@ AST_TEST_DEFINE(apply_config)
                return AST_TEST_NOT_RUN;
        }
 
-       if (!ast_category_get(config, "test_sorcery_section")) {
+       if (!ast_category_get(config, "test_sorcery_section", NULL)) {
                ast_test_status_update(test, "Sorcery configuration file does not have test_sorcery section\n");
                ast_config_destroy(config);
                return AST_TEST_NOT_RUN;
@@ -2226,7 +2226,7 @@ AST_TEST_DEFINE(caching_wizard_behavior)
                return AST_TEST_NOT_RUN;
        }
 
-       if (!ast_category_get(config, "test_sorcery_cache")) {
+       if (!ast_category_get(config, "test_sorcery_cache", NULL)) {
                ast_test_status_update(test, "Sorcery configuration file does not contain 'test_sorcery_cache' section\n");
                ast_config_destroy(config);
                return AST_TEST_NOT_RUN;
index b64ad93..ab9c188 100644 (file)
@@ -139,15 +139,15 @@ static struct ast_config *realtime_sorcery_multi(const char *database, const cha
 
 static int realtime_sorcery_update(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields)
 {
-       struct ast_category *object;
+       struct ast_category *object, *found;
 
-       if (!ast_category_exist(realtime_objects, entity)) {
+       if (!(found = ast_category_get(realtime_objects, entity, NULL))) {
                return 0;
        } else if (!(object = ast_category_new(entity, "", 0))) {
                return -1;
        }
 
-       ast_category_delete(realtime_objects, entity);
+       ast_category_delete(realtime_objects, found);
        ast_variable_append(object, ast_variables_dup((struct ast_variable*)fields));
        ast_variable_append(object, ast_variable_new(keyfield, entity, ""));
        ast_category_append(realtime_objects, object);
@@ -161,7 +161,7 @@ static int realtime_sorcery_store(const char *database, const char *table, const
        const struct ast_variable *keyfield = realtime_find_variable(fields, "id");
        struct ast_category *object;
 
-       if (!keyfield || ast_category_exist(realtime_objects, keyfield->value) || !(object = ast_category_new(keyfield->value, "", 0))) {
+       if (!keyfield || ast_category_exist(realtime_objects, keyfield->value, NULL) || !(object = ast_category_new(keyfield->value, "", 0))) {
                return -1;
        }
 
@@ -173,11 +173,12 @@ static int realtime_sorcery_store(const char *database, const char *table, const
 
 static int realtime_sorcery_destroy(const char *database, const char *table, const char *keyfield, const char *entity, const struct ast_variable *fields)
 {
-       if (!ast_category_exist(realtime_objects, entity)) {
+       struct ast_category *found;
+       if (!(found = ast_category_get(realtime_objects, entity, NULL))) {
                return 0;
        }
 
-       ast_category_delete(realtime_objects, entity);
+       ast_category_delete(realtime_objects, found);
 
        return 1;
 }
@@ -570,7 +571,7 @@ AST_TEST_DEFINE(object_retrieve_regex)
                ast_test_status_update(test, "Failed to retrieve a container of objects\n");
                return AST_TEST_FAIL;
        } else if (ao2_container_count(objects) != 2) {
-               ast_test_status_update(test, "Received a container with incorrect number of objects in it\n");
+               ast_test_status_update(test, "Received a container with incorrect number of objects in it: %d instead of 2\n", ao2_container_count(objects));
                return AST_TEST_FAIL;
        }