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