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