Doxygen Updates - janitor work
[asterisk/asterisk.git] / apps / app_skel.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) <Year>, <Your Name Here>
5  *
6  * <Your Name Here> <<Your Email Here>>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  *
18  * Please follow coding guidelines
19  * https://wiki.asterisk.org/wiki/display/AST/Coding+Guidelines
20  */
21
22 /*! \file
23  *
24  * \brief Skeleton application
25  *
26  * \author\verbatim <Your Name Here> <<Your Email Here>> \endverbatim
27  *
28  * This is a skeleton for development of an Asterisk application
29  * \ingroup applications
30  */
31
32 /*** MODULEINFO
33         <defaultenabled>no</defaultenabled>
34         <support_level>core</support_level>
35  ***/
36
37 #include "asterisk.h"
38
39 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
40
41 #include <math.h> /* log10 */
42 #include "asterisk/file.h"
43 #include "asterisk/channel.h"
44 #include "asterisk/pbx.h"
45 #include "asterisk/module.h"
46 #include "asterisk/lock.h"
47 #include "asterisk/app.h"
48 #include "asterisk/config.h"
49 #include "asterisk/config_options.h"
50 #include "asterisk/say.h"
51 #include "asterisk/astobj2.h"
52 #include "asterisk/acl.h"
53 #include "asterisk/netsock2.h"
54 #include "asterisk/strings.h"
55 #include "asterisk/cli.h"
56
57 /*** DOCUMENTATION
58         <application name="SkelGuessNumber" language="en_US">
59                 <synopsis>
60                         An example number guessing game
61                 </synopsis>
62                 <syntax>
63                         <parameter name="level" required="true"/>
64                         <parameter name="options">
65                                 <optionlist>
66                                         <option name="c">
67                                                 <para>The computer should cheat</para>
68                                         </option>
69                                         <option name="n">
70                                                 <para>How many games to play before hanging up</para>
71                                         </option>
72                                 </optionlist>
73                         </parameter>
74                 </syntax>
75                 <description>
76                 <para>This simple number guessing application is a template to build other applications
77                 from. It shows you the basic structure to create your own Asterisk applications.</para>
78                 </description>
79         </application>
80  ***/
81
82 static char *app = "SkelGuessNumber";
83
84 enum option_flags {
85         OPTION_CHEAT    = (1 << 0),
86         OPTION_NUMGAMES = (1 << 1),
87 };
88
89 enum option_args {
90         OPTION_ARG_NUMGAMES,
91         /* This *must* be the last value in this enum! */
92         OPTION_ARG_ARRAY_SIZE,
93 };
94
95 AST_APP_OPTIONS(app_opts,{
96         AST_APP_OPTION('c', OPTION_CHEAT),
97         AST_APP_OPTION_ARG('n', OPTION_NUMGAMES, OPTION_ARG_NUMGAMES),
98 });
99
100 /*! \brief A structure to hold global configuration-related options */
101 struct skel_global_config {
102         AST_DECLARE_STRING_FIELDS(
103                 AST_STRING_FIELD(prompt); /*!< The comma-separated list of sounds to prompt to enter a number */
104                 AST_STRING_FIELD(wrong);  /*!< The comma-separated list of sounds to indicate a wrong guess */
105                 AST_STRING_FIELD(right);  /*!< The comma-separated list of sounds to indicate a right guess */
106                 AST_STRING_FIELD(high);   /*!< The comma-separated list of sounds to indicate a high guess */
107                 AST_STRING_FIELD(low);    /*!< The comma-separated list of sounds to indicate a low guess */
108                 AST_STRING_FIELD(lose);  /*!< The comma-separated list of sounds to indicate a lost game */
109         );
110         uint32_t num_games;    /*!< The number of games to play before hanging up */
111         unsigned char cheat:1; /*!< Whether the computer can cheat or not */
112 };
113
114 /*! \brief A structure to maintain level state across reloads */
115 struct skel_level_state {
116         uint32_t wins;      /*!< How many wins for this level */
117         uint32_t losses;    /*!< How many losses for this level */
118         double avg_guesses; /*!< The average number of guesses to win for this level */
119 };
120
121 /*! \brief Object to hold level config information.
122  * \note This object should hold a reference to an an object that holds state across reloads.
123  * The other fields are just examples of the kind of data that might be stored in an level.
124  */
125 struct skel_level {
126         AST_DECLARE_STRING_FIELDS(
127                 AST_STRING_FIELD(name);      /*!< The name of the level */
128         );
129         uint32_t max_num;                /*!< The upper value on th range of numbers to guess */
130         uint32_t max_guesses;            /*!< The maximum number of guesses before losing */
131         struct skel_level_state *state;  /*!< A pointer to level state that must exist across all reloads */
132 };
133
134 /*! \brief Information about a currently running set of games
135  * \note Because we want to be able to show true running information about the games
136  * regardless of whether or not a reload has modified what the level looks like, it
137  * is important to either copy the information we need from the level to the
138  * current_game struct, or as we do here, store a reference to the level as it is for
139  * the running game.
140  */
141 struct skel_current_game {
142         uint32_t total_games;          /*! The total number of games for this call to to the app */
143         uint32_t games_left;           /*! How many games are left to play in this set */
144         uint32_t cheat;                /*! Whether or not cheating was enabled for the game */
145         struct skel_level *level_info; /*! The level information for the running game */
146 };
147
148 /* Treat the levels as an array--there won't be many and this will maintain the order */
149 #define LEVEL_BUCKETS 1
150
151 /*! \brief A container that holds all config-related information
152  * \note This object should contain a pointer to structs for global data and containers for
153  * any levels that are configured. Objects of this type will be swapped out on reload. If an
154  * level needs to maintain state across reloads, it needs to allocate a refcounted object to
155  * hold that state and ensure that a reference is passed to that state when creating a new
156  * level for reload. */
157 struct skel_config {
158         struct skel_global_config *global;
159         struct ao2_container *levels;
160 };
161
162 /* Config Options API callbacks */
163
164 /*! \brief Allocate a skel_config to hold a snapshot of the complete results of parsing a config
165  * \internal
166  * \returns A void pointer to a newly allocated skel_config
167  */
168 static void *skel_config_alloc(void);
169
170 /*! \brief Allocate a skel_level based on a category in a configuration file
171  * \param cat The category to base the level on
172  * \returns A void pointer to a newly allocated skel_level
173  */
174 static void *skel_level_alloc(const char *cat);
175
176 /*! \brief Find a skel level in the specified container
177  * \note This function *does not* look for a skel_level in the active container. It is used
178  * internally by the Config Options code to check if an level has already been added to the
179  * container that will be swapped for the live container on a successul reload.
180  *
181  * \param tmp_container A non-active container to search for a level
182  * \param category The category associated with the level to check for
183  * \retval non-NULL The level from the container
184  * \retval NULL The level does not exist in the container
185  */
186 static void *skel_level_find(struct ao2_container *tmp_container, const char *category);
187
188 /*! \brief An aco_type structure to link the "general" category to the skel_global_config type */
189 static struct aco_type global_option = {
190         .type = ACO_GLOBAL,
191         .item_offset = offsetof(struct skel_config, global),
192         .category_match = ACO_WHITELIST,
193         .category = "^general$",
194 };
195
196 struct aco_type *global_options[] = ACO_TYPES(&global_option);
197
198 /*! \brief An aco_type structure to link the "sounds" category to the skel_global_config type */
199 static struct aco_type sound_option = {
200         .type = ACO_GLOBAL,
201         .item_offset = offsetof(struct skel_config, global),
202         .category_match = ACO_WHITELIST,
203         .category = "^sounds$",
204 };
205
206 struct aco_type *sound_options[] = ACO_TYPES(&sound_option);
207
208 /*! \brief An aco_type structure to link the everything but the "general" and "sounds" categories to the skel_level type */
209 static struct aco_type level_option = {
210         .type = ACO_ITEM,
211         .category_match = ACO_BLACKLIST,
212         .category = "^(general|sounds)$",
213         .item_alloc = skel_level_alloc,
214         .item_find = skel_level_find,
215         .item_offset = offsetof(struct skel_config, levels),
216 };
217
218 struct aco_type *level_options[] = ACO_TYPES(&level_option);
219
220 struct aco_file app_skel_conf = {
221         .filename = "app_skel.conf",
222         .types = ACO_TYPES(&global_option, &sound_option, &level_option),
223 };
224
225 /*! \brief A global object container that will contain the skel_config that gets swapped out on reloads */
226 static AO2_GLOBAL_OBJ_STATIC(globals);
227
228 /*! \brief The container of active games */
229 static struct ao2_container *games;
230
231 /*! \brief Register information about the configs being processed by this module */
232 CONFIG_INFO_STANDARD(cfg_info, globals, skel_config_alloc,
233         .files = ACO_FILES(&app_skel_conf),
234 );
235
236 static void skel_global_config_destructor(void *obj)
237 {
238         struct skel_global_config *global = obj;
239         ast_string_field_free_memory(global);
240 }
241
242 static void skel_game_destructor(void *obj)
243 {
244         struct skel_current_game *game = obj;
245         ao2_cleanup(game->level_info);
246 }
247
248 static void skel_state_destructor(void *obj)
249 {
250         return;
251 }
252
253 static struct skel_current_game *skel_game_alloc(struct skel_level *level)
254 {
255         struct skel_current_game *game;
256         if (!(game = ao2_alloc(sizeof(struct skel_current_game), skel_game_destructor))) {
257                 return NULL;
258         }
259         ao2_ref(level, +1);
260         game->level_info = level;
261         return game;
262 }
263
264 static void skel_level_destructor(void *obj)
265 {
266         struct skel_level *level = obj;
267         ast_string_field_free_memory(level);
268         ao2_cleanup(level->state);
269 }
270
271 static int skel_level_hash(const void *obj, const int flags)
272 {
273         const struct skel_level *level = obj;
274         const char *name = (flags & OBJ_KEY) ? obj : level->name;
275         return ast_str_case_hash(name);
276 }
277
278 static int skel_level_cmp(void *obj, void *arg, int flags)
279 {
280         struct skel_level *one = obj, *two = arg;
281         const char *match = (flags & OBJ_KEY) ? arg : two->name;
282         return strcasecmp(one->name, match) ? 0 : (CMP_MATCH | CMP_STOP);
283 }
284
285 /*! \brief A custom bitfield handler
286  * \internal
287  * \note It is not possible to take the address of a bitfield, therefor all
288  * bitfields in the config struct will have to use a custom handler
289  * \param opt The opaque config option
290  * \param var The ast_variable containing the option name and value
291  * \param obj The object registerd for this option type
292  * \retval 0 Success
293  * \retval non-zero Failure
294  */
295 static int custom_bitfield_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
296 {
297         struct skel_global_config *global = obj;
298
299         if (!strcasecmp(var->name, "cheat")) {
300                 global->cheat = ast_true(var->value);
301         } else {
302                 return -1;
303         }
304
305         return 0;
306 }
307
308 static void play_files_helper(struct ast_channel *chan, const char *prompts)
309 {
310         char *prompt, *rest = ast_strdupa(prompts);
311
312         ast_stopstream(chan);
313         while ((prompt = strsep(&rest, "&")) && !ast_stream_and_wait(chan, prompt, "")) {
314                 ast_stopstream(chan);
315         }
316 }
317
318 static int app_exec(struct ast_channel *chan, const char *data)
319 {
320         int win = 0;
321         uint32_t guesses;
322         RAII_VAR(struct skel_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
323         RAII_VAR(struct skel_level *, level, NULL, ao2_cleanup);
324         RAII_VAR(struct skel_current_game *, game, NULL, ao2_cleanup);
325         char *parse, *opts[OPTION_ARG_ARRAY_SIZE];
326         struct ast_flags flags;
327         AST_DECLARE_APP_ARGS(args,
328                 AST_APP_ARG(level);
329                 AST_APP_ARG(options);
330         );
331
332         if (!cfg) {
333                 ast_log(LOG_ERROR, "Couldn't access configuratino data!\n");
334                 return -1;
335         }
336
337         if (ast_strlen_zero(data)) {
338                 ast_log(LOG_WARNING, "%s requires an argument (level[,options])\n", app);
339                 return -1;
340         }
341
342         /* We need to make a copy of the input string if we are going to modify it! */
343         parse = ast_strdupa(data);
344
345         AST_STANDARD_APP_ARGS(args, parse);
346
347         if (args.argc == 2) {
348                 ast_app_parse_options(app_opts, &flags, opts, args.options);
349         }
350
351         if (ast_strlen_zero(args.level)) {
352                 ast_log(LOG_ERROR, "%s requires a level argument\n", app);
353                 return -1;
354         }
355
356         if (!(level = ao2_find(cfg->levels, args.level, OBJ_KEY))) {
357                 ast_log(LOG_ERROR, "Unknown level: %s\n", args.level);
358                 return -1;
359         }
360
361         if (!(game = skel_game_alloc(level))) {
362                 return -1;
363         }
364
365         ao2_link(games, game);
366
367         /* Use app-specified values, or the options specified in [general] if they aren't passed to the app */
368         if (!ast_test_flag(&flags, OPTION_NUMGAMES) ||
369                         ast_strlen_zero(opts[OPTION_ARG_NUMGAMES]) ||
370                         ast_parse_arg(opts[OPTION_ARG_NUMGAMES], PARSE_UINT32, &game->total_games)) {
371                 game->total_games = cfg->global->num_games;
372         }
373         game->games_left = game->total_games;
374         game->cheat = ast_test_flag(&flags, OPTION_CHEAT) || cfg->global->cheat;
375
376         for (game->games_left = game->total_games; game->games_left; game->games_left--) {
377                 uint32_t num = ast_random() % level->max_num; /* random number between 0 and level->max_num */
378
379                 ast_debug(1, "They should totally should guess %u\n", num);
380
381                 /* Play the prompt */
382                 play_files_helper(chan, cfg->global->prompt);
383                 ast_say_number(chan, level->max_num, "", ast_channel_language(chan), "");
384
385                 for (guesses = 0; guesses < level->max_guesses; guesses++) {
386                         size_t buflen = log10(level->max_num) + 1;
387                         char buf[buflen];
388                         int guess;
389                         buf[buflen] = '\0';
390
391                         /* Read the number pressed */
392                         ast_readstring(chan, buf, buflen - 1, 2000, 10000, "");
393                         if (ast_parse_arg(buf, PARSE_INT32 | PARSE_IN_RANGE, &guess, 0, level->max_num)) {
394                                 if (guesses < level->max_guesses - 1) {
395                                         play_files_helper(chan, cfg->global->wrong);
396                                 }
397                                 continue;
398                         }
399
400                         /* Inform whether the guess was right, low, or high */
401                         if (guess == num && !game->cheat) {
402                                 /* win */
403                                 win = 1;
404                                 play_files_helper(chan, cfg->global->right);
405                                 guesses++;
406                                 break;
407                         } else if (guess < num) {
408                                 play_files_helper(chan, cfg->global->low);
409                         } else {
410                                 play_files_helper(chan, cfg->global->high);
411                         }
412
413                         if (guesses < level->max_guesses - 1) {
414                                 play_files_helper(chan, cfg->global->wrong);
415                         }
416                 }
417
418                 /* Process game stats */
419                 ao2_lock(level->state);
420                 if (win) {
421                         ++level->state->wins;
422                         level->state->avg_guesses = ((level->state->wins - 1) * level->state->avg_guesses + guesses) / level->state->wins;
423                 } else {
424                         /* lose */
425                         level->state->losses++;
426                         play_files_helper(chan, cfg->global->lose);
427                 }
428                 ao2_unlock(level->state);
429         }
430
431         ao2_unlink(games, game);
432
433         return 0;
434 }
435
436 static struct skel_level *skel_state_alloc(const char *name)
437 {
438         struct skel_level *level;
439
440         if (!(level = ao2_alloc(sizeof(*level), skel_state_destructor))) {
441                 return NULL;
442         }
443
444         return level;
445 }
446
447 static void *skel_level_find(struct ao2_container *tmp_container, const char *category)
448 {
449         return ao2_find(tmp_container, category, OBJ_KEY);
450 }
451
452 /*! \brief Look up an existing state object, or create a new one
453  * \internal
454  * \note Since the reload code will create a new level from scratch, it
455  * is important for any state that must persist between reloads to be
456  * in a separate refcounted object. This function allows the level alloc
457  * function to get a ref to an existing state object if it exists,
458  * otherwise it will return a reference to a newly allocated state object.
459  */
460 static void *skel_find_or_create_state(const char *category)
461 {
462         RAII_VAR(struct skel_config *, cfg, ao2_global_obj_ref(globals), ao2_cleanup);
463         RAII_VAR(struct skel_level *, level, NULL, ao2_cleanup);
464         if (!cfg || !cfg->levels || !(level = ao2_find(cfg->levels, category, OBJ_KEY))) {
465                 return skel_state_alloc(category);
466         }
467         ao2_ref(level->state, +1);
468         return level->state;
469 }
470
471 static void *skel_level_alloc(const char *cat)
472 {
473         struct skel_level *level;
474
475         if (!(level = ao2_alloc(sizeof(*level), skel_level_destructor))) {
476                 return NULL;
477         }
478
479         if (ast_string_field_init(level, 128)) {
480                 ao2_ref(level, -1);
481                 return NULL;
482         }
483
484         /* Since the level has state information that needs to persist between reloads,
485          * it is important to handle that here in the level's allocation function.
486          * If not separated out into its own object, the data would be destroyed on
487          * reload. */
488         if (!(level->state = skel_find_or_create_state(cat))) {
489                 ao2_ref(level, -1);
490                 return NULL;
491         }
492
493         ast_string_field_set(level, name, cat);
494
495         return level;
496 }
497
498 static void skel_config_destructor(void *obj)
499 {
500         struct skel_config *cfg = obj;
501         ao2_cleanup(cfg->global);
502         ao2_cleanup(cfg->levels);
503 }
504
505 static void *skel_config_alloc(void)
506 {
507         struct skel_config *cfg;
508
509         if (!(cfg = ao2_alloc(sizeof(*cfg), skel_config_destructor))) {
510                 return NULL;
511         }
512
513         /* Allocate/initialize memory */
514         if (!(cfg->global = ao2_alloc(sizeof(*cfg->global), skel_global_config_destructor))) {
515                 goto error;
516         }
517
518         if (ast_string_field_init(cfg->global, 128)) {
519                 goto error;
520         }
521
522         if (!(cfg->levels = ao2_container_alloc(LEVEL_BUCKETS, skel_level_hash, skel_level_cmp))) {
523                 goto error;
524         }
525
526         return cfg;
527 error:
528         ao2_ref(cfg, -1);
529         return NULL;
530 }
531
532 static char *handle_skel_show_config(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
533 {
534         RAII_VAR(struct skel_config *, cfg, NULL, ao2_cleanup);
535
536         switch(cmd) {
537         case CLI_INIT:
538                 e->command = "skel show config";
539                 e->usage =
540                         "Usage: skel show config\n"
541                         "       List app_skel global config\n";
542                 return NULL;
543         case CLI_GENERATE:
544                 return NULL;
545         }
546
547         if (!(cfg = ao2_global_obj_ref(globals)) || !cfg->global) {
548                 return NULL;
549         }
550
551         ast_cli(a->fd, "games per call:  %u\n", cfg->global->num_games);
552         ast_cli(a->fd, "computer cheats: %s\n", AST_CLI_YESNO(cfg->global->cheat));
553         ast_cli(a->fd, "\n");
554         ast_cli(a->fd, "Sounds\n");
555         ast_cli(a->fd, "  prompt:      %s\n", cfg->global->prompt);
556         ast_cli(a->fd, "  wrong guess: %s\n", cfg->global->wrong);
557         ast_cli(a->fd, "  right guess: %s\n", cfg->global->right);
558
559         return CLI_SUCCESS;
560 }
561
562 static char *handle_skel_show_games(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
563 {
564         struct ao2_iterator iter;
565         struct skel_current_game *game;
566
567         switch(cmd) {
568         case CLI_INIT:
569                 e->command = "skel show games";
570                 e->usage =
571                         "Usage: skel show games\n"
572                         "       List app_skel active games\n";
573                 return NULL;
574         case CLI_GENERATE:
575                 return NULL;
576         }
577
578 #define SKEL_FORMAT "%-15.15s %-15.15s %-15.15s\n"
579 #define SKEL_FORMAT1 "%-15.15s %-15u %-15u\n"
580         ast_cli(a->fd, SKEL_FORMAT, "Level", "Total Games", "Games Left");
581         iter = ao2_iterator_init(games, 0);
582         while ((game = ao2_iterator_next(&iter))) {
583                 ast_cli(a->fd, SKEL_FORMAT1, game->level_info->name, game->total_games, game->games_left);
584                 ao2_ref(game, -1);
585         }
586         ao2_iterator_destroy(&iter);
587 #undef SKEL_FORMAT
588 #undef SKEL_FORMAT1
589         return CLI_SUCCESS;
590 }
591
592 static char *handle_skel_show_levels(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
593 {
594         RAII_VAR(struct skel_config *, cfg, NULL, ao2_cleanup);
595         struct ao2_iterator iter;
596         struct skel_level *level;
597
598         switch(cmd) {
599         case CLI_INIT:
600                 e->command = "skel show levels";
601                 e->usage =
602                         "Usage: skel show levels\n"
603                         "       List the app_skel levels\n";
604                 return NULL;
605         case CLI_GENERATE:
606                 return NULL;
607         }
608
609         if (!(cfg = ao2_global_obj_ref(globals)) || !cfg->levels) {
610                 return NULL;
611         }
612
613 #define SKEL_FORMAT "%-15.15s %-11.11s %-12.12s %-8.8s %-8.8s %-12.12s\n"
614 #define SKEL_FORMAT1 "%-15.15s %-11u %-12u %-8u %-8u %-8f\n"
615         ast_cli(a->fd, SKEL_FORMAT, "Name", "Max number", "Max Guesses", "Wins", "Losses", "Avg Guesses");
616         iter = ao2_iterator_init(cfg->levels, 0);
617         while ((level = ao2_iterator_next(&iter))) {
618                 ast_cli(a->fd, SKEL_FORMAT1, level->name, level->max_num, level->max_guesses, level->state->wins, level->state->losses, level->state->avg_guesses);
619                 ao2_ref(level, -1);
620         }
621         ao2_iterator_destroy(&iter);
622 #undef SKEL_FORMAT
623 #undef SKEL_FORMAT1
624
625         return CLI_SUCCESS;
626 }
627
628 static struct ast_cli_entry skel_cli[] = {
629         AST_CLI_DEFINE(handle_skel_show_config, "Show app_skel global config options"),
630         AST_CLI_DEFINE(handle_skel_show_levels, "Show app_skel levels"),
631         AST_CLI_DEFINE(handle_skel_show_games, "Show app_skel active games"),
632 };
633
634 static int reload_module(void)
635 {
636         if (aco_process_config(&cfg_info, 1) == ACO_PROCESS_ERROR) {
637                 return AST_MODULE_LOAD_DECLINE;
638         }
639
640         return 0;
641 }
642
643 static int unload_module(void)
644 {
645         ast_cli_unregister_multiple(skel_cli, ARRAY_LEN(skel_cli));
646         aco_info_destroy(&cfg_info);
647         ao2_global_obj_release(globals);
648         return ast_unregister_application(app);
649 }
650
651 /*!
652  * \brief Load the module
653  *
654  * \par The configuration file
655  * 
656  * The application app_skel uses a configuration file.
657  * \verbinclude app_skel.conf.sample
658  */
659 static int load_module(void)
660 {
661         if (aco_info_init(&cfg_info)) {
662                 goto error;
663         }
664         if (!(games = ao2_container_alloc(1, NULL, NULL))) {
665                 goto error;
666         }
667
668         /* Global options */
669         aco_option_register(&cfg_info, "games", ACO_EXACT, global_options, "3", OPT_UINT_T, 0, FLDSET(struct skel_global_config, num_games));
670         aco_option_register_custom(&cfg_info, "cheat", ACO_EXACT, global_options, "no", custom_bitfield_handler, 0);
671
672         /* Sound options */
673         aco_option_register(&cfg_info, "prompt", ACO_EXACT, sound_options, "please-enter-your&number&queue-less-than", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, prompt));
674         aco_option_register(&cfg_info, "wrong_guess", ACO_EXACT, sound_options, "vm-pls-try-again", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, wrong));
675         aco_option_register(&cfg_info, "right_guess", ACO_EXACT, sound_options, "auth-thankyou", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, right));
676         aco_option_register(&cfg_info, "too_high", ACO_EXACT, sound_options, "high", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, high));
677         aco_option_register(&cfg_info, "too_low", ACO_EXACT, sound_options, "low", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, low));
678         aco_option_register(&cfg_info, "lose", ACO_EXACT, sound_options, "vm-goodbye", OPT_STRINGFIELD_T, 0, STRFLDSET(struct skel_global_config, lose));
679
680         /* Level options */
681         aco_option_register(&cfg_info, "max_number", ACO_EXACT, level_options, NULL, OPT_UINT_T, 0, FLDSET(struct skel_level, max_num));
682         aco_option_register(&cfg_info, "max_guesses", ACO_EXACT, level_options, NULL, OPT_UINT_T, 1, FLDSET(struct skel_level, max_guesses));
683
684         if (aco_process_config(&cfg_info, 0) == ACO_PROCESS_ERROR) {
685                 goto error;
686         }
687
688         ast_cli_register_multiple(skel_cli, ARRAY_LEN(skel_cli));
689         if (ast_register_application_xml(app, app_exec)) {
690                 goto error;
691         }
692         return AST_MODULE_LOAD_SUCCESS;
693
694 error:
695         aco_info_destroy(&cfg_info);
696         ao2_cleanup(games);
697         return AST_MODULE_LOAD_DECLINE;
698 }
699
700 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Skeleton (sample) Application",
701         .load = load_module,
702         .unload = unload_module,
703         .reload = reload_module,
704         .load_pri = AST_MODPRI_DEFAULT,
705 );