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