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