4e4f6338922cedd921287db7653e785eb4351b60
[asterisk/asterisk.git] / funcs / func_dialgroup.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2007, Tilghman Lesher
5  *
6  * Tilghman Lesher <func_dialgroup__200709@the-tilghman.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 Dial group dialplan function
22  *
23  * \author Tilghman Lesher <func_dialgroup__200709@the-tilghman.com>
24  *
25  * \ingroup functions
26  */
27
28 #include "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <sys/stat.h>
33
34 #include "asterisk/module.h"
35 #include "asterisk/channel.h"
36 #include "asterisk/pbx.h"
37 #include "asterisk/utils.h"
38 #include "asterisk/app.h"
39 #include "asterisk/astobj2.h"
40 #include "asterisk/astdb.h"
41
42 /*** DOCUMENTATION
43         <function name="DIALGROUP" language="en_US">
44                 <synopsis>
45                         Manages a group of users for dialing.
46                 </synopsis>
47                 <syntax>
48                         <parameter name="group" required="true" />
49                         <parameter name="op">
50                                 <para>The operation name, possible values are:</para>
51                                 <para><literal>add</literal> - add a channel name or interface (write-only)</para>
52                                 <para><literal>del</literal> - remove a channel name or interface (write-only)</para>
53                         </parameter>
54                 </syntax>
55                 <description>
56                         <para>Presents an interface meant to be used in concert with the Dial
57                         application, by presenting a list of channels which should be dialled when
58                         referenced.</para>
59                         <para>When DIALGROUP is read from, the argument is interpreted as the particular
60                         <replaceable>group</replaceable> for which a dial should be attempted.  When DIALGROUP is written to
61                         with no arguments, the entire list is replaced with the argument specified.</para>
62                         <para>Functionality is similar to a queue, except that when no interfaces are
63                         available, execution may continue in the dialplan.  This is useful when
64                         you want certain people to be the first to answer any calls, with immediate
65                         fallback to a queue when the front line people are busy or unavailable, but
66                         you still want front line people to log in and out of that group, just like
67                         a queue.</para>
68                         <para>Example:</para>
69                         <para>exten => 1,1,Set(DIALGROUP(mygroup,add)=SIP/10)</para>
70                         <para>exten => 1,n,Set(DIALGROUP(mygroup,add)=SIP/20)</para>
71                         <para>exten => 1,n,Dial(${DIALGROUP(mygroup)})</para>
72                 </description>
73         </function>
74  ***/
75
76 static struct ao2_container *group_container = NULL;
77
78 struct group_entry {
79         char name[AST_CHANNEL_NAME];
80 };
81
82 struct group {
83         char name[AST_MAX_EXTENSION];
84         struct ao2_container *entries;
85 };
86
87 static void group_destroy(void *vgroup)
88 {
89         struct group *group = vgroup;
90         ao2_ref(group->entries, -1);
91 }
92
93 static int group_hash_fn(const void *obj, const int flags)
94 {
95         const struct group *g = obj;
96         return ast_str_hash(g->name);
97 }
98
99 static int group_cmp_fn(void *obj1, void *name2, int flags)
100 {
101         struct group *g1 = obj1, *g2 = name2;
102         char *name = name2;
103         if (flags & OBJ_POINTER)
104                 return strcmp(g1->name, g2->name) ? 0 : CMP_MATCH | CMP_STOP;
105         else
106                 return strcmp(g1->name, name) ? 0 : CMP_MATCH | CMP_STOP;
107 }
108
109 static int entry_hash_fn(const void *obj, const int flags)
110 {
111         const struct group_entry *e = obj;
112         return ast_str_hash(e->name);
113 }
114
115 static int entry_cmp_fn(void *obj1, void *name2, int flags)
116 {
117         struct group_entry *e1 = obj1, *e2 = name2;
118         char *name = name2;
119         if (flags & OBJ_POINTER)
120                 return strcmp(e1->name, e2->name) ? 0 : CMP_MATCH | CMP_STOP;
121         else
122                 return strcmp(e1->name, name) ? 0 : CMP_MATCH | CMP_STOP;
123 }
124
125 static int dialgroup_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
126 {
127         struct ao2_iterator i;
128         struct group *grhead = ao2_find(group_container, data, 0);
129         struct group_entry *entry;
130         size_t bufused = 0;
131         int trunc_warning = 0;
132         int res = 0;
133
134         if (!grhead) {
135                 if (!ast_strlen_zero(cmd)) {
136                         ast_log(LOG_WARNING, "No such dialgroup '%s'\n", data);
137                 }
138                 return -1;
139         }
140
141         buf[0] = '\0';
142
143         i = ao2_iterator_init(grhead->entries, OBJ_POINTER);
144         while ((entry = ao2_iterator_next(&i))) {
145                 int tmp = strlen(entry->name);
146                 /* Ensure that we copy only complete names, not partials */
147                 if (len - bufused > tmp + 2) {
148                         if (bufused != 0)
149                                 buf[bufused++] = '&';
150                         ast_copy_string(buf + bufused, entry->name, len - bufused);
151                         bufused += tmp;
152                 } else if (trunc_warning++ == 0) {
153                         if (!ast_strlen_zero(cmd)) {
154                                 ast_log(LOG_WARNING, "Dialgroup '%s' is too large.  Truncating list.\n", data);
155                         } else {
156                                 res = 1;
157                                 ao2_ref(entry, -1);
158                                 break;
159                         }
160                 }
161                 ao2_ref(entry, -1);
162         }
163
164         return res;
165 }
166
167 static int dialgroup_refreshdb(struct ast_channel *chan, const char *cdialgroup)
168 {
169         int len = 500, res = 0;
170         char *buf = NULL;
171         char *dialgroup = ast_strdupa(cdialgroup);
172
173         do {
174                 len *= 2;
175                 buf = ast_realloc(buf, len);
176
177                 if ((res = dialgroup_read(chan, "", dialgroup, buf, len)) < 0) {
178                         ast_free(buf);
179                         return -1;
180                 }
181         } while (res == 1);
182
183         if (ast_strlen_zero(buf)) {
184                 ast_db_del("dialgroup", cdialgroup);
185         } else {
186                 ast_db_put("dialgroup", cdialgroup, buf);
187         }
188         ast_free(buf);
189         return 0;
190 }
191
192 static int dialgroup_write(struct ast_channel *chan, const char *cmd, char *data, const char *cvalue)
193 {
194         struct group *grhead;
195         struct group_entry *entry;
196         int j, needrefresh = 1;
197         AST_DECLARE_APP_ARGS(args,
198                 AST_APP_ARG(group);
199                 AST_APP_ARG(op);
200         );
201         AST_DECLARE_APP_ARGS(inter,
202                 AST_APP_ARG(faces)[100];
203         );
204         char *value = ast_strdupa(cvalue);
205
206         AST_STANDARD_APP_ARGS(args, data);
207         AST_NONSTANDARD_APP_ARGS(inter, value, '&');
208
209         if (!(grhead = ao2_find(group_container, args.group, 0))) {
210                 /* Create group */
211                 grhead = ao2_alloc(sizeof(*grhead), group_destroy);
212                 if (!grhead)
213                         return -1;
214                 grhead->entries = ao2_container_alloc(37, entry_hash_fn, entry_cmp_fn);
215                 if (!grhead->entries) {
216                         ao2_ref(grhead, -1);
217                         return -1;
218                 }
219                 ast_copy_string(grhead->name, args.group, sizeof(grhead->name));
220                 ao2_link(group_container, grhead);
221         }
222
223         if (ast_strlen_zero(args.op)) {
224                 /* Wholesale replacement of the group */
225                 args.op = "add";
226
227                 /* Remove all existing */
228                 ao2_ref(grhead->entries, -1);
229                 if (!(grhead->entries = ao2_container_alloc(37, entry_hash_fn, entry_cmp_fn))) {
230                         ao2_unlink(group_container, grhead);
231                         ao2_ref(grhead, -1);
232                         return -1;
233                 }
234         }
235
236         if (strcasecmp(args.op, "add") == 0) {
237                 for (j = 0; j < inter.argc; j++) {
238                         if ((entry = ao2_alloc(sizeof(*entry), NULL))) {
239                                 ast_copy_string(entry->name, inter.faces[j], sizeof(entry->name));
240                                 ao2_link(grhead->entries, entry);
241                                 ao2_ref(entry, -1);
242                         } else {
243                                 ast_log(LOG_WARNING, "Unable to add '%s' to dialgroup '%s'\n", inter.faces[j], grhead->name);
244                         }
245                 }
246         } else if (strncasecmp(args.op, "del", 3) == 0) {
247                 for (j = 0; j < inter.argc; j++) {
248                         if ((entry = ao2_find(grhead->entries, inter.faces[j], OBJ_UNLINK))) {
249                                 ao2_ref(entry, -1);
250                         } else {
251                                 ast_log(LOG_WARNING, "Interface '%s' not found in dialgroup '%s'\n", inter.faces[j], grhead->name);
252                         }
253                 }
254         } else {
255                 ast_log(LOG_ERROR, "Unrecognized operation: %s\n", args.op);
256                 needrefresh = 0;
257         }
258         ao2_ref(grhead, -1);
259
260         if (needrefresh) {
261                 dialgroup_refreshdb(chan, args.group);
262         }
263
264         return 0;
265 }
266
267 static struct ast_custom_function dialgroup_function = {
268         .name = "DIALGROUP",
269         .read = dialgroup_read,
270         .write = dialgroup_write,
271 };
272
273 static int unload_module(void)
274 {
275         int res = ast_custom_function_unregister(&dialgroup_function);
276         ao2_ref(group_container, -1);
277         return res;
278 }
279
280 static int load_module(void)
281 {
282         struct ast_db_entry *dbtree, *tmp;
283         char groupname[AST_MAX_EXTENSION], *ptr;
284
285         if ((group_container = ao2_container_alloc(37, group_hash_fn, group_cmp_fn))) {
286                 /* Refresh groups from astdb */
287                 if ((dbtree = ast_db_gettree("dialgroup", NULL))) {
288                         for (tmp = dbtree; tmp; tmp = tmp->next) {
289                                 ast_copy_string(groupname, tmp->key, sizeof(groupname));
290                                 if ((ptr = strrchr(groupname, '/'))) {
291                                         ptr++;
292                                         dialgroup_write(NULL, "", ptr, tmp->data);
293                                 }
294                         }
295                         ast_db_freetree(dbtree);
296                 }
297                 return ast_custom_function_register(&dialgroup_function);
298         } else {
299                 return AST_MODULE_LOAD_DECLINE;
300         }
301 }
302
303 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Dialgroup dialplan function");