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