destroy the loaded say.conf on module unload
[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 *tdesc = "Sound File Playback Application";
51
52 static char *app = "Playback";
53
54 static char *synopsis = "Play a file";
55
56 static char *descrip = 
57 "  Playback(filename[&filename2...][|option]):  Plays back given filenames (do not put\n"
58 "extension). Options may also be included following a pipe symbol. The 'skip'\n"
59 "option causes the playback of the message to be skipped if the channel\n"
60 "is not in the 'up' state (i.e. it hasn't been  answered  yet). If 'skip' is \n"
61 "specified, the application will return immediately should the channel not be\n"
62 "off hook.  Otherwise, unless 'noanswer' is specified, the channel will\n"
63 "be answered before the sound is played. Not all channels support playing\n"
64 "messages while still on hook. If 'j' is specified, the application\n"
65 "will jump to priority n+101 if present when a file specified to be played\n"
66 "does not exist.\n"
67 "This application sets the following channel variable upon completion:\n"
68 " PLAYBACKSTATUS    The status of the playback attempt as a text string, one of\n"
69 "               SUCCESS | FAILED\n"
70 ;
71
72 LOCAL_USER_DECL;
73
74 static struct ast_config *say_cfg;
75 /* save the say' api calls.
76  * The first entry is NULL if we have the standard source,
77  * otherwise we are sourcing from here.
78  * 'say load [new|old]' will enable the new or old method, or report status
79  */
80 static const void * say_api_buf[40];
81 static const char *say_old = "old";
82 static const char *say_new = "new";
83
84 static void save_say_mode(const void *arg)
85 {
86         int i = 0;
87         say_api_buf[i++] = arg;
88
89         say_api_buf[i++] = ast_say_number_full;
90         say_api_buf[i++] = ast_say_enumeration_full;
91         say_api_buf[i++] = ast_say_digit_str_full;
92         say_api_buf[i++] = ast_say_character_str_full;
93         say_api_buf[i++] = ast_say_phonetic_str_full;
94         say_api_buf[i++] = ast_say_datetime;
95         say_api_buf[i++] = ast_say_time;
96         say_api_buf[i++] = ast_say_date;
97         say_api_buf[i++] = ast_say_datetime_from_now;
98         say_api_buf[i++] = ast_say_date_with_format;
99 }
100
101 static void restore_say_mode(void *arg)
102 {
103         int i = 0;
104         say_api_buf[i++] = arg;
105
106         ast_say_number_full = say_api_buf[i++];
107         ast_say_enumeration_full = say_api_buf[i++];
108         ast_say_digit_str_full = say_api_buf[i++];
109         ast_say_character_str_full = say_api_buf[i++];
110         ast_say_phonetic_str_full = say_api_buf[i++];
111         ast_say_datetime = say_api_buf[i++];
112         ast_say_time = say_api_buf[i++];
113         ast_say_date = say_api_buf[i++];
114         ast_say_datetime_from_now = say_api_buf[i++];
115         ast_say_date_with_format = say_api_buf[i++];
116 }
117
118 /* 
119  * Typical 'say' arguments in addition to the date or number or string
120  * to say. We do not include 'options' because they may be different
121  * in recursive calls, and so they are better left as an external
122  * parameter.
123  */
124 typedef struct {
125         struct ast_channel *chan;
126         const char *ints;
127         const char *language;
128         int audiofd;
129         int ctrlfd;
130 } say_args_t;
131
132 static int s_streamwait3(const say_args_t *a, const char *fn)
133 {
134         int res = ast_streamfile(a->chan, fn, a->language);
135         if (res) {
136                 ast_log(LOG_WARNING, "Unable to play message %s\n", fn);
137                 return res;
138         }
139         res = (a->audiofd  > -1 && a->ctrlfd > -1) ?
140                 ast_waitstream_full(a->chan, a->ints, a->audiofd, a->ctrlfd) :
141                 ast_waitstream(a->chan, a->ints);
142         ast_stopstream(a->chan);
143         return res;  
144 }
145
146 /*
147  * the string is 'prefix:data' or prefix:fmt:data'
148  * with ':' being invalid in strings.
149  */
150 static int do_say(say_args_t *a, const char *s, const char *options, int depth)
151 {
152         struct ast_variable *v;
153         char *lang, *x, *rule = NULL;
154         int ret = 0;   
155         struct varshead head = { .first = NULL, .last = NULL };
156         struct ast_var_t *n;
157
158         ast_log(LOG_WARNING, "string <%s> depth <%d>\n", s, depth);
159         if (depth++ > 10) {
160                 ast_log(LOG_WARNING, "recursion too deep, exiting\n");
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   
260         if (!say_cfg) {
261                 ast_log(LOG_WARNING, "no say.conf, cannot spell '%s'\n", string);
262                 return -1;   
263         }
264         return do_say(&a, string, options, 0);
265 }
266
267 static int say_number_full(struct ast_channel *chan, int num,
268         const char *ints, const char *lang, const char *options,
269         int audiofd, int ctrlfd)
270 {
271         char buf[64];
272         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
273         snprintf(buf, sizeof(buf), "num:%d", num);
274         return do_say(&a, buf, options, 0);
275 }
276
277 static int say_enumeration_full(struct ast_channel *chan, int num,
278         const char *ints, const char *lang, const char *options,
279         int audiofd, int ctrlfd)
280 {
281         char buf[64];
282         say_args_t a = { chan, ints, lang, audiofd, ctrlfd };
283         snprintf(buf, sizeof(buf), "enum:%d", num);
284         return do_say(&a, buf, options, 0);
285 }
286
287 static int say_date_generic(struct ast_channel *chan, time_t t,
288         const char *ints, const char *lang, const char *format, const char *timezone, const char *prefix)
289 {
290         char buf[128];
291         struct tm tm;
292         say_args_t a = { chan, ints, lang, -1, -1 };
293         if (format == NULL)
294                 format = "";
295
296         ast_localtime(&t, &tm, NULL);
297         snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
298                 prefix,
299                 format,
300                 tm.tm_year+1900,
301                 tm.tm_mon+1,
302                 tm.tm_mday,
303                 tm.tm_hour,
304                 tm.tm_min,
305                 tm.tm_sec,
306                 tm.tm_wday,
307                 tm.tm_yday);
308         return do_say(&a, buf, NULL, 0);
309 }
310
311 static int say_date_with_format(struct ast_channel *chan, time_t t,
312         const char *ints, const char *lang, const char *format, const char *timezone)
313 {
314         return say_date_generic(chan, t, ints, lang, format, timezone, "datetime");
315 }
316
317 static int say_date(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
318 {
319         return say_date_generic(chan, t, ints, lang, "", NULL, "date");
320 }
321
322 static int say_time(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
323 {
324         return say_date_generic(chan, t, ints, lang, "", NULL, "time");
325 }
326
327 static int say_datetime(struct ast_channel *chan, time_t t, const char *ints, const char *lang)
328 {
329         return say_date_generic(chan, t, ints, lang, "", NULL, "datetime");
330 }
331
332 /*
333  * remap the 'say' functions to use those in this file
334  */
335 static int __say_init(int fd, int argc, char *argv[])
336 {
337         const char *old_mode = say_api_buf[0] ? say_new : say_old;
338         char *mode;
339
340         if (argc == 2) {
341                 ast_cli(fd, "say mode is [%s]\n", old_mode);
342                 return RESULT_SUCCESS;
343         } else if (argc != 3)
344                 return RESULT_SHOWUSAGE;
345         mode = argv[2];
346
347         ast_log(LOG_WARNING, "init say.c from %s to %s", old_mode, mode);
348
349         if (!strcmp(mode, old_mode)) {
350                 ast_log(LOG_WARNING, "say mode is %s already\n", mode);
351         } else if (!strcmp(mode, say_new)) {
352                 if (say_cfg == NULL)
353                         say_cfg = ast_config_load("say.conf");
354                 save_say_mode(say_new);
355                 ast_say_number_full = say_number_full;
356
357                 ast_say_enumeration_full = say_enumeration_full;
358 #if 0
359                 ast_say_digits_full = say_digits_full;
360                 ast_say_digit_str_full = say_digit_str_full;
361                 ast_say_character_str_full = say_character_str_full;
362                 ast_say_phonetic_str_full = say_phonetic_str_full;
363                 ast_say_datetime_from_now = say_datetime_from_now;
364 #endif
365                 ast_say_datetime = say_datetime;
366                 ast_say_time = say_time;
367                 ast_say_date = say_date;
368                 ast_say_date_with_format = say_date_with_format;
369         } else if (!strcmp(mode, say_old) && say_api_buf[0] == say_new) {
370                 restore_say_mode(NULL);
371         } else {
372                 ast_log(LOG_WARNING, "unrecognized mode %s\n", mode);
373         }
374         return RESULT_SUCCESS;
375 }
376
377 static struct ast_cli_entry myclis[] = {
378         { { "say", "load", NULL }, __say_init, "set/show the say mode", "say load new|old" },
379 };
380
381 static int playback_exec(struct ast_channel *chan, void *data)
382 {
383         int res = 0;
384         struct localuser *u;
385         char *tmp;
386         int option_skip=0;
387         int option_say=0;
388         int option_noanswer = 0;
389         int priority_jump = 0;
390
391         AST_DECLARE_APP_ARGS(args,
392                 AST_APP_ARG(filenames);
393                 AST_APP_ARG(options);
394         );
395         
396         if (ast_strlen_zero(data)) {
397                 ast_log(LOG_WARNING, "Playback requires an argument (filename)\n");
398                 return -1;
399         }
400
401         tmp = ast_strdupa(data);
402
403         LOCAL_USER_ADD(u);
404         AST_STANDARD_APP_ARGS(args, tmp);
405
406         if (args.options) {
407                 if (strcasestr(args.options, "skip"))
408                         option_skip = 1;
409                 if (strcasestr(args.options, "say"))
410                         option_say = 1;
411                 if (strcasestr(args.options, "noanswer"))
412                         option_noanswer = 1;
413                 if (strchr(args.options, 'j'))
414                         priority_jump = 1;
415         }
416         
417         if (chan->_state != AST_STATE_UP) {
418                 if (option_skip) {
419                         /* At the user's option, skip if the line is not up */
420                         goto done;
421                 } else if (!option_noanswer)
422                         /* Otherwise answer unless we're supposed to send this while on-hook */
423                         res = ast_answer(chan);
424         }
425         if (!res) {
426                 int mres = 0;
427                 char *front;
428
429                 ast_stopstream(chan);
430                 while (!res && (front = strsep(&tmp, "&"))) {
431                         if (option_say)
432                                 res = say_full(chan, front, "", chan->language, NULL, -1, -1);
433                         else
434                                 res = ast_streamfile(chan, front, chan->language);
435                         if (!res) { 
436                                 res = ast_waitstream(chan, ""); 
437                                 ast_stopstream(chan);
438                         } else {
439                                 ast_log(LOG_WARNING, "ast_streamfile failed on %s for %s\n", chan->name, (char *)data);
440                                 if (priority_jump || ast_opt_priority_jumping)
441                                         ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
442                                 res = 0;
443                                 mres = 1;
444                         }
445                 }
446                 pbx_builtin_setvar_helper(chan, "PLAYBACKSTATUS", mres ? "FAILED" : "SUCCESS");
447         }
448 done:
449         LOCAL_USER_REMOVE(u);
450         return res;
451 }
452
453 static int reload(void *mod)
454 {
455         if (say_cfg) {
456                 ast_config_destroy(say_cfg);
457                 ast_log(LOG_NOTICE, "Reloading say.conf\n");
458         }
459         say_cfg = ast_config_load("say.conf");
460         /*
461          * XXX here we should sort rules according to the same order
462          * we have in pbx.c so we have the same matching behaviour.
463          */
464         return 0;
465 }
466
467 static int unload_module(void *mod)
468 {
469         int res;
470
471         res = ast_unregister_application(app);
472
473         STANDARD_HANGUP_LOCALUSERS;
474
475         if (say_cfg)
476                 ast_config_destroy(say_cfg);
477
478         return res;     
479 }
480
481 static int load_module(void *mod)
482 {
483         reload(mod);
484         ast_cli_register_multiple(myclis, sizeof(myclis)/sizeof(struct ast_cli_entry));
485         return ast_register_application(app, playback_exec, synopsis, descrip);
486 }
487
488 static const char *description(void)
489 {
490         return tdesc;
491 }
492
493 static const char *key(void)
494 {
495         return ASTERISK_GPL_KEY;
496 }
497
498 STD_MOD(MOD_1,reload,NULL,NULL);