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