Don't reload a configuration file if nothing has changed.
[asterisk/asterisk.git] / res / res_indications.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2002, Pauline Middelink
5  *
6  *
7  * See http://www.asterisk.org for more information about
8  * the Asterisk project. Please do not directly contact
9  * any of the maintainers of this project for assistance;
10  * the project provides a web site, mailing lists and IRC
11  * channels for your use.
12  *
13  * This program is free software, distributed under the terms of
14  * the GNU General Public License Version 2. See the LICENSE file
15  * at the top of the source tree.
16  */
17
18 /*! \file res_indications.c 
19  *
20  * \brief Load the indications
21  * 
22  * \author Pauline Middelink <middelink@polyware.nl>
23  *
24  * Load the country specific dialtones into the asterisk PBX.
25  */
26  
27 #include "asterisk.h"
28
29 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
30
31 #include <unistd.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <errno.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39
40 #include "asterisk/lock.h"
41 #include "asterisk/file.h"
42 #include "asterisk/cli.h"
43 #include "asterisk/logger.h"
44 #include "asterisk/config.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/module.h"
48 #include "asterisk/translate.h"
49 #include "asterisk/indications.h"
50 #include "asterisk/utils.h"
51
52 /* Globals */
53 static const char config[] = "indications.conf";
54
55 /*
56  * Help for commands provided by this module ...
57  */
58 static char help_add_indication[] =
59 "Usage: indication add <country> <indication> \"<tonelist>\"\n"
60 "       Add the given indication to the country.\n";
61
62 static char help_remove_indication[] =
63 "Usage: indication remove <country> <indication>\n"
64 "       Remove the given indication from the country.\n";
65
66 static char help_show_indications[] =
67 "Usage: indication show [<country> ...]\n"
68 "       Display either a condensed for of all country/indications, or the\n"
69 "       indications for the specified countries.\n";
70
71 char *playtones_desc=
72 "PlayTones(arg): Plays a tone list. Execution will continue with the next step immediately,\n"
73 "while the tones continue to play.\n"
74 "Arg is either the tone name defined in the indications.conf configuration file, or a directly\n"
75 "specified list of frequencies and durations.\n"
76 "See the sample indications.conf for a description of the specification of a tonelist.\n\n"
77 "Use the StopPlayTones application to stop the tones playing. \n";
78
79 /*
80  * Implementation of functions provided by this module
81  */
82
83 /*!
84  * \brief Add a country to indication
85  * \param fd file descriptor of CLI
86  * \param argc no of args
87  * \param argv arguements
88  */
89 static int handle_add_indication(int fd, int argc, char *argv[])
90 {
91         struct ind_tone_zone *tz;
92         int created_country = 0;
93         if (argc != 5) return RESULT_SHOWUSAGE;
94
95         tz = ast_get_indication_zone(argv[2]);
96         if (!tz) {
97                 /* country does not exist, create it */
98                 ast_log(LOG_NOTICE, "Country '%s' does not exist, creating it.\n",argv[2]);
99                 
100                 if (!(tz = ast_calloc(1, sizeof(*tz)))) {
101                         return -1;
102                 }
103                 ast_copy_string(tz->country,argv[2],sizeof(tz->country));
104                 if (ast_register_indication_country(tz)) {
105                         ast_log(LOG_WARNING, "Unable to register new country\n");
106                         ast_free(tz);
107                         return -1;
108                 }
109                 created_country = 1;
110         }
111         if (ast_register_indication(tz,argv[3],argv[4])) {
112                 ast_log(LOG_WARNING, "Unable to register indication %s/%s\n",argv[2],argv[3]);
113                 if (created_country)
114                         ast_unregister_indication_country(argv[2]);
115                 return -1;
116         }
117         return 0;
118 }
119
120 /*!
121  * \brief Remove a country from indication
122  * \param fd file descriptor of CLI
123  * \param argc no of args
124  * \param argv arguements
125  */
126 static int handle_remove_indication(int fd, int argc, char *argv[])
127 {
128         struct ind_tone_zone *tz;
129         if (argc != 3 && argc != 4) return RESULT_SHOWUSAGE;
130
131         if (argc == 3) {
132                 /* remove entiry country */
133                 if (ast_unregister_indication_country(argv[2])) {
134                         ast_log(LOG_WARNING, "Unable to unregister indication country %s\n",argv[2]);
135                         return -1;
136                 }
137                 return 0;
138         }
139
140         tz = ast_get_indication_zone(argv[2]);
141         if (!tz) {
142                 ast_log(LOG_WARNING, "Unable to unregister indication %s/%s, country does not exists\n",argv[2],argv[3]);
143                 return -1;
144         }
145         if (ast_unregister_indication(tz,argv[3])) {
146                 ast_log(LOG_WARNING, "Unable to unregister indication %s/%s\n",argv[2],argv[3]);
147                 return -1;
148         }
149         return 0;
150 }
151
152 /*!
153  * \brief Show the current indications
154  * \param fd file descriptor of CLI
155  * \param argc no of args
156  * \param argv arguements
157  */
158 static int handle_show_indications(int fd, int argc, char *argv[])
159 {
160         struct ind_tone_zone *tz = NULL;
161         char buf[256];
162         int found_country = 0;
163
164         if (argc == 2) {
165                 /* no arguments, show a list of countries */
166                 ast_cli(fd,"Country Alias   Description\n"
167                            "===========================\n");
168                 while ( (tz = ast_walk_indications(tz) ) )
169                         ast_cli(fd,"%-7.7s %-7.7s %s\n", tz->country, tz->alias, tz->description);
170                 return 0;
171         }
172         /* there was a request for specific country(ies), lets humor them */
173         while ( (tz = ast_walk_indications(tz) ) ) {
174                 int i,j;
175                 for (i=2; i<argc; i++) {
176                         if (strcasecmp(tz->country,argv[i])==0 &&
177                             !tz->alias[0]) {
178                                 struct ind_tone_zone_sound* ts;
179                                 if (!found_country) {
180                                         found_country = 1;
181                                         ast_cli(fd,"Country Indication      PlayList\n"
182                                                    "=====================================\n");
183                                 }
184                                 j = snprintf(buf,sizeof(buf),"%-7.7s %-15.15s ",tz->country,"<ringcadence>");
185                                 for (i=0; i<tz->nrringcadence; i++) {
186                                         j += snprintf(buf+j,sizeof(buf)-j,"%d,",tz->ringcadence[i]);
187                                 }
188                                 if (tz->nrringcadence)
189                                         j--;
190                                 ast_copy_string(buf+j,"\n",sizeof(buf)-j);
191                                 ast_cli(fd,buf);
192                                 for (ts=tz->tones; ts; ts=ts->next)
193                                         ast_cli(fd,"%-7.7s %-15.15s %s\n",tz->country,ts->name,ts->data);
194                                 break;
195                         }
196                 }
197         }
198         if (!found_country)
199                 ast_cli(fd,"No countries matched your criteria.\n");
200         return -1;
201 }
202
203 /*!
204  * \brief play tone for indication country
205  * \param chan ast_channel to play the sounds back to
206  * \param data contains tone to play
207  */
208 static int handle_playtones(struct ast_channel *chan, void *data)
209 {
210         struct ind_tone_zone_sound *ts;
211         int res;
212
213         if (!data || !((char*)data)[0]) {
214                 ast_log(LOG_NOTICE,"Nothing to play\n");
215                 return -1;
216         }
217         ts = ast_get_indication_tone(chan->zone, (const char*)data);
218         if (ts && ts->data[0])
219                 res = ast_playtones_start(chan, 0, ts->data, 0);
220         else
221                 res = ast_playtones_start(chan, 0, (const char*)data, 0);
222         if (res)
223                 ast_log(LOG_NOTICE,"Unable to start playtones\n");
224         return res;
225 }
226
227 /*!
228  * \brief Stop tones playing
229  * \param chan 
230  * \param data 
231  */
232 static int handle_stopplaytones(struct ast_channel *chan, void *data)
233 {
234         ast_playtones_stop(chan);
235         return 0;
236 }
237
238 /*! \brief load indications module */
239 static int ind_load_module(int reload)
240 {
241         struct ast_config *cfg;
242         struct ast_variable *v;
243         char *cxt;
244         char *c;
245         struct ind_tone_zone *tones;
246         const char *country = NULL;
247         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
248
249         /* that the following cast is needed, is yuk! */
250         /* yup, checked it out. It is NOT written to. */
251         cfg = ast_config_load((char *)config, config_flags);
252         if (!cfg)
253                 return -1;
254         else if (cfg == CONFIG_STATUS_FILEUNCHANGED)
255                 return 0;
256
257         if (reload)
258                 ast_unregister_indication_country(NULL);
259
260         /* Use existing config to populate the Indication table */
261         cxt = ast_category_browse(cfg, NULL);
262         while(cxt) {
263                 /* All categories but "general" are considered countries */
264                 if (!strcasecmp(cxt, "general")) {
265                         cxt = ast_category_browse(cfg, cxt);
266                         continue;
267                 }               
268                 if (!(tones = ast_calloc(1, sizeof(*tones)))) {
269                         ast_config_destroy(cfg);
270                         return -1;
271                 }
272                 ast_copy_string(tones->country,cxt,sizeof(tones->country));
273
274                 v = ast_variable_browse(cfg, cxt);
275                 while(v) {
276                         if (!strcasecmp(v->name, "description")) {
277                                 ast_copy_string(tones->description, v->value, sizeof(tones->description));
278                         } else if ((!strcasecmp(v->name,"ringcadence"))||(!strcasecmp(v->name,"ringcadance"))) {
279                                 char *ring,*rings = ast_strdupa(v->value);
280                                 c = rings;
281                                 ring = strsep(&c,",");
282                                 while (ring) {
283                                         int *tmp, val;
284                                         if (!isdigit(ring[0]) || (val=atoi(ring))==-1) {
285                                                 ast_log(LOG_WARNING,"Invalid ringcadence given '%s' at line %d.\n",ring,v->lineno);
286                                                 ring = strsep(&c,",");
287                                                 continue;
288                                         }                                       
289                                         if (!(tmp = ast_realloc(tones->ringcadence, (tones->nrringcadence + 1) * sizeof(int)))) {
290                                                 ast_config_destroy(cfg);
291                                                 return -1;
292                                         }
293                                         tones->ringcadence = tmp;
294                                         tmp[tones->nrringcadence] = val;
295                                         tones->nrringcadence++;
296                                         /* next item */
297                                         ring = strsep(&c,",");
298                                 }
299                         } else if (!strcasecmp(v->name,"alias")) {
300                                 char *countries = ast_strdupa(v->value);
301                                 c = countries;
302                                 country = strsep(&c,",");
303                                 while (country) {
304                                         struct ind_tone_zone* azone;
305                                         if (!(azone = ast_calloc(1, sizeof(*azone)))) {
306                                                 ast_config_destroy(cfg);
307                                                 return -1;
308                                         }
309                                         ast_copy_string(azone->country, country, sizeof(azone->country));
310                                         ast_copy_string(azone->alias, cxt, sizeof(azone->alias));
311                                         if (ast_register_indication_country(azone)) {
312                                                 ast_log(LOG_WARNING, "Unable to register indication alias at line %d.\n",v->lineno);
313                                                 ast_free(tones);
314                                         }
315                                         /* next item */
316                                         country = strsep(&c,",");
317                                 }
318                         } else {
319                                 /* add tone to country */
320                                 struct ind_tone_zone_sound *ps,*ts;
321                                 for (ps=NULL,ts=tones->tones; ts; ps=ts, ts=ts->next) {
322                                         if (strcasecmp(v->name,ts->name)==0) {
323                                                 /* already there */
324                                                 ast_log(LOG_NOTICE,"Duplicate entry '%s', skipped.\n",v->name);
325                                                 goto out;
326                                         }
327                                 }
328                                 /* not there, add it to the back */                             
329                                 if (!(ts = ast_malloc(sizeof(*ts)))) {
330                                         ast_config_destroy(cfg);
331                                         return -1;
332                                 }
333                                 ts->next = NULL;
334                                 ts->name = ast_strdup(v->name);
335                                 ts->data = ast_strdup(v->value);
336                                 if (ps)
337                                         ps->next = ts;
338                                 else
339                                         tones->tones = ts;
340                         }
341 out:                    v = v->next;
342                 }
343                 if (tones->description[0] || tones->alias[0] || tones->tones) {
344                         if (ast_register_indication_country(tones)) {
345                                 ast_log(LOG_WARNING, "Unable to register indication at line %d.\n",v->lineno);
346                                 ast_free(tones);
347                         }
348                 } else ast_free(tones);
349
350                 cxt = ast_category_browse(cfg, cxt);
351         }
352
353         /* determine which country is the default */
354         country = ast_variable_retrieve(cfg,"general","country");
355         if (!country || !*country || ast_set_indication_country(country))
356                 ast_log(LOG_WARNING,"Unable to set the default country (for indication tones)\n");
357
358         ast_config_destroy(cfg);
359         return 0;
360 }
361
362 /*! \brief CLI entries for commands provided by this module */
363 static struct ast_cli_entry cli_indications[] = {
364         { { "indication", "add", NULL },
365         handle_add_indication, "Add the given indication to the country",
366         help_add_indication, NULL },
367
368         { { "indication", "remove", NULL },
369         handle_remove_indication, "Remove the given indication from the country",
370         help_remove_indication, NULL },
371
372         { { "indication", "show", NULL },
373         handle_show_indications, "Display a list of all countries/indications",
374         help_show_indications },
375 };
376
377 /*! \brief Unload indicators module */
378 static int unload_module(void)
379 {
380         /* remove the registed indications... */
381         ast_unregister_indication_country(NULL);
382
383         /* and the functions */
384         ast_cli_unregister_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry));
385         ast_unregister_application("PlayTones");
386         ast_unregister_application("StopPlayTones");
387         return 0;
388 }
389
390
391 /*! \brief Load indications module */
392 static int load_module(void)
393 {
394         if (ind_load_module(0))
395                 return AST_MODULE_LOAD_DECLINE; 
396         ast_cli_register_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry));
397         ast_register_application("PlayTones", handle_playtones, "Play a tone list", playtones_desc);
398         ast_register_application("StopPlayTones", handle_stopplaytones, "Stop playing a tone list","Stop playing a tone list");
399
400         return 0;
401 }
402
403 /*! \brief Reload indications module */
404 static int reload(void)
405 {
406         return ind_load_module(1);
407 }
408
409 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Region-specific tones",
410                 .load = load_module,
411                 .unload = unload_module,
412                 .reload = reload,
413                );