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