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