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