make the 'name' and 'value' fields in ast_variable const char *
[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 comma.\n"
57 "The 'skip' 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                 pbx_substitute_variables_varshead(&head, x, fn, sizeof(fn));
206                 ast_log(LOG_WARNING, "doing [%s]\n", fn);
207
208                 /* locate prefix and data, if any */
209                 fmt = index(fn, ':');
210                 if (!fmt || fmt == fn)  {       /* regular filename */
211                         ret = s_streamwait3(a, fn);
212                         continue;
213                 }
214                 fmt++;
215                 data = index(fmt, ':'); /* colon before data */
216                 if (!data || data == fmt) {     /* simple prefix-fmt */
217                         ret = do_say(a, fn, options, depth);
218                         continue;
219                 }
220                 /* prefix:fmt:data */
221                 for (p = fmt; p < data && ret <= 0; p++) {
222                         char fn2[sizeof(fn)];
223                         if (*p == ' ' || *p == '\t')    /* skip blanks */
224                                 continue;
225                         if (*p == '\'') {/* file name - we trim them */
226                                 char *y;
227                                 strcpy(fn2, ast_skip_blanks(p+1));      /* make a full copy */
228                                 y = index(fn2, '\'');
229                                 if (!y) {
230                                         p = data;       /* invalid. prepare to end */
231                                         break;
232                                 }
233                                 *y = '\0';
234                                 ast_trim_blanks(fn2);
235                                 p = index(p+1, '\'');
236                                 ret = s_streamwait3(a, fn2);
237                         } else {
238                                 int l = fmt-fn;
239                                 strcpy(fn2, fn); /* copy everything */
240                                 /* after prefix, append the format */
241                                 fn2[l++] = *p;
242                                 strcpy(fn2 + l, data);
243                                 ret = do_say(a, fn2, options, depth);
244                         }
245                 }
246         }
247         ast_var_delete(n);
248         return ret;
249 }
250
251 static int say_full(struct ast_channel *chan, const char *string,
252         const char *ints, const char *lang, const char *options,
253         int audiofd, int ctrlfd)
254 {
255         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
256         return do_say(&a, string, options, 0);
257 }
258
259 static int say_number_full(struct ast_channel *chan, int num,
260         const char *ints, const char *lang, const char *options,
261         int audiofd, int ctrlfd)
262 {
263         char buf[64];
264         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
265         snprintf(buf, sizeof(buf), "num:%d", num);
266         return do_say(&a, buf, options, 0);
267 }
268
269 static int say_enumeration_full(struct ast_channel *chan, int num,
270         const char *ints, const char *lang, const char *options,
271         int audiofd, int ctrlfd)
272 {
273         char buf[64];
274         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
275         snprintf(buf, sizeof(buf), "enum:%d", num);
276         return do_say(&a, buf, options, 0);
277 }
278
279 static int say_date_generic(struct ast_channel *chan, time_t t,
280         const char *ints, const char *lang, const char *format, const char *timezone, const char *prefix)
281 {
282         char buf[128];
283         struct ast_tm tm;
284         struct timeval tv = { t, 0 };
285         say_args_t a = { chan, ints, lang, -1, -1 };
286         if (format == NULL)
287                 format = "";
288
289         ast_localtime(&tv, &tm, NULL);
290         snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
291                 prefix,
292                 format,
293                 tm.tm_year+1900,
294                 tm.tm_mon+1,
295                 tm.tm_mday,
296                 tm.tm_hour,
297                 tm.tm_min,
298                 tm.tm_sec,
299                 tm.tm_wday,
300                 tm.tm_yday);
301         return do_say(&a, buf, NULL, 0);
302 }
303
304 static int say_date_with_format(struct ast_channel *chan, time_t t,
305         const char *ints, const char *lang, const char *format, const char *timezone)
306 {
307         return say_date_generic(chan, t, ints, lang, format, timezone, "datetime");
308 }
309
310 static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
311 {
312         return say_date_generic(chan, t, ints, lang, "", NULL, "date");
313 }
314
315 static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
316 {
317         return say_date_generic(chan, t, ints, lang, "", NULL, "time");
318 }
319
320 static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
321 {
322         return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
323 }
324
325 /*
326  * remap the 'say' functions to use those in this file
327  */
328 static int say_init_mode(const char *mode) {
329         if (!strcmp(mode, say_new)) {
330                 if (say_cfg == NULL) {
331                         ast_log(LOG_ERROR, "There is no say.conf file to use new mode\n");
332                         return -1;
333                 }
334                 save_say_mode(say_new);
335                 ast_say_number_full = say_number_full;
336
337                 ast_say_enumeration_full = say_enumeration_full;
338 #if 0
339                 ast_say_digits_full = say_digits_full;
340                 ast_say_digit_str_full = say_digit_str_full;
341                 ast_say_character_str_full = say_character_str_full;
342                 ast_say_phonetic_str_full = say_phonetic_str_full;
343                 ast_say_datetime_from_now = say_datetime_from_now;
344 #endif
345                 ast_say_datetime = say_datetime;
346                 ast_say_time = say_time;
347                 ast_say_date = say_date;
348                 ast_say_date_with_format = say_date_with_format;
349         } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
350                 restore_say_mode(NULL);
351         } else if (strcmp(mode, say_old)) {
352                 ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
353                 return -1;
354         }
355         
356         return 0;
357 }
358
359 static char *__say_cli_init(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
360 {
361         const char *old_mode = say_api_buf[0] ? say_new : say_old;
362         char *mode;
363         switch (cmd) {
364         case CLI_INIT:
365                 e->command = "say load [new|old]";
366                 e->usage = 
367                         "Usage: say load [new|old]\n"
368                         "       say load\n"
369                         "           Report status of current say mode\n"
370                         "       say load new\n"
371                         "           Set say method, configured in say.conf\n"
372                         "       say load old\n"
373                         "           Set old say method, coded in asterisk core\n";
374                 return NULL;
375         case CLI_GENERATE:
376                 return NULL;
377         }
378         if (a->argc == 2) {
379                 ast_cli(a->fd, "say mode is [%s]\n", old_mode);
380                 return CLI_SUCCESS;
381         } else if (a->argc != 3)
382                 return CLI_SHOWUSAGE;
383         mode = a->argv[2];
384         if (!strcmp(mode, old_mode))
385                 ast_cli(a->fd, "say mode is %s already\n", mode);
386         else
387                 if (say_init_mode(mode) == 0)
388                         ast_cli(a->fd, "setting say mode from %s to %s\n", old_mode, mode);
389
390         return CLI_SUCCESS;
391 }
392
393 static struct ast_cli_entry cli_playback[] = {
394         AST_CLI_DEFINE(__say_cli_init, "Set or show the say mode"),
395 };
396
397 static int playback_exec(struct ast_channel *chan, void *data)
398 {
399         int res = 0;
400         int mres = 0;
401         char *tmp;
402         int option_skip=0;
403         int option_say=0;
404         int option_noanswer = 0;
405
406         AST_DECLARE_APP_ARGS(args,
407                 AST_APP_ARG(filenames);
408                 AST_APP_ARG(options);
409         );
410         
411         if (ast_strlen_zero(data)) {
412                 ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
413                 return -1;
414         }
415
416         tmp = ast_strdupa(data);
417         AST_STANDARD_APP_ARGS(args, tmp);
418
419         if (args.options) {
420                 if (strcasestr(args.options, "skip"))
421                         option_skip = 1;
422                 if (strcasestr(args.options, "say"))
423                         option_say = 1;
424                 if (strcasestr(args.options, "noanswer"))
425                         option_noanswer = 1;
426         } 
427         if (chan->_state != AST_STATE_UP) {
428                 if (option_skip) {
429                         /* At the user's option, skip if the line is not up */
430                         goto done;
431                 } else if (!option_noanswer)
432                         /* Otherwise answer unless we're supposed to send this while on-hook */
433                         res = ast_answer(chan);
434         }
435         if (!res) {
436                 char *back = args.filenames;
437                 char *front;
438
439                 ast_stopstream(chan);
440                 while (!res && (front = strsep(&back, "&"))) {
441                         if (option_say)
442                                 res = say_full(chan, front, "", chan->language, NULL, -1, -1);
443                         else
444                                 res = ast_streamfile(chan, front, chan->language);
445                         if (!res) { 
446                                 res = ast_waitstream(chan, ""); 
447                                 ast_stopstream(chan);
448                         } else {
449                                 ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
450                                 res = 0;
451                                 mres = 1;
452                         }
453                 }
454         }
455 done:
456         pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
457         return res;
458 }
459
460 static int reload(void)
461 {
462         struct ast_variable *v;
463         struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
464         struct ast_config *newcfg;
465
466         if ((newcfg = ast_config_load("say.conf", config_flags)) == CONFIG_STATUS_FILEUNCHANGED)
467                 return 0;
468
469         if (say_cfg) {
470                 ast_config_destroy(say_cfg);
471                 ast_log(LOG_NOTICE, "Reloading say.conf\n");
472                 say_cfg = newcfg;
473         }
474
475         if (say_cfg) {
476                 for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) {
477                         if (ast_extension_match(v->name, "mode")) {
478                                 say_init_mode(v->value);
479                                 break;
480                         }
481                 }
482         }
483         
484         /*
485          * XXX here we should sort rules according to the same order
486          * we have in pbx.c so we have the same matching behaviour.
487          */
488         return 0;
489 }
490
491 static int unload_module(void)
492 {
493         int res;
494
495         res = ast_unregister_application(app);
496
497         ast_cli_unregister_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
498
499         if (say_cfg)
500                 ast_config_destroy(say_cfg);
501
502         return res;     
503 }
504
505 static int load_module(void)
506 {
507         struct ast_variable *v;
508         struct ast_flags config_flags = { 0 };
509
510         say_cfg = ast_config_load("say.conf", config_flags);
511         if (say_cfg) {
512                 for (v = ast_variable_browse(say_cfg, "general"); v ; v = v->next) {
513                         if (ast_extension_match(v->name, "mode")) {
514                                 say_init_mode(v->value);
515                                 break;
516                         }
517                 }
518         }
519
520         ast_cli_register_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
521         return ast_register_application(app, playback_exec, synopsis, descrip);
522 }
523
524 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application",
525                 .load = load_module,
526                 .unload = unload_module,
527                 .reload = reload,
528                );