Create a new config file status, CONFIG_STATUS_FILEINVALID for differentiating
[asterisk/asterisk.git] / apps / app_playback.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
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
19 /*! \file
20  *
21  * \brief Trivial application to playback a sound file
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27  
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include "asterisk/file.h"
33 #include "asterisk/pbx.h"
34 #include "asterisk/module.h"
35 #include "asterisk/app.h"
36 /* This file provides config-file based 'say' functions, and implenents
37  * some CLI commands.
38  */
39 #include "asterisk/say.h"       /* provides config-file based 'say' functions */
40 #include "asterisk/cli.h"
41
42 static char *app = "Playback";
43
44 static char *synopsis = "Play a file";
45
46 static char *descrip = 
47 "  Playback(filename[&filename2...][,option]):  Plays back given filenames (do not put\n"
48 "extension). Options may also be included following a comma.\n"
49 "The 'skip' option causes the playback of the message to be skipped if the channel\n"
50 "is not in the 'up' state (i.e. it hasn't been  answered  yet). If 'skip' is \n"
51 "specified, the application will return immediately should the channel not be\n"
52 "off hook.  Otherwise, unless 'noanswer' is specified, the channel will\n"
53 "be answered before the sound is played. Not all channels support playing\n"
54 "messages while still on hook.\n"
55 "This application sets the following channel variable upon completion:\n"
56 " PLAYBACKSTATUS    The status of the playback attempt as a text string, one of\n"
57 "               SUCCESS | FAILED\n"
58 "See Also: Background (application) -- for playing soundfiles that are interruptible\n"
59 "          WaitExten (application) -- wait for digits from caller, optionally play music on hold\n"
60 ;
61
62
63 static struct ast_config *say_cfg = NULL;
64 /* save the say' api calls.
65  * The first entry is NULL if we have the standard source,
66  * otherwise we are sourcing from here.
67  * 'say load [new|old]' will enable the new or old method, or report status
68  */
69 static const void *say_api_buf[40];
70 static const char *say_old = "old";
71 static const char *say_new = "new";
72
73 static void save_say_mode(const void *arg)
74 {
75         int i = 0;
76         say_api_buf[i++] = arg;
77
78         say_api_buf[i++] = ast_say_number_full;
79         say_api_buf[i++] = ast_say_enumeration_full;
80         say_api_buf[i++] = ast_say_digit_str_full;
81         say_api_buf[i++] = ast_say_character_str_full;
82         say_api_buf[i++] = ast_say_phonetic_str_full;
83         say_api_buf[i++] = ast_say_datetime;
84         say_api_buf[i++] = ast_say_time;
85         say_api_buf[i++] = ast_say_date;
86         say_api_buf[i++] = ast_say_datetime_from_now;
87         say_api_buf[i++] = ast_say_date_with_format;
88 }
89
90 static void restore_say_mode(void *arg)
91 {
92         int i = 0;
93         say_api_buf[i++] = arg;
94
95         ast_say_number_full = say_api_buf[i++];
96         ast_say_enumeration_full = say_api_buf[i++];
97         ast_say_digit_str_full = say_api_buf[i++];
98         ast_say_character_str_full = say_api_buf[i++];
99         ast_say_phonetic_str_full = say_api_buf[i++];
100         ast_say_datetime = say_api_buf[i++];
101         ast_say_time = say_api_buf[i++];
102         ast_say_date = say_api_buf[i++];
103         ast_say_datetime_from_now = say_api_buf[i++];
104         ast_say_date_with_format = say_api_buf[i++];
105 }
106
107 /* 
108  * Typical 'say' arguments in addition to the date or number or string
109  * to say. We do not include 'options' because they may be different
110  * in recursive calls, and so they are better left as an external
111  * parameter.
112  */
113 typedef struct {
114         struct ast_channel *chan;
115         const char *ints;
116         const char *language;
117         int audiofd;
118         int ctrlfd;
119 } say_args_t;
120
121 static int s_streamwait3(const say_args_t *a, const char *fn)
122 {
123         int res = ast_streamfile(a->chan, fn, a->language);
124         if (res) {
125                 ast_log(LOG_WARNING, "Unable to play message %s\n", fn);
126                 return res;
127         }
128         res = (a->audiofd  > -1 && a->ctrlfd > -1) ?
129         ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) :
130         ast_waitstream(a->chan, a->ints);
131         ast_stopstream(a->chan);
132         return res;  
133 }
134
135 /*
136  * the string is 'prefix:data' or prefix:fmt:data'
137  * with ':' being invalid in strings.
138  */
139 static int do_say(say_args_t *a, const char *s, const char *options, int depth)
140 {
141         struct ast_variable *v;
142         char *lang, *x, *rule = NULL;
143         int ret = 0;   
144         struct varshead head = { .first = NULL, .last = NULL };
145         struct ast_var_t *n;
146
147         ast_debug(2, "string <%s> depth <%d>\n", s, depth);
148         if (depth++ > 10) {
149                 ast_log(LOG_WARNING, "recursion too deep, exiting\n");
150                 return -1;
151         } else if (!say_cfg) {
152                 ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", s);
153                 return -1;
154         }
155
156         /* scan languages same as in file.c */
157         if (a->language == NULL)
158                 a->language = "en";     /* default */
159         ast_debug(2, "try <%s> in <%s>\n", s, a->language);
160         lang = ast_strdupa(a->language);
161         for (;;) {
162                 for (v = ast_variable_browse(say_cfg, lang); v ; v = v->next) {
163                         if (ast_extension_match(v->name, s)) {
164                                 rule = ast_strdupa(v->value);
165                                 break;
166                         }
167                 }
168                 if (rule)
169                         break;
170                 if ( (x = strchr(lang, '_')) )
171                         *x = '\0';      /* try without suffix */
172                 else if (strcmp(lang, "en"))
173                         lang = "en";    /* last resort, try 'en' if not done yet */
174                 else
175                         break;
176         }
177         if (!rule)
178                 return 0;
179
180         /* skip up to two prefixes to get the value */
181         if ( (x = strchr(s, ':')) )
182                 s = x + 1;
183         if ( (x = strchr(s, ':')) )
184                 s = x + 1;
185         ast_debug(2, "value is <%s>\n", s);
186         n = ast_var_assign("SAY", s);
187         AST_LIST_INSERT_HEAD(&head, n, entries);
188
189         /* scan the body, one piece at a time */
190         while ( !ret && (x = strsep(&rule, ",")) ) { /* exit on key */
191                 char fn[128];
192                 const char *p, *fmt, *data; /* format and data pointers */
193
194                 /* prepare a decent file name */
195                 x = ast_skip_blanks(x);
196                 ast_trim_blanks(x);
197
198                 /* replace variables */
199                 pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn));
200                 ast_debug(2, "doing [%s]\n", fn);
201
202                 /* locate prefix and data, if any */
203                 fmt = index(fn, ':');
204                 if (!fmt || fmt == fn)  {       /* regular filename */
205                         ret = s_streamwait3(a, fn);
206                         continue;
207                 }
208                 fmt++;
209                 data = index(fmt, ':'); /* colon before data */
210                 if (!data || data == fmt) {     /* simple prefix-fmt */
211                         ret = do_say(a, fn, options, depth);
212                         continue;
213                 }
214                 /* prefix:fmt:data */
215                 for (p = fmt; p < data && ret <= 0; p++) {
216                         char fn2[sizeof(fn)];
217                         if (*p == ' ' || *p == '\t')    /* skip blanks */
218                                 continue;
219                         if (*p == '\'') {/* file name - we trim them */
220                                 char *y;
221                                 strcpy(fn2, ast_skip_blanks(p+1));      /* make a full copy */
222                                 y = index(fn2, '\'');
223                                 if (!y) {
224                                         p = data;       /* invalid. prepare to end */
225                                         break;
226                                 }
227                                 *y = '\0';
228                                 ast_trim_blanks(fn2);
229                                 p = index(p+1, '\'');
230                                 ret = s_streamwait3(a, fn2);
231                         } else {
232                                 int l = fmt-fn;
233                                 strcpy(fn2, fn); /* copy everything */
234                                 /* after prefix, append the format */
235                                 fn2[l++] = *p;
236                                 strcpy(fn2 + l, data);
237                                 ret = do_say(a, fn2, options, depth);
238                         }
239                         
240                         if (ret) {
241                                 break;
242                         }
243                 }
244         }
245         ast_var_delete(n);
246         return ret;
247 }
248
249 static int say_full(struct ast_channel *chan, const char *string,
250         const char *ints, const char *lang, const char *options,
251         int audiofd, int ctrlfd)
252 {
253         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
254         return do_say(&a, string, options, 0);
255 }
256
257 static int say_number_full(struct ast_channel *chan, int num,
258         const char *ints, const char *lang, const char *options,
259         int audiofd, int ctrlfd)
260 {
261         char buf[64];
262         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
263         snprintf(buf, sizeof(buf), "num:%d", num);
264         return do_say(&a, buf, options, 0);
265 }
266
267 static int say_enumeration_full(struct ast_channel *chan, int num,
268         const char *ints, const char *lang, const char *options,
269         int audiofd, int ctrlfd)
270 {
271         char buf[64];
272         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
273         snprintf(buf, sizeof(buf), "enum:%d", num);
274         return do_say(&a, buf, options, 0);
275 }
276
277 static int say_date_generic(struct ast_channel *chan, time_t t,
278         const char *ints, const char *lang, const char *format, const char *timezonename, const char *prefix)
279 {
280         char buf[128];
281         struct ast_tm tm;
282         struct timeval when = { t, 0 };
283         say_args_t a = { chan, ints, lang, -1, -1 };
284         if (format == NULL)
285                 format = "";
286
287         ast_localtime(&when, &tm, NULL);
288         snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
289                 prefix,
290                 format,
291                 tm.tm_year+1900,
292                 tm.tm_mon+1,
293                 tm.tm_mday,
294                 tm.tm_hour,
295                 tm.tm_min,
296                 tm.tm_sec,
297                 tm.tm_wday,
298                 tm.tm_yday);
299         return do_say(&a, buf, NULL, 0);
300 }
301
302 static int say_date_with_format(struct ast_channel *chan, time_t t,
303         const char *ints, const char *lang, const char *format, const char *timezonename)
304 {
305         return say_date_generic(chan, t, ints, lang, format, timezonename, "datetime");
306 }
307
308 static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
309 {
310         return say_date_generic(chan, t, ints, lang, "", NULL, "date");
311 }
312
313 static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
314 {
315         return say_date_generic(chan, t, ints, lang, "", NULL, "time");
316 }
317
318 static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
319 {
320         return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
321 }
322
323 /*
324  * remap the 'say' functions to use those in this file
325  */
326 static int say_init_mode(const char *mode) {
327         if (!strcmp(mode, say_new)) {
328                 if (say_cfg == NULL) {
329                         ast_log(LOG_ERROR, "There is no say.conf file to use new mode\n");
330                         return -1;
331                 }
332                 save_say_mode(say_new);
333                 ast_say_number_full = say_number_full;
334
335                 ast_say_enumeration_full = say_enumeration_full;
336 #if 0
337                 ast_say_digits_full = say_digits_full;
338                 ast_say_digit_str_full = say_digit_str_full;
339                 ast_say_character_str_full = say_character_str_full;
340                 ast_say_phonetic_str_full = say_phonetic_str_full;
341                 ast_say_datetime_from_now = say_datetime_from_now;
342 #endif
343                 ast_say_datetime = say_datetime;
344                 ast_say_time = say_time;
345                 ast_say_date = say_date;
346                 ast_say_date_with_format = say_date_with_format;
347         } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
348                 restore_say_mode(NULL);
349         } else if (strcmp(mode, say_old)) {
350                 ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
351                 return -1;
352         }
353         
354         return 0;
355 }
356
357 static char *__say_cli_init(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
358 {
359         const char *old_mode = say_api_buf[0] ? say_new : say_old;
360         char *mode;
361         switch (cmd) {
362         case CLI_INIT:
363                 e->command = "say load [new|old]";
364                 e->usage = 
365                         "Usage: say load [new|old]\n"
366                         "       say load\n"
367                         "           Report status of current say mode\n"
368                         "       say load new\n"
369                         "           Set say method, configured in say.conf\n"
370                         "       say load old\n"
371                         "           Set old say method, coded in asterisk core\n";
372                 return NULL;
373         case CLI_GENERATE:
374                 return NULL;
375         }
376         if (a->argc == 2) {
377                 ast_cli(a->fd, "say mode is [%s]\n", old_mode);
378                 return CLI_SUCCESS;
379         } else if (a->argc != e->args)
380                 return CLI_SHOWUSAGE;
381         mode = a->argv[2];
382         if (!strcmp(mode, old_mode))
383                 ast_cli(a->fd, "say mode is %s already\n", mode);
384         else
385                 if (say_init_mode(mode) == 0)
386                         ast_cli(a->fd, "setting say mode from %s to %s\n", old_mode, mode);
387
388         return CLI_SUCCESS;
389 }
390
391 static struct ast_cli_entry cli_playback[] = {
392         AST_CLI_DEFINE(__say_cli_init, "Set or show the say mode"),
393 };
394
395 static int playback_exec(struct ast_channel *chan, void *data)
396 {
397         int res = 0;
398         int mres = 0;
399         char *tmp;
400         int option_skip=0;
401         int option_say=0;
402         int option_noanswer = 0;
403
404         AST_DECLARE_APP_ARGS(args,
405                 AST_APP_ARG(filenames);
406                 AST_APP_ARG(options);
407         );
408         
409         if (ast_strlen_zero(data)) {
410                 ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
411                 return -1;
412         }
413
414         tmp = ast_strdupa(data);
415         AST_STANDARD_APP_ARGS(args, tmp);
416
417         if (args.options) {
418                 if (strcasestr(args.options, "skip"))
419                         option_skip = 1;
420                 if (strcasestr(args.options, "say"))
421                         option_say = 1;
422                 if (strcasestr(args.options, "noanswer"))
423                         option_noanswer = 1;
424         } 
425         if (chan->_state != AST_STATE_UP) {
426                 if (option_skip) {
427                         /* At the user's option, skip if the line is not up */
428                         goto done;
429                 } else if (!option_noanswer)
430                         /* Otherwise answer unless we're supposed to send this while on-hook */
431                         res = ast_answer(chan);
432         }
433         if (!res) {
434                 char *back = args.filenames;
435                 char *front;
436
437                 ast_stopstream(chan);
438                 while (!res && (front = strsep(&back, "&"))) {
439                         if (option_say)
440                                 res = say_full(chan, front, "", chan->language, NULL, -1, -1);
441                         else
442                                 res = ast_streamfile(chan, front, chan->language);
443                         if (!res) { 
444                                 res = ast_waitstream(chan, ""); 
445                                 ast_stopstream(chan);
446                         } else {
447                                 ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
448                                 res = 0;
449                                 mres = 1;
450                         }
451                 }
452         }
453 done:
454         pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
455         return res;
456 }
457
458 static int reload(void)
459 {
460         struct ast_variable *v;
461         struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
462         struct ast_config *newcfg;
463
464         if ((newcfg = ast_config_load("say.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED) {
465                 return 0;
466         } else if (newcfg == CONFIG_STATUS_FILEINVALID) {
467                 ast_log(LOG_ERROR, "Config file say.conf is in an invalid format.  Aborting.\n");
468                 return 0;
469         }
470
471         if (say_cfg) {
472                 ast_config_destroy(say_cfg);
473                 ast_log(LOG_NOTICE, "Reloading say.conf\n");
474                 say_cfg = newcfg;
475         }
476
477         if (say_cfg) {
478                 for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) {
479                         if (ast_extension_match(v->name, "mode")) {
480                                 say_init_mode(v->value);
481                                 break;
482                         }
483                 }
484         }
485         
486         /*
487          * XXX here we should sort rules according to the same order
488          * we have in pbx.c so we have the same matching behaviour.
489          */
490         return 0;
491 }
492
493 static int unload_module(void)
494 {
495         int res;
496
497         res = ast_unregister_application(app);
498
499         ast_cli_unregister_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
500
501         if (say_cfg)
502                 ast_config_destroy(say_cfg);
503
504         return res;     
505 }
506
507 static int load_module(void)
508 {
509         struct ast_variable *v;
510         struct ast_flags config_flags = { 0 };
511
512         say_cfg = ast_config_load("say.conf", config_flags);
513         if (say_cfg && say_cfg != CONFIG_STATUS_FILEINVALID) {
514                 for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) {
515                         if (ast_extension_match(v->name, "mode")) {
516                                 say_init_mode(v->value);
517                                 break;
518                         }
519                 }
520         }
521
522         ast_cli_register_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
523         return ast_register_application(app, playback_exec, synopsis, descrip);
524 }
525
526 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application",
527                 .load = load_module,
528                 .unload = unload_module,
529                 .reload = reload,
530                );