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