diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index 05b0cfb096..538b6cd1bc 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -47,6 +47,7 @@
*/
/*** MODULEINFO
+ res_mwi_external
core
diff --git a/configs/sorcery.conf.sample b/configs/sorcery.conf.sample
index ab802d450a..ee13003603 100644
--- a/configs/sorcery.conf.sample
+++ b/configs/sorcery.conf.sample
@@ -48,6 +48,13 @@ test=memory
test/cache=test
test=memory
+;
+; The following object mapping is the default mapping of external MWI mailbox
+; objects to give persistence to the message counts.
+;
+;[res_mwi_external]
+;mailboxes=astdb,mwi_external
+
;
; The following object mappings set PJSIP objects to use realtime database mappings from extconfig
; with the table names used when automatically generating configuration from the alembic script.
diff --git a/include/asterisk/res_mwi_external.h b/include/asterisk/res_mwi_external.h
new file mode 100644
index 0000000000..7698a1bd69
--- /dev/null
+++ b/include/asterisk/res_mwi_external.h
@@ -0,0 +1,226 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Richard Mudgett
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Core external MWI support.
+ *
+ * \author Richard Mudgett
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+#ifndef _ASTERISK_RES_MWI_EXTERNAL_H
+#define _ASTERISK_RES_MWI_EXTERNAL_H
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C"
+{
+#endif
+
+/* ------------------------------------------------------------------- */
+
+/*!
+ * \brief Increase the external MWI resource module reference count.
+ * \since 12.1.0
+ *
+ * \return Nothing
+ */
+void ast_mwi_external_ref(void);
+
+/*!
+ * \brief Decrease the external MWI resource module reference count.
+ * \since 12.1.0
+ *
+ * \return Nothing
+ */
+void ast_mwi_external_unref(void);
+
+struct ast_mwi_mailbox_object;
+
+/*! \brief Convienience unref function for mailbox object. */
+#define ast_mwi_mailbox_unref(mailbox) ao2_ref((struct ast_mwi_mailbox_object *) mailbox, -1)
+
+/*!
+ * \brief Allocate an external MWI object.
+ * \since 12.1.0
+ *
+ * \param mailbox_id Name of mailbox.
+ *
+ * \retval object on success. The object is an ao2 object.
+ * \retval NULL on error.
+ */
+struct ast_mwi_mailbox_object *ast_mwi_mailbox_alloc(const char *mailbox_id);
+
+/*!
+ * \brief Get mailbox id.
+ * \since 12.1.0
+ *
+ * \param mailbox Object to get id.
+ *
+ * \return mailbox_id of the object.
+ *
+ * \note This should never return NULL unless there is a bug in sorcery.
+ */
+const char *ast_mwi_mailbox_get_id(const struct ast_mwi_mailbox_object *mailbox);
+
+/*!
+ * \brief Get the number of new messages.
+ * \since 12.1.0
+ *
+ * \param mailbox Object to get number of new messages.
+ *
+ * \return Number of new messages.
+ */
+unsigned int ast_mwi_mailbox_get_msgs_new(const struct ast_mwi_mailbox_object *mailbox);
+
+/*!
+ * \brief Get the number of old messages.
+ * \since 12.1.0
+ *
+ * \param mailbox Object to get number of old messages.
+ *
+ * \return Number of old messages.
+ */
+unsigned int ast_mwi_mailbox_get_msgs_old(const struct ast_mwi_mailbox_object *mailbox);
+
+/*!
+ * \brief Copy the external MWI counts object.
+ * \since 12.1.0
+ *
+ * \param mailbox What to copy.
+ *
+ * \retval copy on success. The object is an ao2 object.
+ * \retval NULL on error.
+ */
+struct ast_mwi_mailbox_object *ast_mwi_mailbox_copy(const struct ast_mwi_mailbox_object *mailbox);
+
+/*!
+ * \brief Set the number of new messages.
+ * \since 12.1.0
+ *
+ * \param mailbox Object to set number of new messages.
+ * \param num_msgs Number of messages to set.
+ *
+ * \return Nothing
+ */
+void ast_mwi_mailbox_set_msgs_new(struct ast_mwi_mailbox_object *mailbox, unsigned int num_msgs);
+
+/*!
+ * \brief Set the number of old messages.
+ * \since 12.1.0
+ *
+ * \param mailbox Object to set number of old messages.
+ * \param num_msgs Number of messages to set.
+ *
+ * \return Nothing
+ */
+void ast_mwi_mailbox_set_msgs_old(struct ast_mwi_mailbox_object *mailbox, unsigned int num_msgs);
+
+/*!
+ * \brief Update the external MWI counts with the given object.
+ * \since 12.1.0
+ *
+ * \param mailbox What to update.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_mwi_mailbox_update(struct ast_mwi_mailbox_object *mailbox);
+
+/*!
+ * \brief Delete matching external MWI object.
+ * \since 12.1.0
+ *
+ * \param mailbox_id Name of mailbox to delete.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_mwi_mailbox_delete(const char *mailbox_id);
+
+/*!
+ * \brief Delete all external MWI objects selected by the regular expression.
+ * \since 12.1.0
+ *
+ * \param regex Regular expression in extended syntax. (NULL is same as "")
+ *
+ * \note The provided regex is treated as extended case sensitive.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_mwi_mailbox_delete_by_regex(const char *regex);
+
+/*!
+ * \brief Delete all external MWI objects.
+ * \since 12.1.0
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+int ast_mwi_mailbox_delete_all(void);
+
+/*!
+ * \brief Get matching external MWI object.
+ * \since 12.1.0
+ *
+ * \param mailbox_id Name of mailbox to retrieve.
+ *
+ * \retval requested mailbox on success. The object is an ao2 object.
+ * \retval NULL on error or no mailbox.
+ *
+ * \note The object must be treated as read-only.
+ */
+const struct ast_mwi_mailbox_object *ast_mwi_mailbox_get(const char *mailbox_id);
+
+/*!
+ * \brief Get all external MWI objects selected by the regular expression.
+ * \since 12.1.0
+ *
+ * \param regex Regular expression in extended syntax. (NULL is same as "")
+ *
+ * \note The provided regex is treated as extended case sensitive.
+ *
+ * \retval container of struct ast_mwi_mailbox_object on success.
+ * \retval NULL on error.
+ *
+ * \note The objects in the container must be treated as read-only.
+ */
+struct ao2_container *ast_mwi_mailbox_get_by_regex(const char *regex);
+
+/*!
+ * \brief Get all external MWI objects.
+ * \since 12.1.0
+ *
+ * \retval container of struct ast_mwi_mailbox_object on success.
+ * \retval NULL on error.
+ *
+ * \note The objects in the container must be treated as read-only.
+ */
+struct ao2_container *ast_mwi_mailbox_get_all(void);
+
+
+/* ------------------------------------------------------------------- */
+
+#if defined(__cplusplus) || defined(c_plusplus)
+}
+#endif
+
+#endif /* _ASTERISK_RES_MWI_EXTERNAL_H */
diff --git a/res/res_mwi_external.c b/res/res_mwi_external.c
new file mode 100644
index 0000000000..a7531c5cbf
--- /dev/null
+++ b/res/res_mwi_external.c
@@ -0,0 +1,960 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Richard Mudgett
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*!
+ * \file
+ * \brief Core external MWI support.
+ *
+ * \details
+ * The module manages the persistent message counts cache and supplies
+ * an API to allow the protocol specific modules to control the counts
+ * or a subset.
+ *
+ * \author Richard Mudgett
+ *
+ * See Also:
+ * \arg \ref AstCREDITS
+ */
+
+/*** MODULEINFO
+ no
+ app_voicemail
+ core
+ ***/
+
+/*** DOCUMENTATION
+
+ Core external MWI support
+
+
+ Persistent cache of external MWI Mailboxs.
+
+ Allows the alteration of sorcery backend mapping for
+ the persistent cache of external MWI mailboxes.
+
+
+
+
+ ***/
+
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/app.h"
+#include "asterisk/module.h"
+#include "asterisk/res_mwi_external.h"
+#include "asterisk/sorcery.h"
+#include "asterisk/cli.h"
+
+/* ------------------------------------------------------------------- */
+
+/*!
+ * Define to include CLI commands to manipulate the external MWI mailboxes.
+ * Useful for testing the module functionality.
+ */
+//#define MWI_DEBUG_CLI 1
+
+#define MWI_ASTDB_PREFIX "mwi_external"
+#define MWI_MAILBOX_TYPE "mailboxes"
+
+struct ast_mwi_mailbox_object {
+ SORCERY_OBJECT(details);
+ /*! Number of new messages in mailbox. */
+ unsigned int msgs_new;
+ /*! Number of old messages in mailbox. */
+ unsigned int msgs_old;
+};
+
+static struct ast_sorcery *mwi_sorcery;
+
+void ast_mwi_external_ref(void)
+{
+ ast_module_ref(ast_module_info->self);
+}
+
+void ast_mwi_external_unref(void)
+{
+ ast_module_unref(ast_module_info->self);
+}
+
+/*!
+ * \internal
+ * \brief Post an update event to the MWI counts.
+ * \since 12.1.0
+ *
+ * \return Nothing
+ */
+static void mwi_post_event(const struct ast_mwi_mailbox_object *mailbox)
+{
+ ast_publish_mwi_state(ast_sorcery_object_get_id(mailbox), NULL,
+ mailbox->msgs_new, mailbox->msgs_old);
+}
+
+static void mwi_observe_update(const void *obj)
+{
+ mwi_post_event(obj);
+}
+
+/*!
+ * \internal
+ * \brief Post a count clearing event to the MWI counts.
+ * \since 12.1.0
+ *
+ * \return Nothing
+ */
+static void mwi_observe_delete(const void *obj)
+{
+ const struct ast_mwi_mailbox_object *mailbox = obj;
+
+ if (!mailbox->msgs_new && !mailbox->msgs_old) {
+ /* No need to post a count clearing event. */
+ return;
+ }
+
+ /* Post a count clearing event. */
+ ast_publish_mwi_state(ast_sorcery_object_get_id(mailbox), NULL, 0, 0);
+}
+
+static const struct ast_sorcery_observer mwi_observers = {
+ .created = mwi_observe_update,
+ .updated = mwi_observe_update,
+ .deleted = mwi_observe_delete,
+};
+
+/*! \brief Internal function to allocate a mwi object */
+static void *mwi_sorcery_object_alloc(const char *id)
+{
+ return ast_sorcery_generic_alloc(sizeof(struct ast_mwi_mailbox_object), NULL);
+}
+
+/*!
+ * \internal
+ * \brief Initialize sorcery for external MWI.
+ * \since 12.1.0
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int mwi_sorcery_init(void)
+{
+ int res;
+
+ mwi_sorcery = ast_sorcery_open();
+ if (!mwi_sorcery) {
+ ast_log(LOG_ERROR, "MWI external: Sorcery failed to open.\n");
+ return -1;
+ }
+
+ /* Map the external MWI wizards. */
+ res = !!ast_sorcery_apply_config(mwi_sorcery, "res_mwi_external");
+ res &= !!ast_sorcery_apply_default(mwi_sorcery, MWI_MAILBOX_TYPE, "astdb",
+ MWI_ASTDB_PREFIX);
+ if (res) {
+ ast_log(LOG_ERROR, "MWI external: Sorcery could not setup wizards.\n");
+ return -1;
+ }
+
+ res = ast_sorcery_object_register(mwi_sorcery, MWI_MAILBOX_TYPE,
+ mwi_sorcery_object_alloc, NULL, NULL);
+ if (res) {
+ ast_log(LOG_ERROR, "MWI external: Sorcery could not register object type '%s'.\n",
+ MWI_MAILBOX_TYPE);
+ return -1;
+ }
+
+ /* Define the MWI_MAILBOX_TYPE object fields. */
+ res |= ast_sorcery_object_field_register_nodoc(mwi_sorcery, MWI_MAILBOX_TYPE,
+ "msgs_new", "0", OPT_UINT_T, 0, FLDSET(struct ast_mwi_mailbox_object, msgs_new));
+ res |= ast_sorcery_object_field_register_nodoc(mwi_sorcery, MWI_MAILBOX_TYPE,
+ "msgs_old", "0", OPT_UINT_T, 0, FLDSET(struct ast_mwi_mailbox_object, msgs_old));
+ return res ? -1 : 0;
+}
+
+struct ao2_container *ast_mwi_mailbox_get_all(void)
+{
+ return ast_sorcery_retrieve_by_fields(mwi_sorcery, MWI_MAILBOX_TYPE,
+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+}
+
+struct ao2_container *ast_mwi_mailbox_get_by_regex(const char *regex)
+{
+ return ast_sorcery_retrieve_by_regex(mwi_sorcery, MWI_MAILBOX_TYPE, regex ?: "");
+}
+
+const struct ast_mwi_mailbox_object *ast_mwi_mailbox_get(const char *mailbox_id)
+{
+ if (ast_strlen_zero(mailbox_id)) {
+ return NULL;
+ }
+
+ return ast_sorcery_retrieve_by_id(mwi_sorcery, MWI_MAILBOX_TYPE, mailbox_id);
+}
+
+struct ast_mwi_mailbox_object *ast_mwi_mailbox_alloc(const char *mailbox_id)
+{
+ if (ast_strlen_zero(mailbox_id)) {
+ return NULL;
+ }
+
+ return ast_sorcery_alloc(mwi_sorcery, MWI_MAILBOX_TYPE, mailbox_id);
+}
+
+struct ast_mwi_mailbox_object *ast_mwi_mailbox_copy(const struct ast_mwi_mailbox_object *mailbox)
+{
+ return ast_sorcery_copy(mwi_sorcery, mailbox);
+}
+
+const char *ast_mwi_mailbox_get_id(const struct ast_mwi_mailbox_object *mailbox)
+{
+ return ast_sorcery_object_get_id(mailbox);
+}
+
+unsigned int ast_mwi_mailbox_get_msgs_new(const struct ast_mwi_mailbox_object *mailbox)
+{
+ return mailbox->msgs_new;
+}
+
+unsigned int ast_mwi_mailbox_get_msgs_old(const struct ast_mwi_mailbox_object *mailbox)
+{
+ return mailbox->msgs_old;
+}
+
+void ast_mwi_mailbox_set_msgs_new(struct ast_mwi_mailbox_object *mailbox, unsigned int num_msgs)
+{
+ mailbox->msgs_new = num_msgs;
+}
+
+void ast_mwi_mailbox_set_msgs_old(struct ast_mwi_mailbox_object *mailbox, unsigned int num_msgs)
+{
+ mailbox->msgs_old = num_msgs;
+}
+
+int ast_mwi_mailbox_update(struct ast_mwi_mailbox_object *mailbox)
+{
+ const struct ast_mwi_mailbox_object *exists;
+ int res;
+
+ exists = ast_sorcery_retrieve_by_id(mwi_sorcery, MWI_MAILBOX_TYPE,
+ ast_sorcery_object_get_id(mailbox));
+ if (exists) {
+ res = ast_sorcery_update(mwi_sorcery, mailbox);
+ ast_mwi_mailbox_unref(exists);
+ } else {
+ res = ast_sorcery_create(mwi_sorcery, mailbox);
+ }
+ return res;
+}
+
+/*!
+ * \internal
+ * \brief Delete a mailbox.
+ * \since 12.1.0
+ *
+ * \param mailbox Mailbox object to delete from sorcery.
+ *
+ * \return Nothing
+ */
+static void mwi_mailbox_delete(struct ast_mwi_mailbox_object *mailbox)
+{
+ ast_sorcery_delete(mwi_sorcery, mailbox);
+}
+
+/*!
+ * \internal
+ * \brief Delete all mailboxes in container.
+ * \since 12.1.0
+ *
+ * \param mailboxes Mailbox objects to delete from sorcery.
+ *
+ * \return Nothing
+ */
+static void mwi_mailbox_delete_all(struct ao2_container *mailboxes)
+{
+ struct ast_mwi_mailbox_object *mailbox;
+ struct ao2_iterator iter;
+
+ iter = ao2_iterator_init(mailboxes, AO2_ITERATOR_UNLINK);
+ for (; (mailbox = ao2_iterator_next(&iter)); ast_mwi_mailbox_unref(mailbox)) {
+ mwi_mailbox_delete(mailbox);
+ }
+ ao2_iterator_destroy(&iter);
+}
+
+int ast_mwi_mailbox_delete_all(void)
+{
+ struct ao2_container *mailboxes;
+
+ mailboxes = ast_mwi_mailbox_get_all();
+ if (mailboxes) {
+ mwi_mailbox_delete_all(mailboxes);
+ ao2_ref(mailboxes, -1);
+ }
+ return 0;
+}
+
+int ast_mwi_mailbox_delete_by_regex(const char *regex)
+{
+ struct ao2_container *mailboxes;
+
+ mailboxes = ast_mwi_mailbox_get_by_regex(regex);
+ if (mailboxes) {
+ mwi_mailbox_delete_all(mailboxes);
+ ao2_ref(mailboxes, -1);
+ }
+ return 0;
+}
+
+int ast_mwi_mailbox_delete(const char *mailbox_id)
+{
+ const struct ast_mwi_mailbox_object *mailbox;
+
+ if (ast_strlen_zero(mailbox_id)) {
+ return -1;
+ }
+
+ mailbox = ast_mwi_mailbox_get(mailbox_id);
+ if (mailbox) {
+ mwi_mailbox_delete((struct ast_mwi_mailbox_object *) mailbox);
+ ast_mwi_mailbox_unref(mailbox);
+ }
+ return 0;
+}
+
+enum folder_map {
+ FOLDER_INVALID = 0,
+ FOLDER_INBOX = 1,
+ FOLDER_OLD = 2,
+};
+
+/*!
+ * \internal
+ * \brief Determine if the requested folder is valid for external MWI support.
+ * \since 12.1.0
+ *
+ * \param folder Folder name to check (NULL is valid).
+ *
+ * \return Enum of the supported folder.
+ */
+static enum folder_map mwi_folder_map(const char *folder)
+{
+ enum folder_map which_folder;
+
+ if (ast_strlen_zero(folder) || !strcasecmp(folder, "INBOX")) {
+ which_folder = FOLDER_INBOX;
+ } else if (!strcasecmp(folder, "Old")) {
+ which_folder = FOLDER_OLD;
+ } else {
+ which_folder = FOLDER_INVALID;
+ }
+ return which_folder;
+}
+
+/*!
+ * \internal
+ * \brief Gets the number of messages that exist in a mailbox folder.
+ * \since 12.1.0
+ *
+ * \param mailbox_id The mailbox name.
+ * \param folder The folder to look in. Default is INBOX if not provided.
+ *
+ * \return The number of messages in the mailbox folder (zero or more).
+ */
+static int mwi_messagecount(const char *mailbox_id, const char *folder)
+{
+ const struct ast_mwi_mailbox_object *mailbox;
+ int num_msgs;
+ enum folder_map which_folder;
+
+ which_folder = mwi_folder_map(folder);
+ if (which_folder == FOLDER_INVALID) {
+ return 0;
+ }
+
+ mailbox = ast_mwi_mailbox_get(mailbox_id);
+ if (!mailbox) {
+ return 0;
+ }
+ num_msgs = 0;
+ switch (which_folder) {
+ case FOLDER_INVALID:
+ break;
+ case FOLDER_INBOX:
+ num_msgs = mailbox->msgs_new;
+ break;
+ case FOLDER_OLD:
+ num_msgs = mailbox->msgs_old;
+ break;
+ }
+ ast_mwi_mailbox_unref(mailbox);
+
+ return num_msgs;
+}
+
+/*!
+ * \internal
+ * \brief Determines if the given folder has messages.
+ * \since 12.1.0
+ *
+ * \param mailboxes Comma or & delimited list of mailboxes.
+ * \param folder The folder to look in. Default is INBOX if not provided.
+ *
+ * \retval 1 if the folder has one or more messages.
+ * \retval 0 otherwise.
+ */
+static int mwi_has_voicemail(const char *mailboxes, const char *folder)
+{
+ char *parse;
+ char *mailbox_id;
+ enum folder_map which_folder;
+
+ which_folder = mwi_folder_map(folder);
+ if (which_folder == FOLDER_INVALID) {
+ return 0;
+ }
+
+ /* For each mailbox in the list. */
+ parse = ast_strdupa(mailboxes);
+ while ((mailbox_id = strsep(&parse, ",&"))) {
+ const struct ast_mwi_mailbox_object *mailbox;
+ int num_msgs;
+
+ /* Get the specified mailbox. */
+ mailbox = ast_mwi_mailbox_get(mailbox_id);
+ if (!mailbox) {
+ continue;
+ }
+
+ /* Done if the found mailbox has any messages. */
+ num_msgs = 0;
+ switch (which_folder) {
+ case FOLDER_INVALID:
+ break;
+ case FOLDER_INBOX:
+ num_msgs = mailbox->msgs_new;
+ break;
+ case FOLDER_OLD:
+ num_msgs = mailbox->msgs_old;
+ break;
+ }
+ ast_mwi_mailbox_unref(mailbox);
+ if (num_msgs) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Gets the number of messages that exist for the mailbox list.
+ * \since 12.1.0
+ *
+ * \param mailboxes Comma or space delimited list of mailboxes.
+ * \param newmsgs Where to put the count of new messages. (Can be NULL)
+ * \param oldmsgs Where to put the count of old messages. (Can be NULL)
+ *
+ * \details
+ * Simultaneously determines the count of new and old
+ * messages. The total messages would then be the sum of these.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int mwi_inboxcount(const char *mailboxes, int *newmsgs, int *oldmsgs)
+{
+ char *parse;
+ char *mailbox_id;
+
+ if (!newmsgs && !oldmsgs) {
+ /* Nowhere to accumulate counts */
+ return 0;
+ }
+
+ /* For each mailbox in the list. */
+ parse = ast_strdupa(mailboxes);
+ while ((mailbox_id = strsep(&parse, ", "))) {
+ const struct ast_mwi_mailbox_object *mailbox;
+
+ /* Get the specified mailbox. */
+ mailbox = ast_mwi_mailbox_get(mailbox_id);
+ if (!mailbox) {
+ continue;
+ }
+
+ /* Accumulate the counts. */
+ if (newmsgs) {
+ *newmsgs += mailbox->msgs_new;
+ }
+ if (oldmsgs) {
+ *oldmsgs += mailbox->msgs_old;
+ }
+
+ ast_mwi_mailbox_unref(mailbox);
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Gets the number of messages that exist for the mailbox list.
+ * \since 12.1.0
+ *
+ * \param mailboxes Comma or space delimited list of mailboxes.
+ * \param urgentmsgs Where to put the count of urgent messages. (Can be NULL)
+ * \param newmsgs Where to put the count of new messages. (Can be NULL)
+ * \param oldmsgs Where to put the count of old messages. (Can be NULL)
+ *
+ * \details
+ * Simultaneously determines the count of new, old, and urgent
+ * messages. The total messages would then be the sum of these
+ * three.
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+static int mwi_inboxcount2(const char *mailboxes, int *urgentmsgs, int *newmsgs, int *oldmsgs)
+{
+ /*
+ * This module does not support urgentmsgs. Just ignore them.
+ * The global API call has already set the count to zero.
+ */
+ return mwi_inboxcount(mailboxes, newmsgs, oldmsgs);
+}
+
+static const struct ast_vm_functions vm_table = {
+ .module_version = VM_MODULE_VERSION,
+ .module_name = AST_MODULE,
+
+ .has_voicemail = mwi_has_voicemail,
+ .inboxcount = mwi_inboxcount,
+ .inboxcount2 = mwi_inboxcount2,
+ .messagecount = mwi_messagecount,
+};
+
+#if defined(MWI_DEBUG_CLI)
+static char *complete_mailbox(const char *word, int state)
+{
+ struct ao2_iterator iter;
+ int wordlen = strlen(word);
+ int which = 0;
+ char *ret = NULL;
+ char *regex;
+ const struct ast_mwi_mailbox_object *mailbox;
+ RAII_VAR(struct ao2_container *, mailboxes, NULL, ao2_cleanup);
+
+ regex = ast_alloca(2 + wordlen);
+ sprintf(regex, "^%s", word);/* Safe */
+
+ mailboxes = ast_mwi_mailbox_get_by_regex(regex);
+ if (!mailboxes) {
+ return NULL;
+ }
+
+ iter = ao2_iterator_init(mailboxes, 0);
+ for (; (mailbox = ao2_iterator_next(&iter)); ast_mwi_mailbox_unref(mailbox)) {
+ if (++which > state) {
+ ret = ast_strdup(ast_sorcery_object_get_id(mailbox));
+ ast_mwi_mailbox_unref(mailbox);
+ break;
+ }
+ }
+ ao2_iterator_destroy(&iter);
+
+ return ret;
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#if defined(MWI_DEBUG_CLI)
+static char *handle_mwi_delete_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mwi delete all";
+ e->usage =
+ "Usage: mwi delete all\n"
+ " Delete all external MWI mailboxes.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ ast_mwi_mailbox_delete_all();
+ ast_cli(a->fd, "Deleted all external MWI mailboxes.\n");
+ return CLI_SUCCESS;
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#if defined(MWI_DEBUG_CLI)
+static char *handle_mwi_delete_like(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ const char *regex;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mwi delete like";
+ e->usage =
+ "Usage: mwi delete like \n"
+ " Delete external MWI mailboxes matching a regular expression.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+ regex = a->argv[3];
+
+ ast_mwi_mailbox_delete_by_regex(regex);
+ ast_cli(a->fd, "Deleted external MWI mailboxes matching '%s'.\n", regex);
+ return CLI_SUCCESS;
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#if defined(MWI_DEBUG_CLI)
+static char *handle_mwi_delete_mailbox(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ const char *mailbox_id;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mwi delete mailbox";
+ e->usage =
+ "Usage: mwi delete mailbox \n"
+ " Delete a specific external MWI mailbox.\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 3) {
+ return complete_mailbox(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+ mailbox_id = a->argv[3];
+
+ ast_mwi_mailbox_delete(mailbox_id);
+ ast_cli(a->fd, "Deleted external MWI mailbox '%s'.\n", mailbox_id);
+
+ return CLI_SUCCESS;
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#define FORMAT_MAILBOX_HDR "%6s %6s %s\n"
+#define FORMAT_MAILBOX_ROW "%6u %6u %s\n"
+
+#if defined(MWI_DEBUG_CLI)
+/*!
+ * \internal
+ * \brief Print a mailbox list line to CLI.
+ * \since 12.1.0
+ *
+ * \param cli_fd File descriptor for CLI output.
+ * \param mailbox What to list.
+ *
+ * \return Nothing
+ */
+static void mwi_cli_print_mailbox(int cli_fd, const struct ast_mwi_mailbox_object *mailbox)
+{
+ ast_cli(cli_fd, FORMAT_MAILBOX_ROW, mailbox->msgs_new, mailbox->msgs_old,
+ ast_sorcery_object_get_id(mailbox));
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#if defined(MWI_DEBUG_CLI)
+/*!
+ * \internal
+ * \brief List all mailboxes in the given container.
+ * \since 12.1.0
+ *
+ * \param cli_fd File descriptor for CLI output.
+ * \param mailboxes What to list.
+ *
+ * \return Nothing
+ */
+static void mwi_cli_list_mailboxes(int cli_fd, struct ao2_container *mailboxes)
+{
+ struct ao2_iterator iter;
+ const struct ast_mwi_mailbox_object *mailbox;
+
+ ast_cli(cli_fd, FORMAT_MAILBOX_HDR, "New", "Old", "Mailbox");
+
+ iter = ao2_iterator_init(mailboxes, 0);
+ for (; (mailbox = ao2_iterator_next(&iter)); ast_mwi_mailbox_unref(mailbox)) {
+ mwi_cli_print_mailbox(cli_fd, mailbox);
+ }
+ ao2_iterator_destroy(&iter);
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#undef FORMAT_MAILBOX_HDR
+#undef FORMAT_MAILBOX_ROW
+
+#if defined(MWI_DEBUG_CLI)
+static char *handle_mwi_list_all(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ao2_container *mailboxes;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mwi list all";
+ e->usage =
+ "Usage: mwi list all\n"
+ " List all external MWI mailboxes.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ mailboxes = ast_mwi_mailbox_get_all();
+ if (!mailboxes) {
+ ast_cli(a->fd, "Failed to retrieve external MWI mailboxes.\n");
+ return CLI_SUCCESS;
+ }
+ mwi_cli_list_mailboxes(a->fd, mailboxes);
+ ao2_ref(mailboxes, -1);
+ return CLI_SUCCESS;
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#if defined(MWI_DEBUG_CLI)
+static char *handle_mwi_list_like(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ao2_container *mailboxes;
+ const char *regex;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mwi list like";
+ e->usage =
+ "Usage: mwi list like \n"
+ " List external MWI mailboxes matching a regular expression.\n";
+ return NULL;
+ case CLI_GENERATE:
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+ regex = a->argv[3];
+
+ mailboxes = ast_mwi_mailbox_get_by_regex(regex);
+ if (!mailboxes) {
+ ast_cli(a->fd, "Failed to retrieve external MWI mailboxes.\n");
+ return CLI_SUCCESS;
+ }
+ mwi_cli_list_mailboxes(a->fd, mailboxes);
+ ao2_ref(mailboxes, -1);
+ return CLI_SUCCESS;
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#if defined(MWI_DEBUG_CLI)
+static char *handle_mwi_show_mailbox(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ const struct ast_mwi_mailbox_object *mailbox;
+ const char *mailbox_id;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mwi show mailbox";
+ e->usage =
+ "Usage: mwi show mailbox \n"
+ " Show a specific external MWI mailbox.\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 3) {
+ return complete_mailbox(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc != 4) {
+ return CLI_SHOWUSAGE;
+ }
+ mailbox_id = a->argv[3];
+
+ mailbox = ast_mwi_mailbox_get(mailbox_id);
+ if (mailbox) {
+ ast_cli(a->fd,
+ "Mailbox: %s\n"
+ "NewMessages: %u\n"
+ "OldMessages: %u\n",
+ ast_sorcery_object_get_id(mailbox),
+ mailbox->msgs_new,
+ mailbox->msgs_old);
+
+ ast_mwi_mailbox_unref(mailbox);
+ } else {
+ ast_cli(a->fd, "External MWI mailbox '%s' not found.\n", mailbox_id);
+ }
+
+ return CLI_SUCCESS;
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#if defined(MWI_DEBUG_CLI)
+static char *handle_mwi_update_mailbox(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+ struct ast_mwi_mailbox_object *mailbox;
+ const char *mailbox_id;
+ unsigned int num_new;
+ unsigned int num_old;
+
+ switch (cmd) {
+ case CLI_INIT:
+ e->command = "mwi update mailbox";
+ e->usage =
+ "Usage: mwi update mailbox [ []]\n"
+ " Update a specific external MWI mailbox.\n";
+ return NULL;
+ case CLI_GENERATE:
+ if (a->pos == 3) {
+ return complete_mailbox(a->word, a->n);
+ }
+ return NULL;
+ }
+
+ if (a->argc < 4 || 6 < a->argc) {
+ return CLI_SHOWUSAGE;
+ }
+ mailbox_id = a->argv[3];
+
+ num_new = 0;
+ if (4 < a->argc) {
+ const char *count_new = a->argv[4];
+
+ if (sscanf(count_new, "%u", &num_new) != 1) {
+ ast_cli(a->fd, "Invalid NewMessages: '%s'.\n", count_new);
+ return CLI_SHOWUSAGE;
+ }
+ }
+
+ num_old = 0;
+ if (5 < a->argc) {
+ const char *count_old = a->argv[5];
+
+ if (sscanf(count_old, "%u", &num_old) != 1) {
+ ast_cli(a->fd, "Invalid OldMessages: '%s'.\n", count_old);
+ return CLI_SHOWUSAGE;
+ }
+ }
+
+ mailbox = ast_mwi_mailbox_alloc(mailbox_id);
+ if (mailbox) {
+ ast_mwi_mailbox_set_msgs_new(mailbox, num_new);
+ ast_mwi_mailbox_set_msgs_old(mailbox, num_old);
+ if (ast_mwi_mailbox_update(mailbox)) {
+ ast_cli(a->fd, "Could not update mailbox %s.\n",
+ ast_sorcery_object_get_id(mailbox));
+ } else {
+ ast_cli(a->fd, "Updated mailbox %s.\n", ast_sorcery_object_get_id(mailbox));
+ }
+
+ ast_mwi_mailbox_unref(mailbox);
+ }
+
+ return CLI_SUCCESS;
+}
+#endif /* defined(MWI_DEBUG_CLI) */
+
+#if defined(MWI_DEBUG_CLI)
+static struct ast_cli_entry mwi_cli[] = {
+ AST_CLI_DEFINE(handle_mwi_delete_all, "Delete all external MWI mailboxes"),
+ AST_CLI_DEFINE(handle_mwi_delete_like, "Delete external MWI mailboxes matching regex"),
+ AST_CLI_DEFINE(handle_mwi_delete_mailbox, "Delete a specific external MWI mailbox"),
+ AST_CLI_DEFINE(handle_mwi_list_all, "List all external MWI mailboxes"),
+ AST_CLI_DEFINE(handle_mwi_list_like, "List external MWI mailboxes matching regex"),
+ AST_CLI_DEFINE(handle_mwi_show_mailbox, "Show a specific external MWI mailbox"),
+ AST_CLI_DEFINE(handle_mwi_update_mailbox, "Update a specific external MWI mailbox"),
+};
+#endif /* defined(MWI_DEBUG_CLI) */
+
+/*!
+ * \internal
+ * \brief Post initial MWI count events.
+ * \since 12.1.0
+ *
+ * \return Nothing
+ */
+static void mwi_initial_events(void)
+{
+ struct ao2_container *mailboxes;
+ const struct ast_mwi_mailbox_object *mailbox;
+ struct ao2_iterator iter;
+
+ /* Get all mailbox counts. */
+ mailboxes = ast_mwi_mailbox_get_all();
+ if (!mailboxes) {
+ return;
+ }
+
+ /* Post all mailbox counts. */
+ iter = ao2_iterator_init(mailboxes, AO2_ITERATOR_UNLINK);
+ for (; (mailbox = ao2_iterator_next(&iter)); ast_mwi_mailbox_unref(mailbox)) {
+ mwi_post_event(mailbox);
+ }
+ ao2_iterator_destroy(&iter);
+
+ ao2_ref(mailboxes, -1);
+}
+
+static int unload_module(void)
+{
+ ast_vm_unregister(vm_table.module_name);
+#if defined(MWI_DEBUG_CLI)
+ ast_cli_unregister_multiple(mwi_cli, ARRAY_LEN(mwi_cli));
+#endif /* defined(MWI_DEBUG_CLI) */
+ ast_sorcery_observer_remove(mwi_sorcery, MWI_MAILBOX_TYPE, &mwi_observers);
+
+ ast_sorcery_unref(mwi_sorcery);
+ mwi_sorcery = NULL;
+
+ return 0;
+}
+
+static int load_module(void)
+{
+ if (mwi_sorcery_init()
+ || ast_sorcery_observer_add(mwi_sorcery, MWI_MAILBOX_TYPE, &mwi_observers)
+#if defined(MWI_DEBUG_CLI)
+ || ast_cli_register_multiple(mwi_cli, ARRAY_LEN(mwi_cli))
+#endif /* defined(MWI_DEBUG_CLI) */
+ || ast_vm_register(&vm_table)) {
+ unload_module();
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
+ /* Post initial MWI count events. */
+ mwi_initial_events();
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Core external MWI resource",
+ .load = load_module,
+ .unload = unload_module,
+ .load_pri = AST_MODPRI_CHANNEL_DEPEND - 5,
+);
diff --git a/res/res_mwi_external.exports.in b/res/res_mwi_external.exports.in
new file mode 100644
index 0000000000..c8231974b3
--- /dev/null
+++ b/res/res_mwi_external.exports.in
@@ -0,0 +1,6 @@
+{
+ global:
+ LINKER_SYMBOL_PREFIXast_*;
+ local:
+ *;
+};