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