Applications no longer need to call ast_module_user_add and ast_module_user_remove...
[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 tm tm;
285         say_args_t a = { chan, ints, lang, -1, -1 };
286         if (format == NULL)
287                 format = "";
288
289         ast_localtime(&t, &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(int fd, int argc, char *argv[])
329 {
330         const char *old_mode = say_api_buf[0] ? say_new : say_old;
331         char *mode;
332
333         if (argc == 2) {
334                 ast_cli(fd, "say mode is [%s]\n", old_mode);
335                 return RESULT_SUCCESS;
336         } else if (argc != 3)
337                 return RESULT_SHOWUSAGE;
338         mode = argv[2];
339
340         ast_log(LOG_WARNING, "init say.c from %s to %s\n", old_mode, mode);
341
342         if (!strcmp(mode, old_mode)) {
343                 ast_log(LOG_WARNING, "say mode is %s already\n", mode);
344         } else if (!strcmp(mode, say_new)) {
345                 if (say_cfg == NULL)
346                         say_cfg = ast_config_load("say.conf");
347                 save_say_mode(say_new);
348                 ast_say_number_full = say_number_full;
349
350                 ast_say_enumeration_full = say_enumeration_full;
351 #if 0
352                 ast_say_digits_full = say_digits_full;
353                 ast_say_digit_str_full = say_digit_str_full;
354                 ast_say_character_str_full = say_character_str_full;
355                 ast_say_phonetic_str_full = say_phonetic_str_full;
356                 ast_say_datetime_from_now = say_datetime_from_now;
357 #endif
358                 ast_say_datetime = say_datetime;
359                 ast_say_time = say_time;
360                 ast_say_date = say_date;
361                 ast_say_date_with_format = say_date_with_format;
362         } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
363                 restore_say_mode(NULL);
364         } else {
365                 ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
366         }
367         return RESULT_SUCCESS;
368 }
369
370 static struct ast_cli_entry cli_playback[] = {
371         { { "say", "load", NULL },
372         __say_init, "set/show the say mode",
373         "say load new|old" },
374 };
375
376 static int playback_exec(struct ast_channel *chan, void *data)
377 {
378         int res = 0;
379         int mres = 0;
380         char *tmp;
381         int option_skip=0;
382         int option_say=0;
383         int option_noanswer = 0;
384
385         AST_DECLARE_APP_ARGS(args,
386                 AST_APP_ARG(filenames);
387                 AST_APP_ARG(options);
388         );
389         
390         if (ast_strlen_zero(data)) {
391                 ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
392                 return -1;
393         }
394
395         tmp = ast_strdupa(data);
396         AST_STANDARD_APP_ARGS(args, tmp);
397
398         if (args.options) {
399                 if (strcasestr(args.options, "skip"))
400                         option_skip = 1;
401                 if (strcasestr(args.options, "say"))
402                         option_say = 1;
403                 if (strcasestr(args.options, "noanswer"))
404                         option_noanswer = 1;
405         } 
406         if (chan->_state != AST_STATE_UP) {
407                 if (option_skip) {
408                         /* At the user's option, skip if the line is not up */
409                         goto done;
410                 } else if (!option_noanswer)
411                         /* Otherwise answer unless we're supposed to send this while on-hook */
412                         res = ast_answer(chan);
413         }
414         if (!res) {
415                 char *back = args.filenames;
416                 char *front;
417
418                 ast_stopstream(chan);
419                 while (!res && (front = strsep(&back, "&"))) {
420                         if (option_say)
421                                 res = say_full(chan, front, "", chan->language, NULL, -1, -1);
422                         else
423                                 res = ast_streamfile(chan, front, chan->language);
424                         if (!res) { 
425                                 res = ast_waitstream(chan, ""); 
426                                 ast_stopstream(chan);
427                         } else {
428                                 ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
429                                 res = 0;
430                                 mres = 1;
431                         }
432                 }
433         }
434 done:
435         pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
436         return res;
437 }
438
439 static int reload(void)
440 {
441         if (say_cfg) {
442                 ast_config_destroy(say_cfg);
443                 ast_log(LOG_NOTICE, "Reloading say.conf\n");
444         }
445         say_cfg = ast_config_load("say.conf");
446         /*
447          * XXX here we should sort rules according to the same order
448          * we have in pbx.c so we have the same matching behaviour.
449          */
450         return 0;
451 }
452
453 static int unload_module(void)
454 {
455         int res;
456
457         res = ast_unregister_application(app);
458
459         ast_cli_unregister_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
460
461         if (say_cfg)
462                 ast_config_destroy(say_cfg);
463
464         return res;     
465 }
466
467 static int load_module(void)
468 {
469         say_cfg = ast_config_load("say.conf");
470         ast_cli_register_multiple(cli_playback, sizeof(cli_playback) / sizeof(struct ast_cli_entry));
471         return ast_register_application(app, playback_exec, synopsis, descrip);
472 }
473
474 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Sound File Playback Application",
475                 .load = load_module,
476                 .unload = unload_module,
477                 .reload = reload,
478                );