fe352888fe5cfdd0ff1900d0073b96ef37bec7e9
[asterisk/asterisk.git] / funcs / func_presencestate.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2011, Digium, Inc.
5  *
6  * David Vossel <dvossel@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 Custom presence provider
22  * \ingroup functions
23  */
24
25 /*** MODULEINFO
26         <support_level>core</support_level>
27  ***/
28
29 #include "asterisk.h"
30
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
32
33 #include "asterisk/module.h"
34 #include "asterisk/channel.h"
35 #include "asterisk/pbx.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/linkedlists.h"
38 #include "asterisk/presencestate.h"
39 #include "asterisk/cli.h"
40 #include "asterisk/astdb.h"
41 #include "asterisk/app.h"
42 #ifdef TEST_FRAMEWORK
43 #include "asterisk/test.h"
44 #include "asterisk/event.h"
45 #include <semaphore.h>
46 #endif
47
48 /*** DOCUMENTATION
49         <function name="PRESENCE_STATE" language="en_US">
50                 <synopsis>
51                         Get or Set a presence state.
52                 </synopsis>
53                 <syntax>
54                         <parameter name="provider" required="true">
55                           <para>The provider of the presence, such as <literal>CustomPresence</literal></para>
56                         </parameter>
57                         <parameter name="field" required="true">
58                           <para>Which field of the presence state information is wanted.</para>
59                           <optionlist>
60                                 <option name="value">
61                                   <para>The current presence, such as <literal>away</literal></para>
62                                 </option>
63                                 <option name="subtype">
64                                   <para>Further information about the current presence</para>
65                                 </option>
66                             <option name="message">
67                                   <para>A custom message that may indicate further details about the presence</para>
68                                 </option>
69                           </optionlist>
70                         </parameter>
71                         <parameter name="options" required="false">
72                           <optionlist>
73                             <option name="e">
74                                   <para>Base-64 encode the data.</para>
75                                 </option>
76                           </optionlist>
77                         </parameter>
78                 </syntax>
79                 <description>
80                         <para>The PRESENCE_STATE function can be used to retrieve the presence from any
81                         presence provider. For example:</para>
82                         <para>NoOp(SIP/mypeer has presence ${PRESENCE_STATE(SIP/mypeer,value)})</para>
83                         <para>NoOp(Conference number 1234 has presence message ${PRESENCE_STATE(MeetMe:1234,message)})</para>
84                         <para>The PRESENCE_STATE function can also be used to set custom presence state from
85                         the dialplan.  The <literal>CustomPresence:</literal> prefix must be used. For example:</para>
86                         <para>Set(PRESENCE_STATE(CustomPresence:lamp1)=away,temporary,Out to lunch)</para>
87                         <para>Set(PRESENCE_STATE(CustomPresence:lamp2)=dnd,,Trying to get work done)</para>
88                         <para>You can subscribe to the status of a custom presence state using a hint in
89                         the dialplan:</para>
90                         <para>exten => 1234,hint,CustomPresence:lamp1</para>
91                         <para>The possible values for both uses of this function are:</para>
92                         <para>not_set | unavailable | available | away | xa | chat | dnd</para>
93                 </description>
94         </function>
95  ***/
96
97
98 static const char astdb_family[] = "CustomPresence";
99
100 static int presence_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
101 {
102         int state;
103         char *message = NULL;
104         char *subtype = NULL;
105         char *parse;
106         int base64encode = 0;
107         AST_DECLARE_APP_ARGS(args,
108                 AST_APP_ARG(provider);
109                 AST_APP_ARG(field);
110                 AST_APP_ARG(options);
111         );
112
113         if (ast_strlen_zero(data)) {
114                 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires an argument \n");
115                 return -1;
116         }
117
118         parse = ast_strdupa(data);
119
120         AST_STANDARD_APP_ARGS(args, parse);
121
122         if (ast_strlen_zero(args.provider) || ast_strlen_zero(args.field)) {
123                 ast_log(LOG_WARNING, "PRESENCE_STATE reading requires both presence provider and presence field arguments. \n");
124                 return -1;
125         }
126
127         state = ast_presence_state_nocache(args.provider, &subtype, &message);
128         if (state == AST_PRESENCE_INVALID) {
129                 ast_log(LOG_WARNING, "PRESENCE_STATE unknown \n");
130                 return -1;
131         }
132
133         if (!(ast_strlen_zero(args.options)) && (strchr(args.options, 'e'))) {
134                 base64encode = 1;
135         }
136
137         if (!ast_strlen_zero(subtype) && !strcasecmp(args.field, "subtype")) {
138                 if (base64encode) {
139                         ast_base64encode(buf, (unsigned char *) subtype, strlen(subtype), len);
140                 } else {
141                         ast_copy_string(buf, subtype, len);
142                 }
143         } else if (!ast_strlen_zero(message) && !strcasecmp(args.field, "message")) {
144                 if (base64encode) {
145                         ast_base64encode(buf, (unsigned char *) message, strlen(message), len);
146                 } else {
147                         ast_copy_string(buf, message, len);
148                 }
149
150         } else if (!strcasecmp(args.field, "value")) {
151                 ast_copy_string(buf, ast_presence_state2str(state), len);
152         }
153
154         ast_free(message);
155         ast_free(subtype);
156
157         return 0;
158 }
159
160 static int parse_data(char *data, enum ast_presence_state *state, char **subtype, char **message, char **options)
161 {
162         char *state_str;
163
164         /* data syntax is state,subtype,message,options */
165         *subtype = "";
166         *message = "";
167         *options = "";
168
169         state_str = strsep(&data, ",");
170         if (ast_strlen_zero(state_str)) {
171                 return -1; /* state is required */
172         }
173
174         *state = ast_presence_state_val(state_str);
175
176         /* not a valid state */
177         if (*state == AST_PRESENCE_INVALID) {
178                 ast_log(LOG_WARNING, "Unknown presence state value %s\n", state_str);
179                 return -1;
180         }
181
182         if (!(*subtype = strsep(&data,","))) {
183                 *subtype = "";
184                 return 0;
185         }
186
187         if (!(*message = strsep(&data, ","))) {
188                 *message = "";
189                 return 0;
190         }
191
192         if (!(*options = strsep(&data, ","))) {
193                 *options = "";
194                 return 0;
195         }
196
197         if (!ast_strlen_zero(*options) && !(strchr(*options, 'e'))) {
198                 ast_log(LOG_NOTICE, "Invalid options  '%s'\n", *options);
199                 return -1;
200         }
201
202         return 0;
203 }
204
205 static int presence_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
206 {
207         size_t len = strlen("CustomPresence:");
208         char *tmp = data;
209         char *args = ast_strdupa(value);
210         enum ast_presence_state state;
211         char *options, *message, *subtype;
212
213         if (strncasecmp(data, "CustomPresence:", len)) {
214                 ast_log(LOG_WARNING, "The PRESENCE_STATE function can only set CustomPresence: presence providers.\n");
215                 return -1;
216         }
217         data += len;
218         if (ast_strlen_zero(data)) {
219                 ast_log(LOG_WARNING, "PRESENCE_STATE function called with no custom device name!\n");
220                 return -1;
221         }
222
223         if (parse_data(args, &state, &subtype, &message, &options)) {
224                 ast_log(LOG_WARNING, "Invalid arguments to PRESENCE_STATE\n");
225                 return -1;
226         }
227
228         ast_db_put(astdb_family, data, value);
229
230         ast_presence_state_changed_literal(state, subtype, message, tmp);
231
232         return 0;
233 }
234
235 static enum ast_presence_state custom_presence_callback(const char *data, char **subtype, char **message)
236 {
237         char buf[1301] = "";
238         enum ast_presence_state state;
239         char *_options;
240         char *_message;
241         char *_subtype;
242
243         ast_log(LOG_NOTICE, "TITTY BOMBS!\n");
244
245         ast_db_get(astdb_family, data, buf, sizeof(buf));
246
247         if (parse_data(buf, &state, &_subtype, &_message, &_options)) {
248                 return -1;
249         }
250
251         if ((strchr(_options, 'e'))) {
252                 char tmp[1301];
253                 if (ast_strlen_zero(_subtype)) {
254                         *subtype = NULL;
255                 } else {
256                         memset(tmp, 0, sizeof(tmp));
257                         ast_log(LOG_NOTICE, "Hey there, I'm doing some base64 decoding\n");
258                         ast_base64decode((unsigned char *) tmp, _subtype, sizeof(tmp) - 1);
259                         *subtype = ast_strdup(tmp);
260                 }
261
262                 if (ast_strlen_zero(_message)) {
263                         *message = NULL;
264                 } else {
265                         memset(tmp, 0, sizeof(tmp));
266                         ast_log(LOG_NOTICE, "Hey there, I'm doing some more base64 decoding\n");
267                         ast_base64decode((unsigned char *) tmp, _message, sizeof(tmp) - 1);
268                         *message = ast_strdup(tmp);
269                 }
270         } else {
271                 ast_log(LOG_NOTICE, "Not doing any base64 decoding\n");
272                 *subtype = ast_strlen_zero(_subtype) ? NULL : ast_strdup(_subtype);
273                 *message = ast_strlen_zero(_message) ? NULL : ast_strdup(_message);
274         }
275         return state;
276 }
277
278 static struct ast_custom_function presence_function = {
279         .name = "PRESENCE_STATE",
280         .read = presence_read,
281         .write = presence_write,
282 };
283
284 static char *handle_cli_presencestate_list(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
285 {
286         struct ast_db_entry *db_entry, *db_tree;
287
288         switch (cmd) {
289         case CLI_INIT:
290                 e->command = "presencestate list";
291                 e->usage =
292                         "Usage: presencestate list\n"
293                         "       List all custom presence states that have been set by using\n"
294                         "       the PRESENCE_STATE dialplan function.\n";
295                 return NULL;
296         case CLI_GENERATE:
297                 return NULL;
298         }
299
300         if (a->argc != e->args) {
301                 return CLI_SHOWUSAGE;
302         }
303
304         ast_cli(a->fd, "\n"
305                 "---------------------------------------------------------------------\n"
306                 "--- Custom Presence States ------------------------------------------\n"
307                 "---------------------------------------------------------------------\n"
308                 "---\n");
309
310         db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
311         if (!db_entry) {
312                 ast_cli(a->fd, "No custom presence states defined\n");
313                 return CLI_SUCCESS;
314         }
315         for (; db_entry; db_entry = db_entry->next) {
316                 const char *object_name = strrchr(db_entry->key, '/') + 1;
317                 char state_info[1301];
318                 enum ast_presence_state state;
319                 char *subtype;
320                 char *message;
321                 char *options;
322
323                 ast_copy_string(state_info, db_entry->data, sizeof(state_info));
324                 if (parse_data(state_info, &state, &subtype, &message, &options)) {
325                         ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
326                         continue;
327                 }
328
329                 if (object_name <= (const char *) 1) {
330                         continue;
331                 }
332                 ast_cli(a->fd, "--- Name: 'CustomPresence:%s'\n"
333                                        "    --- State: '%s'\n"
334                                            "    --- Subtype: '%s'\n"
335                                            "    --- Message: '%s'\n"
336                                            "    --- Base64 Encoded: '%s'\n"
337                                "---\n",
338                                            object_name,
339                                            ast_presence_state2str(state),
340                                            subtype,
341                                            message,
342                                            AST_CLI_YESNO(strchr(options, 'e')));
343         }
344         ast_db_freetree(db_tree);
345         db_tree = NULL;
346
347         ast_cli(a->fd,
348                 "---------------------------------------------------------------------\n"
349                 "---------------------------------------------------------------------\n"
350                 "\n");
351
352         return CLI_SUCCESS;
353 }
354
355 static char *handle_cli_presencestate_change(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
356 {
357     size_t len;
358         const char *dev, *state, *full_dev;
359         enum ast_presence_state state_val;
360         char *message;
361         char *subtype;
362         char *options;
363         char *args;
364
365         switch (cmd) {
366         case CLI_INIT:
367                 e->command = "presencestate change";
368                 e->usage =
369                         "Usage: presencestate change <entity> <state>[,<subtype>[,message[,options]]]\n"
370                         "       Change a custom presence to a new state.\n"
371                         "       The possible values for the state are:\n"
372                         "NOT_SET | UNAVAILABLE | AVAILABLE | AWAY | XA | CHAT | DND\n"
373                         "Optionally, a custom subtype and message may be provided, along with any options\n"
374                         "accepted by func_presencestate. If the subtype or message provided contain spaces,\n"
375                         "be sure to enclose the data in quotation marks (\"\")\n"
376                         "\n"
377                         "Examples:\n"
378                         "       presencestate change CustomPresence:mystate1 AWAY\n"
379                         "       presencestate change CustomPresence:mystate1 AVAILABLE\n"
380                         "       presencestate change CustomPresence:mystate1 \"Away,upstairs,eating lunch\"\n"
381                         "       \n";
382                 return NULL;
383         case CLI_GENERATE:
384         {
385                 static const char * const cmds[] = { "NOT_SET", "UNAVAILABLE", "AVAILABLE", "AWAY",
386                                                      "XA", "CHAT", "DND", NULL };
387
388                 if (a->pos == e->args + 1) {
389                         return ast_cli_complete(a->word, cmds, a->n);
390                 }
391
392                 return NULL;
393         }
394         }
395
396         if (a->argc != e->args + 2) {
397                 return CLI_SHOWUSAGE;
398         }
399
400         len = strlen("CustomPresence:");
401         full_dev = dev = a->argv[e->args];
402         state = a->argv[e->args + 1];
403
404         if (strncasecmp(dev, "CustomPresence:", len)) {
405                 ast_cli(a->fd, "The presencestate command can only be used to set 'CustomPresence:' presence state!\n");
406                 return CLI_FAILURE;
407         }
408
409         dev += len;
410         if (ast_strlen_zero(dev)) {
411                 return CLI_SHOWUSAGE;
412         }
413
414         args = ast_strdupa(state);
415         if (parse_data(args, &state_val, &subtype, &message, &options)) {
416                 return CLI_SHOWUSAGE;
417         }
418
419         if (state_val == AST_PRESENCE_NOT_SET) {
420                 return CLI_SHOWUSAGE;
421         }
422
423         ast_cli(a->fd, "Changing %s to %s\n", dev, args);
424
425         ast_db_put(astdb_family, dev, state);
426
427         ast_presence_state_changed_literal(state_val, subtype, message, full_dev);
428
429         return CLI_SUCCESS;
430 }
431
432 static struct ast_cli_entry cli_funcpresencestate[] = {
433         AST_CLI_DEFINE(handle_cli_presencestate_list, "List currently know custom presence states"),
434         AST_CLI_DEFINE(handle_cli_presencestate_change, "Change a custom presence state"),
435 };
436
437 #ifdef TEST_FRAMEWORK
438
439 struct test_string {
440         char *parse_string;
441         struct {
442                 int value;
443                 const char *subtype;
444                 const char *message;
445                 const char *options; 
446         } outputs;
447 };
448
449 AST_TEST_DEFINE(test_valid_parse_data)
450 {
451         int i;
452         enum ast_presence_state state;
453         char *subtype;
454         char *message;
455         char *options;
456         enum ast_test_result_state res = AST_TEST_PASS;
457         
458         struct test_string tests [] = {
459                 { "away",
460                         { AST_PRESENCE_AWAY,
461                                 "",
462                                 "",
463                                 ""
464                         }
465                 },
466                 { "not_set",
467                         { AST_PRESENCE_NOT_SET,
468                                 "",
469                                 "",
470                                 ""
471                         }
472                 },
473                 { "unavailable",
474                         { AST_PRESENCE_UNAVAILABLE,
475                                 "",
476                                 "",
477                                 ""
478                         }
479                 },
480                 { "available",
481                         { AST_PRESENCE_AVAILABLE,
482                                 "",
483                                 "",
484                                 ""
485                         }
486                 },
487                 { "xa",
488                         { AST_PRESENCE_XA,
489                                 "",
490                                 "",
491                                 ""
492                         }
493                 },
494                 { "chat",
495                         { AST_PRESENCE_CHAT,
496                                 "",
497                                 "",
498                                 ""
499                         }
500                 },
501                 { "dnd",
502                         { AST_PRESENCE_DND,
503                                 "",
504                                 "",
505                                 ""
506                         }
507                 },
508                 { "away,down the hall",
509                         { AST_PRESENCE_AWAY,
510                                 "down the hall",
511                                 "",
512                                 ""
513                         }
514                 },
515                 { "away,down the hall,Quarterly financial meeting",
516                         { AST_PRESENCE_AWAY,
517                                 "down the hall",
518                                 "Quarterly financial meeting",
519                                 ""
520                         }
521                 },
522                 { "away,,Quarterly financial meeting",
523                         { AST_PRESENCE_AWAY,
524                                 "",
525                                 "Quarterly financial meeting",
526                                 ""
527                         }
528                 },
529                 { "away,,,e",
530                         { AST_PRESENCE_AWAY,
531                                 "",
532                                 "",
533                                 "e",
534                         }
535                 },
536                 { "away,down the hall,,e",
537                         { AST_PRESENCE_AWAY,
538                                 "down the hall",
539                                 "",
540                                 "e"
541                         }
542                 },
543                 { "away,down the hall,Quarterly financial meeting,e",
544                         { AST_PRESENCE_AWAY,
545                                 "down the hall",
546                                 "Quarterly financial meeting",
547                                 "e"
548                         }
549                 },
550                 { "away,,Quarterly financial meeting,e",
551                         { AST_PRESENCE_AWAY,
552                                 "",
553                                 "Quarterly financial meeting",
554                                 "e"
555                         }
556                 }
557         };
558
559         switch (cmd) {
560         case TEST_INIT:
561                 info->name = "parse_valid_presence_data";
562                 info->category = "/funcs/func_presence/";
563                 info->summary = "PRESENCESTATE parsing test";
564                 info->description =
565                         "Ensure that parsing function accepts proper values, and gives proper outputs";
566                 return AST_TEST_NOT_RUN;
567         case TEST_EXECUTE:
568                 break;
569         }
570
571         for (i = 0; i < ARRAY_LEN(tests); ++i) {
572                 int parse_result;
573                 char *parse_string = ast_strdup(tests[i].parse_string);
574                 if (!parse_string) {
575                         res = AST_TEST_FAIL;
576                         break;
577                 }
578                 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
579                 if (parse_result == -1) {
580                         res = AST_TEST_FAIL;
581                         ast_free(parse_string);
582                         break;
583                 }
584                 if (tests[i].outputs.value != state ||
585                                 strcmp(tests[i].outputs.subtype, subtype) ||
586                                 strcmp(tests[i].outputs.message, message) ||
587                                 strcmp(tests[i].outputs.options, options)) {
588                         res = AST_TEST_FAIL;
589                         ast_free(parse_string);
590                         break;
591                 }
592                 ast_free(parse_string);
593         }
594
595         return res;
596 }
597
598 AST_TEST_DEFINE(test_invalid_parse_data)
599 {
600         int i;
601         enum ast_presence_state state;
602         char *subtype;
603         char *message;
604         char *options;
605         enum ast_test_result_state res = AST_TEST_PASS;
606
607         char *tests[] = {
608                 "",
609                 "bored",
610                 "away,,,i",
611                 /* XXX The following actually is parsed correctly. Should that
612                  * be changed?
613                  * "away,,,,e",
614                  */
615         };
616
617         switch (cmd) {
618         case TEST_INIT:
619                 info->name = "parse_invalid_presence_data";
620                 info->category = "/funcs/func_presence/";
621                 info->summary = "PRESENCESTATE parsing test";
622                 info->description =
623                         "Ensure that parsing function rejects improper values";
624                 return AST_TEST_NOT_RUN;
625         case TEST_EXECUTE:
626                 break;
627         }
628
629         for (i = 0; i < ARRAY_LEN(tests); ++i) {
630                 int parse_result;
631                 char *parse_string = ast_strdup(tests[i]);
632                 if (!parse_string) {
633                         res = AST_TEST_FAIL;
634                         break;
635                 }
636                 parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
637                 if (parse_result == 0) {
638                         ast_log(LOG_WARNING, "Invalid string parsing failed on %s\n", tests[i]);
639                         res = AST_TEST_FAIL;
640                         ast_free(parse_string);
641                         break;
642                 }
643                 ast_free(parse_string);
644         }
645
646         return res;
647 }
648
649 struct test_cb_data {
650         enum ast_presence_state presence;
651         const char *provider;
652         const char *subtype;
653         const char *message;
654         /* That's right. I'm using a semaphore */
655         sem_t sem;
656 };
657
658 static void test_cb(const struct ast_event *event, void *userdata)
659 {
660         struct test_cb_data *cb_data = userdata;
661         cb_data->presence = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
662         cb_data->provider = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
663         cb_data->subtype = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE));
664         cb_data->message = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE));
665         sem_post(&cb_data->sem);
666 }
667
668 /* XXX This test could probably stand to be moved since
669  * it does not test func_presencestate but rather code in
670  * presencestate.h and presencestate.c. However, the convenience
671  * of presence_write() makes this a nice location for this test.
672  */
673 AST_TEST_DEFINE(test_presence_state_change)
674 {
675         struct ast_event_sub *test_sub;
676         struct test_cb_data *cb_data;
677
678         switch (cmd) {
679         case TEST_INIT:
680                 info->name = "test_presence_state_change";
681                 info->category = "/funcs/func_presence/";
682                 info->summary = "presence state change subscription";
683                 info->description =
684                         "Ensure that presence state changes are communicated to subscribers";
685                 return AST_TEST_NOT_RUN;
686         case TEST_EXECUTE:
687                 break;
688         }
689
690         cb_data = ast_calloc(1, sizeof(*cb_data));
691         if (!cb_data) {
692                 return AST_TEST_FAIL;
693         }
694
695         if (!(test_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE,
696                         test_cb, "Test presence state callbacks", cb_data, AST_EVENT_IE_END))) {
697                 return AST_TEST_FAIL;
698         }
699
700         if (sem_init(&cb_data->sem, 0, 0)) {
701                 return AST_TEST_FAIL;
702         }
703
704         presence_write(NULL, "PRESENCESTATE", "CustomPresence:TestPresenceStateChange", "away,down the hall,Quarterly financial meeting");
705         sem_wait(&cb_data->sem);
706         if (cb_data->presence != AST_PRESENCE_AWAY ||
707                         strcmp(cb_data->provider, "CustomPresence:TestPresenceStateChange") ||
708                         strcmp(cb_data->subtype, "down the hall") ||
709                         strcmp(cb_data->message, "Quarterly financial meeting")) {
710                 return AST_TEST_FAIL;
711         }
712
713         ast_free((char *)cb_data->provider);
714         ast_free((char *)cb_data->subtype);
715         ast_free((char *)cb_data->message);
716         ast_free((char *)cb_data);
717
718         ast_db_del("CustomPresence", "TestPresenceStateChange");
719
720         return AST_TEST_PASS;
721 }
722
723 #endif
724
725 static int unload_module(void)
726 {
727         int res = 0;
728
729         res |= ast_custom_function_unregister(&presence_function);
730         res |= ast_presence_state_prov_del("CustomPresence");
731         res |= ast_cli_unregister_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
732 #ifdef TEST_FRAMEWORK
733         AST_TEST_UNREGISTER(test_valid_parse_data);
734         AST_TEST_UNREGISTER(test_invalid_parse_data);
735         AST_TEST_UNREGISTER(test_presence_state_change);
736 #endif
737         return res;
738 }
739
740 static int load_module(void)
741 {
742         int res = 0;
743         struct ast_db_entry *db_entry, *db_tree;
744
745         /* Populate the presence state cache on the system with all of the currently
746          * known custom presence states. */
747         db_entry = db_tree = ast_db_gettree(astdb_family, NULL);
748         for (; db_entry; db_entry = db_entry->next) {
749                 const char *dev_name = strrchr(db_entry->key, '/') + 1;
750                 char state_info[1301];
751                 enum ast_presence_state state;
752                 char *message;
753                 char *subtype;
754                 char *options;
755                 if (dev_name <= (const char *) 1) {
756                         continue;
757                 }
758                 ast_copy_string(state_info, db_entry->data, sizeof(state_info));
759                 if (parse_data(state_info, &state, &subtype, &message, &options)) {
760                         ast_log(LOG_WARNING, "Invalid CustomPresence entry %s encountered\n", db_entry->data);
761                         continue;
762                 }
763                 ast_presence_state_changed(state, subtype, message, "CustomPresence:%s", dev_name);
764         }
765         ast_db_freetree(db_tree);
766         db_tree = NULL;
767
768         res |= ast_custom_function_register(&presence_function);
769         res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
770         res |= ast_cli_register_multiple(cli_funcpresencestate, ARRAY_LEN(cli_funcpresencestate));
771 #ifdef TEST_FRAMEWORK
772         AST_TEST_REGISTER(test_valid_parse_data);
773         AST_TEST_REGISTER(test_invalid_parse_data);
774         AST_TEST_REGISTER(test_presence_state_change);
775 #endif
776
777         return res;
778 }
779
780 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Gets or sets a presence state in the dialplan",
781         .load = load_module,
782         .unload = unload_module,
783         .load_pri = AST_MODPRI_DEVSTATE_PROVIDER,
784 );
785