ast_pkgconfig.m4: AST_PKG_CONFIG_CHECK() relies on sed.
[asterisk/asterisk.git] / apps / app_read.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 read a variable
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 #include "asterisk/file.h"
35 #include "asterisk/pbx.h"
36 #include "asterisk/channel.h"
37 #include "asterisk/app.h"
38 #include "asterisk/module.h"
39 #include "asterisk/indications.h"
40
41 /*** DOCUMENTATION
42         <application name="Read" language="en_US">
43                 <synopsis>
44                         Read a variable.
45                 </synopsis>
46                 <syntax>
47                         <parameter name="variable" required="true">
48                                 <para>The input digits will be stored in the given <replaceable>variable</replaceable>
49                                 name.</para>
50                         </parameter>
51                         <parameter name="filenames" argsep="&amp;">
52                                 <argument name="filename" required="true">
53                                         <para>file(s) to play before reading digits or tone with option i</para>
54                                 </argument>
55                                 <argument name="filename2" multiple="true" />
56                         </parameter>
57                         <parameter name="maxdigits">
58                                 <para>Maximum acceptable number of digits. Stops reading after
59                                 <replaceable>maxdigits</replaceable> have been entered (without
60                                 requiring the user to press the <literal>#</literal> key).</para>
61                                 <para>Defaults to <literal>0</literal> - no limit - wait for the
62                                 user press the <literal>#</literal> key. Any value below
63                                 <literal>0</literal> means the same. Max accepted value is
64                                 <literal>255</literal>.</para>
65                         </parameter>
66                         <parameter name="options">
67                                 <optionlist>
68                                         <option name="s">
69                                                 <para>to return immediately if the line is not up.</para>
70                                         </option>
71                                         <option name="i">
72                                                 <para>to play  filename as an indication tone from your
73                                                 <filename>indications.conf</filename>.</para>
74                                         </option>
75                                         <option name="n">
76                                                 <para>to read digits even if the line is not up.</para>
77                                         </option>
78                                         <option name="t">
79                                                 <para>Terminator digit(s) to use for ending input.
80                                                 Default is <literal>#</literal>. If you need to read
81                                                 the digit <literal>#</literal> literally, you should
82                                                 remove or change the terminator character. Multiple
83                                                 terminator characters may be specified. If no terminator
84                                                 digit is present, input cannot be ended using digits
85                                                 and you will need to rely on duration and max digits
86                                                 for ending input.</para>
87                                         </option>
88                                 </optionlist>
89                         </parameter>
90                         <parameter name="attempts">
91                                 <para>If greater than <literal>1</literal>, that many
92                                 <replaceable>attempts</replaceable> will be made in the
93                                 event no data is entered.</para>
94                         </parameter>
95                         <parameter name="timeout">
96                                 <para>The number of seconds to wait for a digit response. If greater
97                                 than <literal>0</literal>, that value will override the default timeout.
98                                 Can be floating point.</para>
99                         </parameter>
100                 </syntax>
101                 <description>
102                         <para>Reads a #-terminated string of digits a certain number of times from the
103                         user in to the given <replaceable>variable</replaceable>.</para>
104                         <para>This application sets the following channel variable upon completion:</para>
105                         <variablelist>
106                                 <variable name="READSTATUS">
107                                         <para>This is the status of the read operation.</para>
108                                         <value name="OK" />
109                                         <value name="ERROR" />
110                                         <value name="HANGUP" />
111                                         <value name="INTERRUPTED" />
112                                         <value name="SKIPPED" />
113                                         <value name="TIMEOUT" />
114                                 </variable>
115                         </variablelist>
116                 </description>
117                 <see-also>
118                         <ref type="application">SendDTMF</ref>
119                 </see-also>
120         </application>
121  ***/
122
123 enum read_option_flags {
124         OPT_SKIP = (1 << 0),
125         OPT_INDICATION = (1 << 1),
126         OPT_NOANSWER = (1 << 2),
127         OPT_TERMINATOR = (1 << 3),
128 };
129
130 enum {
131         OPT_ARG_TERMINATOR,
132         /* note: this entry _MUST_ be the last one in the enum */
133         OPT_ARG_ARRAY_SIZE,
134 };
135
136 AST_APP_OPTIONS(read_app_options, {
137         AST_APP_OPTION('s', OPT_SKIP),
138         AST_APP_OPTION('i', OPT_INDICATION),
139         AST_APP_OPTION('n', OPT_NOANSWER),
140         AST_APP_OPTION_ARG('t', OPT_TERMINATOR, OPT_ARG_TERMINATOR),
141 });
142
143 static char *app = "Read";
144
145 static int read_exec(struct ast_channel *chan, const char *data)
146 {
147         int res = 0;
148         char tmp[256] = "";
149         int maxdigits = 255;
150         int tries = 1, to = 0, x = 0;
151         double tosec;
152         char *argcopy = NULL;
153         char *opt_args[OPT_ARG_ARRAY_SIZE];
154         struct ast_tone_zone_sound *ts = NULL;
155         struct ast_flags flags = {0};
156         const char *status = "ERROR";
157         char *terminator = "#"; /* use default terminator # by default */
158
159         AST_DECLARE_APP_ARGS(arglist,
160                 AST_APP_ARG(variable);
161                 AST_APP_ARG(filename);
162                 AST_APP_ARG(maxdigits);
163                 AST_APP_ARG(options);
164                 AST_APP_ARG(attempts);
165                 AST_APP_ARG(timeout);
166         );
167
168         pbx_builtin_setvar_helper(chan, "READSTATUS", status);
169         if (ast_strlen_zero(data)) {
170                 ast_log(LOG_WARNING, "Read requires an argument (variable)\n");
171                 return 0;
172         }
173
174         argcopy = ast_strdupa(data);
175
176         AST_STANDARD_APP_ARGS(arglist, argcopy);
177
178         if (!ast_strlen_zero(arglist.options)) {
179                 ast_app_parse_options(read_app_options, &flags, opt_args, arglist.options);
180         }
181
182         if (!ast_strlen_zero(arglist.attempts)) {
183                 tries = atoi(arglist.attempts);
184                 if (tries <= 0)
185                         tries = 1;
186         }
187
188         if (!ast_strlen_zero(arglist.timeout)) {
189                 tosec = atof(arglist.timeout);
190                 if (tosec <= 0)
191                         to = 0;
192                 else
193                         to = tosec * 1000.0;
194         }
195
196         if (ast_strlen_zero(arglist.filename)) {
197                 arglist.filename = NULL;
198         }
199         if (!ast_strlen_zero(arglist.maxdigits)) {
200                 maxdigits = atoi(arglist.maxdigits);
201                 if ((maxdigits < 1) || (maxdigits > 255)) {
202                         maxdigits = 255;
203                 } else
204                         ast_verb(3, "Accepting a maximum of %d digits.\n", maxdigits);
205         }
206         if (ast_strlen_zero(arglist.variable)) {
207                 ast_log(LOG_WARNING, "Invalid! Usage: Read(variable[,filename][,maxdigits][,option][,attempts][,timeout])\n\n");
208                 return 0;
209         }
210         if (ast_test_flag(&flags, OPT_INDICATION)) {
211                 if (!ast_strlen_zero(arglist.filename)) {
212                         ts = ast_get_indication_tone(ast_channel_zone(chan), arglist.filename);
213                 }
214         }
215         if (ast_test_flag(&flags, OPT_TERMINATOR)) {
216                 if (!ast_strlen_zero(opt_args[OPT_ARG_TERMINATOR])) {
217                         terminator = opt_args[OPT_ARG_TERMINATOR];
218                 } else {
219                         terminator = ""; /* no digit inherently will terminate input */
220                 }
221         }
222         if (ast_channel_state(chan) != AST_STATE_UP) {
223                 if (ast_test_flag(&flags, OPT_SKIP)) {
224                         /* At the user's option, skip if the line is not up */
225                         if (ts) {
226                                 ts = ast_tone_zone_sound_unref(ts);
227                         }
228                         pbx_builtin_setvar_helper(chan, arglist.variable, "");
229                         pbx_builtin_setvar_helper(chan, "READSTATUS", "SKIPPED");
230                         return 0;
231                 } else if (!ast_test_flag(&flags, OPT_NOANSWER)) {
232                         /* Otherwise answer unless we're supposed to read while on-hook */
233                         res = ast_answer(chan);
234                 }
235         }
236         if (!res) {
237                 while (tries && !res) {
238                         ast_stopstream(chan);
239                         if (ts && ts->data[0]) {
240                                 if (!to)
241                                         to = ast_channel_pbx(chan) ? ast_channel_pbx(chan)->rtimeoutms : 6000;
242                                 res = ast_playtones_start(chan, 0, ts->data, 0);
243                                 for (x = 0; x < maxdigits; ) {
244                                         res = ast_waitfordigit(chan, to);
245                                         ast_playtones_stop(chan);
246                                         if (res < 1) {
247                                                 if (res == 0)
248                                                         status = "TIMEOUT";
249                                                 tmp[x]='\0';
250                                                 break;
251                                         }
252                                         tmp[x++] = res;
253                                         if (terminator && strchr(terminator, tmp[x-1])) {
254                                                 tmp[x-1] = '\0';
255                                                 status = "OK";
256                                                 break;
257                                         }
258                                         if (x >= maxdigits) {
259                                                 status = "OK";
260                                         }
261                                 }
262                         } else {
263                                 res = ast_app_getdata_terminator(chan, arglist.filename, tmp, maxdigits, to, terminator);
264                                 if (res == AST_GETDATA_COMPLETE || res == AST_GETDATA_EMPTY_END_TERMINATED)
265                                         status = "OK";
266                                 else if (res == AST_GETDATA_TIMEOUT)
267                                         status = "TIMEOUT";
268                                 else if (res == AST_GETDATA_INTERRUPTED)
269                                         status = "INTERRUPTED";
270                         }
271                         if (res > -1) {
272                                 pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
273                                 if (!ast_strlen_zero(tmp)) {
274                                         ast_verb(3, "User entered '%s'\n", tmp);
275                                         tries = 0;
276                                 } else {
277                                         tries--;
278                                         if (tries)
279                                                 ast_verb(3, "User entered nothing, %d chance%s left\n", tries, (tries != 1) ? "s" : "");
280                                         else
281                                                 ast_verb(3, "User entered nothing.\n");
282                                 }
283                                 res = 0;
284                         } else {
285                                 pbx_builtin_setvar_helper(chan, arglist.variable, tmp);
286                                 ast_verb(3, "User disconnected\n");
287                         }
288                 }
289         }
290
291         if (ts) {
292                 ts = ast_tone_zone_sound_unref(ts);
293         }
294
295         if (ast_check_hangup(chan))
296                 status = "HANGUP";
297         pbx_builtin_setvar_helper(chan, "READSTATUS", status);
298         return 0;
299 }
300
301 static int unload_module(void)
302 {
303         return ast_unregister_application(app);
304 }
305
306 static int load_module(void)
307 {
308         return ast_register_application_xml(app, read_exec);
309 }
310
311 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Read Variable Application");