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