Merge "res_odbc: fix missing SQL error diagnostic"
[asterisk/asterisk.git] / res / res_mwi_external.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2013, Digium, Inc.
5  *
6  * Richard Mudgett <rmudgett@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 /*!
20  * \file
21  * \brief Core external MWI support.
22  *
23  * \details
24  * The module manages the persistent message counts cache and supplies
25  * an API to allow the protocol specific modules to control the counts
26  * or a subset.
27  *
28  * \author Richard Mudgett <rmudgett@digium.com>
29  *
30  * See Also:
31  * \arg \ref AstCREDITS
32  */
33
34 /*** MODULEINFO
35         <defaultenabled>no</defaultenabled>
36         <support_level>core</support_level>
37  ***/
38
39 /*** DOCUMENTATION
40         <configInfo name="res_mwi_external" language="en_US">
41                 <synopsis>Core external MWI support</synopsis>
42                 <configFile name="sorcery.conf">
43                         <configObject name="mailboxes">
44                                 <synopsis>Persistent cache of external MWI Mailboxs.</synopsis>
45                                 <description>
46                                         <para>Allows the alteration of sorcery backend mapping for
47                                         the persistent cache of external MWI mailboxes.</para>
48                                 </description>
49                         </configObject>
50                 </configFile>
51         </configInfo>
52  ***/
53
54
55 #include "asterisk.h"
56
57 #include "asterisk/app.h"
58 #include "asterisk/module.h"
59 #include "asterisk/res_mwi_external.h"
60 #include "asterisk/sorcery.h"
61 #include "asterisk/cli.h"
62
63 /* ------------------------------------------------------------------- */
64
65 /*!
66  * Define to include CLI commands to manipulate the external MWI mailboxes.
67  * Useful for testing the module functionality.
68  */
69 //#define MWI_DEBUG_CLI         1
70
71 #define MWI_ASTDB_PREFIX        "mwi_external"
72 #define MWI_MAILBOX_TYPE        "mailboxes"
73
74 struct ast_mwi_mailbox_object {
75         SORCERY_OBJECT(details);
76         /*! Number of new messages in mailbox. */
77         unsigned int msgs_new;
78         /*! Number of old messages in mailbox. */
79         unsigned int msgs_old;
80 };
81
82 static struct ast_sorcery *mwi_sorcery;
83
84 /*!
85  * \internal
86  * \brief Post an update event to the MWI counts.
87  * \since 12.1.0
88  *
89  * \return Nothing
90  */
91 static void mwi_post_event(const struct ast_mwi_mailbox_object *mailbox)
92 {
93         ast_publish_mwi_state(ast_sorcery_object_get_id(mailbox), NULL,
94                 mailbox->msgs_new, mailbox->msgs_old);
95 }
96
97 static void mwi_observe_update(const void *obj)
98 {
99         mwi_post_event(obj);
100 }
101
102 /*!
103  * \internal
104  * \brief Post a count clearing event to the MWI counts.
105  * \since 12.1.0
106  *
107  * \return Nothing
108  */
109 static void mwi_observe_delete(const void *obj)
110 {
111         const struct ast_mwi_mailbox_object *mailbox = obj;
112
113         if (mailbox->msgs_new || mailbox->msgs_old) {
114                 /* Post a count clearing event. */
115                 ast_publish_mwi_state(ast_sorcery_object_get_id(mailbox), NULL, 0, 0);
116         }
117
118         /* Post a cache remove event. */
119         ast_delete_mwi_state(ast_sorcery_object_get_id(mailbox), NULL);
120 }
121
122 static const struct ast_sorcery_observer mwi_observers = {
123         .created = mwi_observe_update,
124         .updated = mwi_observe_update,
125         .deleted = mwi_observe_delete,
126 };
127
128 /*! \brief Internal function to allocate a mwi object */
129 static void *mwi_sorcery_object_alloc(const char *id)
130 {
131         return ast_sorcery_generic_alloc(sizeof(struct ast_mwi_mailbox_object), NULL);
132 }
133
134 /*!
135  * \internal
136  * \brief Initialize sorcery for external MWI.
137  * \since 12.1.0
138  *
139  * \retval 0 on success.
140  * \retval -1 on error.
141  */
142 static int mwi_sorcery_init(void)
143 {
144         int res;
145
146         mwi_sorcery = ast_sorcery_open();
147         if (!mwi_sorcery) {
148                 ast_log(LOG_ERROR, "MWI external: Sorcery failed to open.\n");
149                 return -1;
150         }
151
152         /* Map the external MWI wizards. */
153         if (ast_sorcery_apply_default(mwi_sorcery, MWI_MAILBOX_TYPE, "astdb",
154                         MWI_ASTDB_PREFIX) == AST_SORCERY_APPLY_FAIL) {
155                 ast_log(LOG_ERROR, "MWI external: Sorcery could not setup wizards.\n");
156                 return -1;
157         }
158
159         res = ast_sorcery_object_register(mwi_sorcery, MWI_MAILBOX_TYPE,
160                 mwi_sorcery_object_alloc, NULL, NULL);
161         if (res) {
162                 ast_log(LOG_ERROR, "MWI external: Sorcery could not register object type '%s'.\n",
163                         MWI_MAILBOX_TYPE);
164                 return -1;
165         }
166
167         /* Define the MWI_MAILBOX_TYPE object fields. */
168         res |= ast_sorcery_object_field_register_nodoc(mwi_sorcery, MWI_MAILBOX_TYPE,
169                 "msgs_new", "0", OPT_UINT_T, 0, FLDSET(struct ast_mwi_mailbox_object, msgs_new));
170         res |= ast_sorcery_object_field_register_nodoc(mwi_sorcery, MWI_MAILBOX_TYPE,
171                 "msgs_old", "0", OPT_UINT_T, 0, FLDSET(struct ast_mwi_mailbox_object, msgs_old));
172         return res ? -1 : 0;
173 }
174
175 struct ao2_container *ast_mwi_mailbox_get_all(void)
176 {
177         return ast_sorcery_retrieve_by_fields(mwi_sorcery, MWI_MAILBOX_TYPE,
178                 AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
179 }
180
181 struct ao2_container *ast_mwi_mailbox_get_by_regex(const char *regex)
182 {
183         return ast_sorcery_retrieve_by_regex(mwi_sorcery, MWI_MAILBOX_TYPE, regex ?: "");
184 }
185
186 const struct ast_mwi_mailbox_object *ast_mwi_mailbox_get(const char *mailbox_id)
187 {
188         if (ast_strlen_zero(mailbox_id)) {
189                 return NULL;
190         }
191
192         return ast_sorcery_retrieve_by_id(mwi_sorcery, MWI_MAILBOX_TYPE, mailbox_id);
193 }
194
195 struct ast_mwi_mailbox_object *ast_mwi_mailbox_alloc(const char *mailbox_id)
196 {
197         if (ast_strlen_zero(mailbox_id)) {
198                 return NULL;
199         }
200
201         return ast_sorcery_alloc(mwi_sorcery, MWI_MAILBOX_TYPE, mailbox_id);
202 }
203
204 struct ast_mwi_mailbox_object *ast_mwi_mailbox_copy(const struct ast_mwi_mailbox_object *mailbox)
205 {
206         return ast_sorcery_copy(mwi_sorcery, mailbox);
207 }
208
209 const char *ast_mwi_mailbox_get_id(const struct ast_mwi_mailbox_object *mailbox)
210 {
211         return ast_sorcery_object_get_id(mailbox);
212 }
213
214 unsigned int ast_mwi_mailbox_get_msgs_new(const struct ast_mwi_mailbox_object *mailbox)
215 {
216         return mailbox->msgs_new;
217 }
218
219 unsigned int ast_mwi_mailbox_get_msgs_old(const struct ast_mwi_mailbox_object *mailbox)
220 {
221         return mailbox->msgs_old;
222 }
223
224 void ast_mwi_mailbox_set_msgs_new(struct ast_mwi_mailbox_object *mailbox, unsigned int num_msgs)
225 {
226         mailbox->msgs_new = num_msgs;
227 }
228
229 void ast_mwi_mailbox_set_msgs_old(struct ast_mwi_mailbox_object *mailbox, unsigned int num_msgs)
230 {
231         mailbox->msgs_old = num_msgs;
232 }
233
234 int ast_mwi_mailbox_update(struct ast_mwi_mailbox_object *mailbox)
235 {
236         const struct ast_mwi_mailbox_object *exists;
237         int res;
238
239         exists = ast_sorcery_retrieve_by_id(mwi_sorcery, MWI_MAILBOX_TYPE,
240                 ast_sorcery_object_get_id(mailbox));
241         if (exists) {
242                 res = ast_sorcery_update(mwi_sorcery, mailbox);
243                 ast_mwi_mailbox_unref(exists);
244         } else {
245                 res = ast_sorcery_create(mwi_sorcery, mailbox);
246         }
247         return res;
248 }
249
250 /*!
251  * \internal
252  * \brief Delete a mailbox.
253  * \since 12.1.0
254  *
255  * \param mailbox Mailbox object to delete from sorcery.
256  *
257  * \return Nothing
258  */
259 static void mwi_mailbox_delete(struct ast_mwi_mailbox_object *mailbox)
260 {
261         ast_sorcery_delete(mwi_sorcery, mailbox);
262 }
263
264 /*!
265  * \internal
266  * \brief Delete all mailboxes in container.
267  * \since 12.1.0
268  *
269  * \param mailboxes Mailbox objects to delete from sorcery.
270  *
271  * \return Nothing
272  */
273 static void mwi_mailbox_delete_all(struct ao2_container *mailboxes)
274 {
275         struct ast_mwi_mailbox_object *mailbox;
276         struct ao2_iterator iter;
277
278         iter = ao2_iterator_init(mailboxes, AO2_ITERATOR_UNLINK);
279         for (; (mailbox = ao2_iterator_next(&iter)); ast_mwi_mailbox_unref(mailbox)) {
280                 mwi_mailbox_delete(mailbox);
281         }
282         ao2_iterator_destroy(&iter);
283 }
284
285 int ast_mwi_mailbox_delete_all(void)
286 {
287         struct ao2_container *mailboxes;
288
289         mailboxes = ast_mwi_mailbox_get_all();
290         if (mailboxes) {
291                 mwi_mailbox_delete_all(mailboxes);
292                 ao2_ref(mailboxes, -1);
293         }
294         return 0;
295 }
296
297 int ast_mwi_mailbox_delete_by_regex(const char *regex)
298 {
299         struct ao2_container *mailboxes;
300
301         mailboxes = ast_mwi_mailbox_get_by_regex(regex);
302         if (mailboxes) {
303                 mwi_mailbox_delete_all(mailboxes);
304                 ao2_ref(mailboxes, -1);
305         }
306         return 0;
307 }
308
309 int ast_mwi_mailbox_delete(const char *mailbox_id)
310 {
311         const struct ast_mwi_mailbox_object *mailbox;
312
313         if (ast_strlen_zero(mailbox_id)) {
314                 return -1;
315         }
316
317         mailbox = ast_mwi_mailbox_get(mailbox_id);
318         if (mailbox) {
319                 mwi_mailbox_delete((struct ast_mwi_mailbox_object *) mailbox);
320                 ast_mwi_mailbox_unref(mailbox);
321         }
322         return 0;
323 }
324
325 enum folder_map {
326         FOLDER_INVALID = 0,
327         FOLDER_INBOX = 1,
328         FOLDER_OLD = 2,
329 };
330
331 /*!
332  * \internal
333  * \brief Determine if the requested folder is valid for external MWI support.
334  * \since 12.1.0
335  *
336  * \param folder Folder name to check (NULL is valid).
337  *
338  * \return Enum of the supported folder.
339  */
340 static enum folder_map mwi_folder_map(const char *folder)
341 {
342         enum folder_map which_folder;
343
344         if (ast_strlen_zero(folder) || !strcasecmp(folder, "INBOX")) {
345                 which_folder = FOLDER_INBOX;
346         } else if (!strcasecmp(folder, "Old")) {
347                 which_folder = FOLDER_OLD;
348         } else {
349                 which_folder = FOLDER_INVALID;
350         }
351         return which_folder;
352 }
353
354 /*!
355  * \internal
356  * \brief Gets the number of messages that exist in a mailbox folder.
357  * \since 12.1.0
358  *
359  * \param mailbox_id The mailbox name.
360  * \param folder The folder to look in.  Default is INBOX if not provided.
361  *
362  * \return The number of messages in the mailbox folder (zero or more).
363  */
364 static int mwi_messagecount(const char *mailbox_id, const char *folder)
365 {
366         const struct ast_mwi_mailbox_object *mailbox;
367         int num_msgs;
368         enum folder_map which_folder;
369
370         which_folder = mwi_folder_map(folder);
371         if (which_folder == FOLDER_INVALID) {
372                 return 0;
373         }
374
375         mailbox = ast_mwi_mailbox_get(mailbox_id);
376         if (!mailbox) {
377                 return 0;
378         }
379         num_msgs = 0;
380         switch (which_folder) {
381         case FOLDER_INVALID:
382                 break;
383         case FOLDER_INBOX:
384                 num_msgs = mailbox->msgs_new;
385                 break;
386         case FOLDER_OLD:
387                 num_msgs = mailbox->msgs_old;
388                 break;
389         }
390         ast_mwi_mailbox_unref(mailbox);
391
392         return num_msgs;
393 }
394
395 /*!
396  * \internal
397  * \brief Determines if the given folder has messages.
398  * \since 12.1.0
399  *
400  * \param mailboxes Comma or & delimited list of mailboxes.
401  * \param folder The folder to look in.  Default is INBOX if not provided.
402  *
403  * \retval 1 if the folder has one or more messages.
404  * \retval 0 otherwise.
405  */
406 static int mwi_has_voicemail(const char *mailboxes, const char *folder)
407 {
408         char *parse;
409         char *mailbox_id;
410         enum folder_map which_folder;
411
412         which_folder = mwi_folder_map(folder);
413         if (which_folder == FOLDER_INVALID) {
414                 return 0;
415         }
416
417         /* For each mailbox in the list. */
418         parse = ast_strdupa(mailboxes);
419         while ((mailbox_id = strsep(&parse, ",&"))) {
420                 const struct ast_mwi_mailbox_object *mailbox;
421                 int num_msgs;
422
423                 /* Get the specified mailbox. */
424                 mailbox = ast_mwi_mailbox_get(mailbox_id);
425                 if (!mailbox) {
426                         continue;
427                 }
428
429                 /* Done if the found mailbox has any messages. */
430                 num_msgs = 0;
431                 switch (which_folder) {
432                 case FOLDER_INVALID:
433                         break;
434                 case FOLDER_INBOX:
435                         num_msgs = mailbox->msgs_new;
436                         break;
437                 case FOLDER_OLD:
438                         num_msgs = mailbox->msgs_old;
439                         break;
440                 }
441                 ast_mwi_mailbox_unref(mailbox);
442                 if (num_msgs) {
443                         return 1;
444                 }
445         }
446
447         return 0;
448 }
449
450 /*!
451  * \internal
452  * \brief Gets the number of messages that exist for the mailbox list.
453  * \since 12.1.0
454  *
455  * \param mailboxes Comma or space delimited list of mailboxes.
456  * \param newmsgs Where to put the count of new messages. (Can be NULL)
457  * \param oldmsgs Where to put the count of old messages. (Can be NULL)
458  *
459  * \details
460  * Simultaneously determines the count of new and old
461  * messages.  The total messages would then be the sum of these.
462  *
463  * \retval 0 on success
464  * \retval -1 on failure
465  */
466 static int mwi_inboxcount(const char *mailboxes, int *newmsgs, int *oldmsgs)
467 {
468         char *parse;
469         char *mailbox_id;
470
471         if (!newmsgs && !oldmsgs) {
472                 /* Nowhere to accumulate counts */
473                 return 0;
474         }
475
476         /* For each mailbox in the list. */
477         parse = ast_strdupa(mailboxes);
478         while ((mailbox_id = strsep(&parse, ", "))) {
479                 const struct ast_mwi_mailbox_object *mailbox;
480
481                 /* Get the specified mailbox. */
482                 mailbox = ast_mwi_mailbox_get(mailbox_id);
483                 if (!mailbox) {
484                         continue;
485                 }
486
487                 /* Accumulate the counts. */
488                 if (newmsgs) {
489                         *newmsgs += mailbox->msgs_new;
490                 }
491                 if (oldmsgs) {
492                         *oldmsgs += mailbox->msgs_old;
493                 }
494
495                 ast_mwi_mailbox_unref(mailbox);
496         }
497
498         return 0;
499 }
500
501 /*!
502  * \internal
503  * \brief Gets the number of messages that exist for the mailbox list.
504  * \since 12.1.0
505  *
506  * \param mailboxes Comma or space delimited list of mailboxes.
507  * \param urgentmsgs Where to put the count of urgent messages. (Can be NULL)
508  * \param newmsgs Where to put the count of new messages. (Can be NULL)
509  * \param oldmsgs Where to put the count of old messages. (Can be NULL)
510  *
511  * \details
512  * Simultaneously determines the count of new, old, and urgent
513  * messages.  The total messages would then be the sum of these
514  * three.
515  *
516  * \retval 0 on success
517  * \retval -1 on failure
518  */
519 static int mwi_inboxcount2(const char *mailboxes, int *urgentmsgs, int *newmsgs, int *oldmsgs)
520 {
521         /*
522          * This module does not support urgentmsgs.  Just ignore them.
523          * The global API call has already set the count to zero.
524          */
525         return mwi_inboxcount(mailboxes, newmsgs, oldmsgs);
526 }
527
528 static const struct ast_vm_functions vm_table = {
529         .module_version = VM_MODULE_VERSION,
530         .module_name = AST_MODULE,
531
532         .has_voicemail = mwi_has_voicemail,
533         .inboxcount = mwi_inboxcount,
534         .inboxcount2 = mwi_inboxcount2,
535         .messagecount = mwi_messagecount,
536 };
537
538 #if defined(MWI_DEBUG_CLI)
539 static char *complete_mailbox(const char *word, int state)
540 {
541         struct ao2_iterator iter;
542         int wordlen = strlen(word);
543         int which = 0;
544         char *ret = NULL;
545         char *regex;
546         const struct ast_mwi_mailbox_object *mailbox;
547         RAII_VAR(struct ao2_container *, mailboxes, NULL, ao2_cleanup);
548
549         regex = ast_alloca(2 + wordlen);
550         sprintf(regex, "^%s", word);/* Safe */
551
552         mailboxes = ast_mwi_mailbox_get_by_regex(regex);
553         if (!mailboxes) {
554                 return NULL;
555         }
556
557         iter = ao2_iterator_init(mailboxes, 0);
558         for (; (mailbox = ao2_iterator_next(&iter)); ast_mwi_mailbox_unref(mailbox)) {
559                 if (++which > state) {
560                         ret = ast_strdup(ast_sorcery_object_get_id(mailbox));
561                         ast_mwi_mailbox_unref(mailbox);
562                         break;
563                 }
564         }
565         ao2_iterator_destroy(&iter);
566
567         return ret;
568 }
569 #endif  /* defined(MWI_DEBUG_CLI) */
570
571 #if defined(MWI_DEBUG_CLI)
572 static char *handle_mwi_delete_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
573 {
574         switch (cmd) {
575         case CLI_INIT:
576                 e->command = "mwi delete all";
577                 e->usage =
578                         "Usage: mwi delete all\n"
579                         "       Delete all external MWI mailboxes.\n";
580                 return NULL;
581         case CLI_GENERATE:
582                 return NULL;
583         }
584
585         ast_mwi_mailbox_delete_all();
586         ast_cli(a->fd, "Deleted all external MWI mailboxes.\n");
587         return CLI_SUCCESS;
588 }
589 #endif  /* defined(MWI_DEBUG_CLI) */
590
591 #if defined(MWI_DEBUG_CLI)
592 static char *handle_mwi_delete_like(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
593 {
594         const char *regex;
595
596         switch (cmd) {
597         case CLI_INIT:
598                 e->command = "mwi delete like";
599                 e->usage =
600                         "Usage: mwi delete like <pattern>\n"
601                         "       Delete external MWI mailboxes matching a regular expression.\n";
602                 return NULL;
603         case CLI_GENERATE:
604                 return NULL;
605         }
606
607         if (a->argc != 4) {
608                 return CLI_SHOWUSAGE;
609         }
610         regex = a->argv[3];
611
612         ast_mwi_mailbox_delete_by_regex(regex);
613         ast_cli(a->fd, "Deleted external MWI mailboxes matching '%s'.\n", regex);
614         return CLI_SUCCESS;
615 }
616 #endif  /* defined(MWI_DEBUG_CLI) */
617
618 #if defined(MWI_DEBUG_CLI)
619 static char *handle_mwi_delete_mailbox(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
620 {
621         const char *mailbox_id;
622
623         switch (cmd) {
624         case CLI_INIT:
625                 e->command = "mwi delete mailbox";
626                 e->usage =
627                         "Usage: mwi delete mailbox <mailbox_id>\n"
628                         "       Delete a specific external MWI mailbox.\n";
629                 return NULL;
630         case CLI_GENERATE:
631                 if (a->pos == 3) {
632                         return complete_mailbox(a->word, a->n);
633                 }
634                 return NULL;
635         }
636
637         if (a->argc != 4) {
638                 return CLI_SHOWUSAGE;
639         }
640         mailbox_id = a->argv[3];
641
642         ast_mwi_mailbox_delete(mailbox_id);
643         ast_cli(a->fd, "Deleted external MWI mailbox '%s'.\n", mailbox_id);
644
645         return CLI_SUCCESS;
646 }
647 #endif  /* defined(MWI_DEBUG_CLI) */
648
649 #define FORMAT_MAILBOX_HDR "%6s %6s %s\n"
650 #define FORMAT_MAILBOX_ROW "%6u %6u %s\n"
651
652 #if defined(MWI_DEBUG_CLI)
653 /*!
654  * \internal
655  * \brief Print a mailbox list line to CLI.
656  * \since 12.1.0
657  *
658  * \param cli_fd File descriptor for CLI output.
659  * \param mailbox What to list.
660  *
661  * \return Nothing
662  */
663 static void mwi_cli_print_mailbox(int cli_fd, const struct ast_mwi_mailbox_object *mailbox)
664 {
665         ast_cli(cli_fd, FORMAT_MAILBOX_ROW, mailbox->msgs_new, mailbox->msgs_old,
666                 ast_sorcery_object_get_id(mailbox));
667 }
668 #endif  /* defined(MWI_DEBUG_CLI) */
669
670 #if defined(MWI_DEBUG_CLI)
671 /*!
672  * \internal
673  * \brief List all mailboxes in the given container.
674  * \since 12.1.0
675  *
676  * \param cli_fd File descriptor for CLI output.
677  * \param mailboxes What to list.
678  *
679  * \return Nothing
680  */
681 static void mwi_cli_list_mailboxes(int cli_fd, struct ao2_container *mailboxes)
682 {
683         struct ao2_iterator iter;
684         const struct ast_mwi_mailbox_object *mailbox;
685
686         ast_cli(cli_fd, FORMAT_MAILBOX_HDR, "New", "Old", "Mailbox");
687
688         iter = ao2_iterator_init(mailboxes, 0);
689         for (; (mailbox = ao2_iterator_next(&iter)); ast_mwi_mailbox_unref(mailbox)) {
690                 mwi_cli_print_mailbox(cli_fd, mailbox);
691         }
692         ao2_iterator_destroy(&iter);
693 }
694 #endif  /* defined(MWI_DEBUG_CLI) */
695
696 #undef FORMAT_MAILBOX_HDR
697 #undef FORMAT_MAILBOX_ROW
698
699 #if defined(MWI_DEBUG_CLI)
700 static char *handle_mwi_list_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
701 {
702         struct ao2_container *mailboxes;
703
704         switch (cmd) {
705         case CLI_INIT:
706                 e->command = "mwi list all";
707                 e->usage =
708                         "Usage: mwi list all\n"
709                         "       List all external MWI mailboxes.\n";
710                 return NULL;
711         case CLI_GENERATE:
712                 return NULL;
713         }
714
715         mailboxes = ast_mwi_mailbox_get_all();
716         if (!mailboxes) {
717                 ast_cli(a->fd, "Failed to retrieve external MWI mailboxes.\n");
718                 return CLI_SUCCESS;
719         }
720         mwi_cli_list_mailboxes(a->fd, mailboxes);
721         ao2_ref(mailboxes, -1);
722         return CLI_SUCCESS;
723 }
724 #endif  /* defined(MWI_DEBUG_CLI) */
725
726 #if defined(MWI_DEBUG_CLI)
727 static char *handle_mwi_list_like(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
728 {
729         struct ao2_container *mailboxes;
730         const char *regex;
731
732         switch (cmd) {
733         case CLI_INIT:
734                 e->command = "mwi list like";
735                 e->usage =
736                         "Usage: mwi list like <pattern>\n"
737                         "       List external MWI mailboxes matching a regular expression.\n";
738                 return NULL;
739         case CLI_GENERATE:
740                 return NULL;
741         }
742
743         if (a->argc != 4) {
744                 return CLI_SHOWUSAGE;
745         }
746         regex = a->argv[3];
747
748         mailboxes = ast_mwi_mailbox_get_by_regex(regex);
749         if (!mailboxes) {
750                 ast_cli(a->fd, "Failed to retrieve external MWI mailboxes.\n");
751                 return CLI_SUCCESS;
752         }
753         mwi_cli_list_mailboxes(a->fd, mailboxes);
754         ao2_ref(mailboxes, -1);
755         return CLI_SUCCESS;
756 }
757 #endif  /* defined(MWI_DEBUG_CLI) */
758
759 #if defined(MWI_DEBUG_CLI)
760 static char *handle_mwi_show_mailbox(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
761 {
762         const struct ast_mwi_mailbox_object *mailbox;
763         const char *mailbox_id;
764
765         switch (cmd) {
766         case CLI_INIT:
767                 e->command = "mwi show mailbox";
768                 e->usage =
769                         "Usage: mwi show mailbox <mailbox_id>\n"
770                         "       Show a specific external MWI mailbox.\n";
771                 return NULL;
772         case CLI_GENERATE:
773                 if (a->pos == 3) {
774                         return complete_mailbox(a->word, a->n);
775                 }
776                 return NULL;
777         }
778
779         if (a->argc != 4) {
780                 return CLI_SHOWUSAGE;
781         }
782         mailbox_id = a->argv[3];
783
784         mailbox = ast_mwi_mailbox_get(mailbox_id);
785         if (mailbox) {
786                 ast_cli(a->fd,
787                         "Mailbox: %s\n"
788                         "NewMessages: %u\n"
789                         "OldMessages: %u\n",
790                         ast_sorcery_object_get_id(mailbox),
791                         mailbox->msgs_new,
792                         mailbox->msgs_old);
793
794                 ast_mwi_mailbox_unref(mailbox);
795         } else {
796                 ast_cli(a->fd, "External MWI mailbox '%s' not found.\n", mailbox_id);
797         }
798
799         return CLI_SUCCESS;
800 }
801 #endif  /* defined(MWI_DEBUG_CLI) */
802
803 #if defined(MWI_DEBUG_CLI)
804 static char *handle_mwi_update_mailbox(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
805 {
806         struct ast_mwi_mailbox_object *mailbox;
807         const char *mailbox_id;
808         unsigned int num_new;
809         unsigned int num_old;
810
811         switch (cmd) {
812         case CLI_INIT:
813                 e->command = "mwi update mailbox";
814                 e->usage =
815                         "Usage: mwi update mailbox <mailbox_id> [<new> [<old>]]\n"
816                         "       Update a specific external MWI mailbox.\n";
817                 return NULL;
818         case CLI_GENERATE:
819                 if (a->pos == 3) {
820                         return complete_mailbox(a->word, a->n);
821                 }
822                 return NULL;
823         }
824
825         if (a->argc < 4 || 6 < a->argc) {
826                 return CLI_SHOWUSAGE;
827         }
828         mailbox_id = a->argv[3];
829
830         num_new = 0;
831         if (4 < a->argc) {
832                 const char *count_new = a->argv[4];
833
834                 if (sscanf(count_new, "%u", &num_new) != 1) {
835                         ast_cli(a->fd, "Invalid NewMessages: '%s'.\n", count_new);
836                         return CLI_SHOWUSAGE;
837                 }
838         }
839
840         num_old = 0;
841         if (5 < a->argc) {
842                 const char *count_old = a->argv[5];
843
844                 if (sscanf(count_old, "%u", &num_old) != 1) {
845                         ast_cli(a->fd, "Invalid OldMessages: '%s'.\n", count_old);
846                         return CLI_SHOWUSAGE;
847                 }
848         }
849
850         mailbox = ast_mwi_mailbox_alloc(mailbox_id);
851         if (mailbox) {
852                 ast_mwi_mailbox_set_msgs_new(mailbox, num_new);
853                 ast_mwi_mailbox_set_msgs_old(mailbox, num_old);
854                 if (ast_mwi_mailbox_update(mailbox)) {
855                         ast_cli(a->fd, "Could not update mailbox %s.\n",
856                                 ast_sorcery_object_get_id(mailbox));
857                 } else {
858                         ast_cli(a->fd, "Updated mailbox %s.\n", ast_sorcery_object_get_id(mailbox));
859                 }
860
861                 ast_mwi_mailbox_unref(mailbox);
862         }
863
864         return CLI_SUCCESS;
865 }
866 #endif  /* defined(MWI_DEBUG_CLI) */
867
868 #if defined(MWI_DEBUG_CLI)
869 static struct ast_cli_entry mwi_cli[] = {
870         AST_CLI_DEFINE(handle_mwi_delete_all, "Delete all external MWI mailboxes"),
871         AST_CLI_DEFINE(handle_mwi_delete_like, "Delete external MWI mailboxes matching regex"),
872         AST_CLI_DEFINE(handle_mwi_delete_mailbox, "Delete a specific external MWI mailbox"),
873         AST_CLI_DEFINE(handle_mwi_list_all, "List all external MWI mailboxes"),
874         AST_CLI_DEFINE(handle_mwi_list_like, "List external MWI mailboxes matching regex"),
875         AST_CLI_DEFINE(handle_mwi_show_mailbox, "Show a specific external MWI mailbox"),
876         AST_CLI_DEFINE(handle_mwi_update_mailbox, "Update a specific external MWI mailbox"),
877 };
878 #endif  /* defined(MWI_DEBUG_CLI) */
879
880 /*!
881  * \internal
882  * \brief Post initial MWI count events.
883  * \since 12.1.0
884  *
885  * \return Nothing
886  */
887 static void mwi_initial_events(void)
888 {
889         struct ao2_container *mailboxes;
890         const struct ast_mwi_mailbox_object *mailbox;
891         struct ao2_iterator iter;
892
893         /* Get all mailbox counts. */
894         mailboxes = ast_mwi_mailbox_get_all();
895         if (!mailboxes) {
896                 return;
897         }
898
899         /* Post all mailbox counts. */
900         iter = ao2_iterator_init(mailboxes, AO2_ITERATOR_UNLINK);
901         for (; (mailbox = ao2_iterator_next(&iter)); ast_mwi_mailbox_unref(mailbox)) {
902                 mwi_post_event(mailbox);
903         }
904         ao2_iterator_destroy(&iter);
905
906         ao2_ref(mailboxes, -1);
907 }
908
909 static int unload_module(void)
910 {
911         ast_vm_unregister(vm_table.module_name);
912 #if defined(MWI_DEBUG_CLI)
913         ast_cli_unregister_multiple(mwi_cli, ARRAY_LEN(mwi_cli));
914 #endif  /* defined(MWI_DEBUG_CLI) */
915         ast_sorcery_observer_remove(mwi_sorcery, MWI_MAILBOX_TYPE, &mwi_observers);
916
917         ast_sorcery_unref(mwi_sorcery);
918         mwi_sorcery = NULL;
919
920         return 0;
921 }
922
923 static int load_module(void)
924 {
925         int res;
926
927         if (mwi_sorcery_init()
928                 || ast_sorcery_observer_add(mwi_sorcery, MWI_MAILBOX_TYPE, &mwi_observers)
929 #if defined(MWI_DEBUG_CLI)
930                 || ast_cli_register_multiple(mwi_cli, ARRAY_LEN(mwi_cli))
931 #endif  /* defined(MWI_DEBUG_CLI) */
932                 ) {
933                 unload_module();
934                 return AST_MODULE_LOAD_DECLINE;
935         }
936
937         /* ast_vm_register may return DECLINE if another module registered for vm */
938         res = ast_vm_register(&vm_table);
939         if (res) {
940                 ast_log(LOG_ERROR, "Failure registering as a voicemail provider\n");
941                 unload_module();
942                 return AST_MODULE_LOAD_DECLINE;
943         }
944
945         /* Post initial MWI count events. */
946         mwi_initial_events();
947
948         return AST_MODULE_LOAD_SUCCESS;
949 }
950
951 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Core external MWI resource",
952         .support_level = AST_MODULE_SUPPORT_CORE,
953         .load = load_module,
954         .unload = unload_module,
955         .load_pri = AST_MODPRI_CHANNEL_DEPEND - 5,
956 );