diff --git a/apps/app_mixmonitor.c b/apps/app_mixmonitor.c
index c1f5ada72b..8eda3997fd 100644
--- a/apps/app_mixmonitor.c
+++ b/apps/app_mixmonitor.c
@@ -42,6 +42,7 @@
ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/paths.h" /* use ast_config_AST_MONITOR_DIR */
+#include "asterisk/stringfields.h"
#include "asterisk/file.h"
#include "asterisk/audiohook.h"
#include "asterisk/pbx.h"
@@ -51,6 +52,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/channel.h"
#include "asterisk/autochan.h"
#include "asterisk/manager.h"
+#include "asterisk/callerid.h"
#include "asterisk/mod_format.h"
#include "asterisk/linkedlists.h"
@@ -112,6 +114,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
Stores the MixMonitor's ID on this channel variable.
+
@@ -238,6 +246,17 @@ static const char * const stop_app = "StopMixMonitor";
static const char * const mixmonitor_spy_type = "MixMonitor";
+/*!
+ * \internal
+ * \brief This struct is a list item holds data needed to find a vm_recipient within voicemail
+ */
+struct vm_recipient {
+ char mailbox[AST_MAX_CONTEXT];
+ char context[AST_MAX_EXTENSION];
+ char folder[80];
+ AST_LIST_ENTRY(vm_recipient) list;
+};
+
struct mixmonitor {
struct ast_audiohook audiohook;
struct ast_callid *callid;
@@ -249,6 +268,20 @@ struct mixmonitor {
unsigned int flags;
struct ast_autochan *autochan;
struct mixmonitor_ds *mixmonitor_ds;
+
+ /* the below string fields describe data used for creating voicemails from the recording */
+ AST_DECLARE_STRING_FIELDS(
+ AST_STRING_FIELD(call_context);
+ AST_STRING_FIELD(call_macrocontext);
+ AST_STRING_FIELD(call_extension);
+ AST_STRING_FIELD(call_callerchan);
+ AST_STRING_FIELD(call_callerid);
+ );
+ int call_priority;
+
+ /* FUTURE DEVELOPMENT NOTICE
+ * recipient_list will need locks if we make it editable after the monitor is started */
+ AST_LIST_HEAD_NOLOCK(, vm_recipient) recipient_list;
};
enum mixmonitor_flags {
@@ -260,7 +293,8 @@ enum mixmonitor_flags {
MUXFLAG_READ = (1 << 6),
MUXFLAG_WRITE = (1 << 7),
MUXFLAG_COMBINED = (1 << 8),
- MUXFLAG_UID = (1 << 9),
+ MUXFLAG_UID = (1 << 9),
+ MUXFLAG_VMRECIPIENTS = (1 << 10),
};
enum mixmonitor_args {
@@ -269,7 +303,8 @@ enum mixmonitor_args {
OPT_ARG_VOLUME,
OPT_ARG_WRITENAME,
OPT_ARG_READNAME,
- OPT_ARG_UID,
+ OPT_ARG_UID,
+ OPT_ARG_VMRECIPIENTS,
OPT_ARG_ARRAY_SIZE, /* Always last element of the enum */
};
@@ -282,6 +317,7 @@ AST_APP_OPTIONS(mixmonitor_opts, {
AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
AST_APP_OPTION_ARG('i', MUXFLAG_UID, OPT_ARG_UID),
+ AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
});
struct mixmonitor_ds {
@@ -382,6 +418,70 @@ static int startmon(struct ast_channel *chan, struct ast_audiohook *audiohook)
return res;
}
+/*!
+ * \internal
+ * \brief adds recipients to a mixmonitor's recipient list
+ * \param mixmonitor mixmonitor being affected
+ * \param vm_recipients string containing the desired recipients to add
+ */
+static void add_vm_recipients_from_string(struct mixmonitor *mixmonitor, const char *vm_recipients)
+{
+ /* recipients are in a single string with a format format resembling "mailbox@context/INBOX,mailbox2@context2,mailbox3@context3/Work" */
+ char *cur_mailbox = ast_strdupa(vm_recipients);
+ char *cur_context;
+ char *cur_folder;
+ char *next;
+ int elements_processed = 0;
+
+ while (!ast_strlen_zero(cur_mailbox)) {
+ ast_debug(3, "attempting to add next element %d from %s\n", elements_processed, cur_mailbox);
+ if ((next = strchr(cur_mailbox, ',')) || (next = strchr(cur_mailbox, '&'))) {
+ *(next++) = '\0';
+ }
+
+ if ((cur_folder = strchr(cur_mailbox, '/'))) {
+ *(cur_folder++) = '\0';
+ } else {
+ cur_folder = "INBOX";
+ }
+
+ if ((cur_context = strchr(cur_mailbox, '@'))) {
+ *(cur_context++) = '\0';
+ } else {
+ cur_context = "default";
+ }
+
+ if (!ast_strlen_zero(cur_mailbox) && !ast_strlen_zero(cur_context)) {
+ struct vm_recipient *recipient;
+ if (!(recipient = ast_malloc(sizeof(*recipient)))) {
+ ast_log(LOG_ERROR, "Failed to allocate recipient. Aborting function.\n");
+ return;
+ }
+ ast_copy_string(recipient->context, cur_context, sizeof(recipient->context));
+ ast_copy_string(recipient->mailbox, cur_mailbox, sizeof(recipient->mailbox));
+ ast_copy_string(recipient->folder, cur_folder, sizeof(recipient->folder));
+
+ /* Add to list */
+ ast_verb(5, "Adding %s@%s to recipient list\n", recipient->mailbox, recipient->context);
+ AST_LIST_INSERT_HEAD(&mixmonitor->recipient_list, recipient, list);
+ } else {
+ ast_log(LOG_ERROR, "Failed to properly parse extension and/or context from element %d of recipient string: %s\n", elements_processed, vm_recipients);
+ }
+
+ cur_mailbox = next;
+ elements_processed++;
+ }
+}
+
+static void clear_mixmonitor_recipient_list(struct mixmonitor *mixmonitor)
+{
+ struct vm_recipient *current;
+ while ((current = AST_LIST_REMOVE_HEAD(&mixmonitor->recipient_list, list))) {
+ /* Clear list element data */
+ ast_free(current);
+ }
+}
+
#define SAMPLES_PER_FRAME 160
static void mixmonitor_free(struct mixmonitor *mixmonitor)
@@ -397,6 +497,12 @@ static void mixmonitor_free(struct mixmonitor *mixmonitor)
ast_free(mixmonitor->post_process);
}
+ /* Free everything in the recipient list */
+ clear_mixmonitor_recipient_list(mixmonitor);
+
+ /* clean stringfields */
+ ast_string_field_free_memory(mixmonitor);
+
if (mixmonitor->callid) {
ast_callid_unref(mixmonitor->callid);
}
@@ -404,10 +510,50 @@ static void mixmonitor_free(struct mixmonitor *mixmonitor)
}
}
-static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag)
+/*!
+ * \internal
+ * \brief Copies the mixmonitor to all voicemail recipients
+ * \param mixmonitor The mixmonitor that needs to forward its file to recipients
+ * \param ext Format of the file that was saved
+ */
+static void copy_to_voicemail(struct mixmonitor *mixmonitor, const char *ext, const char *filename)
+{
+ struct vm_recipient *recipient = NULL;
+ struct ast_vm_recording_data recording_data;
+ if (ast_string_field_init(&recording_data, 512)) {
+ ast_log(LOG_ERROR, "Failed to string_field_init, skipping copy_to_voicemail\n");
+ return;
+ }
+
+ /* Copy strings to stringfields that will be used for all recipients */
+ ast_string_field_set(&recording_data, recording_file, filename);
+ ast_string_field_set(&recording_data, recording_ext, ext);
+ ast_string_field_set(&recording_data, call_context, mixmonitor->call_context);
+ ast_string_field_set(&recording_data, call_macrocontext, mixmonitor->call_macrocontext);
+ ast_string_field_set(&recording_data, call_extension, mixmonitor->call_extension);
+ ast_string_field_set(&recording_data, call_callerchan, mixmonitor->call_callerchan);
+ ast_string_field_set(&recording_data, call_callerid, mixmonitor->call_callerid);
+ /* and call_priority gets copied too */
+ recording_data.call_priority = mixmonitor->call_priority;
+
+ AST_LIST_TRAVERSE(&mixmonitor->recipient_list, recipient, list) {
+ /* context, mailbox, and folder need to be set per recipient */
+ ast_string_field_set(&recording_data, context, recipient->context);
+ ast_string_field_set(&recording_data, mailbox, recipient->mailbox);
+ ast_string_field_set(&recording_data, folder, recipient->folder);
+
+ ast_verb(4, "MixMonitor attempting to send voicemail copy to %s@%s\n", recording_data.mailbox,
+ recording_data.context);
+ ast_app_copy_recording_to_vm(&recording_data);
+ }
+
+ /* Free the string fields for recording_data before exiting the function. */
+ ast_string_field_free_memory(&recording_data);
+}
+
+static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename, struct ast_filestream **fs, unsigned int *oflags, int *errflag, char **ext)
{
/* Initialize the file if not already done so */
- char *ext = NULL;
char *last_slash = NULL;
if (!ast_strlen_zero(filename)) {
if (!*fs && !*errflag && !mixmonitor->mixmonitor_ds->fs_quit) {
@@ -416,14 +562,19 @@ static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename,
last_slash = strrchr(filename, '/');
- if ((ext = strrchr(filename, '.')) && (ext > last_slash)) {
- *(ext++) = '\0';
+ ast_log(LOG_NOTICE, "!!!!!! File name is %s\n", filename);
+
+ if ((*ext = strrchr(filename, '.')) && (*ext > last_slash)) {
+ ast_log(LOG_NOTICE, "Found a dot. *ext is %s\n", *ext);
+ **ext = '\0';
+ *ext = *ext + 1;
+ ast_log(LOG_NOTICE, "After increment *ext is %s\n", *ext);
} else {
- ext = "raw";
+ *ext = "raw";
}
- if (!(*fs = ast_writefile(filename, ext, NULL, *oflags, 0, 0666))) {
- ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, ext);
+ if (!(*fs = ast_writefile(filename, *ext, NULL, *oflags, 0, 0666))) {
+ ast_log(LOG_ERROR, "Cannot open %s.%s\n", filename, *ext);
*errflag = 1;
} else {
struct ast_filestream *tmp = *fs;
@@ -436,6 +587,9 @@ static void mixmonitor_save_prep(struct mixmonitor *mixmonitor, char *filename,
static void *mixmonitor_thread(void *obj)
{
struct mixmonitor *mixmonitor = obj;
+ char *fs_ext = "";
+ char *fs_read_ext = "";
+ char *fs_write_ext = "";
struct ast_filestream **fs = NULL;
struct ast_filestream **fs_read = NULL;
@@ -457,9 +611,9 @@ static void *mixmonitor_thread(void *obj)
fs_write = &mixmonitor->mixmonitor_ds->fs_write;
ast_mutex_lock(&mixmonitor->mixmonitor_ds->lock);
- mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag);
- mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag);
- mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag);
+ mixmonitor_save_prep(mixmonitor, mixmonitor->filename, fs, &oflags, &errflag, &fs_ext);
+ mixmonitor_save_prep(mixmonitor, mixmonitor->filename_read, fs_read, &oflags, &errflag, &fs_read_ext);
+ mixmonitor_save_prep(mixmonitor, mixmonitor->filename_write, fs_write, &oflags, &errflag, &fs_write_ext);
ast_format_set(&format_slin, ast_format_slin_by_rate(mixmonitor->mixmonitor_ds->samp_rate), 0);
@@ -554,6 +708,27 @@ static void *mixmonitor_thread(void *obj)
}
ast_verb(2, "End MixMonitor Recording %s\n", mixmonitor->name);
+
+ if (!AST_LIST_EMPTY(&mixmonitor->recipient_list)) {
+ if (ast_strlen_zero(fs_ext)) {
+ ast_log(LOG_ERROR, "No file extension set for Mixmonitor %s. Skipping copy to voicemail.\n",
+ mixmonitor -> name);
+ } else {
+ ast_verb(3, "Copying recordings for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+ copy_to_voicemail(mixmonitor, fs_ext, mixmonitor->filename);
+ }
+ if (!ast_strlen_zero(fs_read_ext)) {
+ ast_verb(3, "Copying read recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+ copy_to_voicemail(mixmonitor, fs_read_ext, mixmonitor->filename_read);
+ }
+ if (!ast_strlen_zero(fs_write_ext)) {
+ ast_verb(3, "Copying write recording for Mixmonitor %s to voicemail recipients\n", mixmonitor->name);
+ copy_to_voicemail(mixmonitor, fs_write_ext, mixmonitor->filename_write);
+ }
+ } else {
+ ast_debug(3, "No recipients to forward monitor to, moving on.\n");
+ }
+
mixmonitor_free(mixmonitor);
return NULL;
}
@@ -597,7 +772,8 @@ static int setup_mixmonitor_ds(struct mixmonitor *mixmonitor, struct ast_channel
static void launch_monitor_thread(struct ast_channel *chan, const char *filename,
unsigned int flags, int readvol, int writevol,
const char *post_process, const char *filename_write,
- char *filename_read, const char *uid_channel_var)
+ char *filename_read, const char *uid_channel_var,
+ const char *recipients)
{
pthread_t thread;
struct mixmonitor *mixmonitor;
@@ -623,6 +799,12 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
return;
}
+ /* Now that the struct has been calloced, go ahead and initialize the string fields. */
+ if (ast_string_field_init(mixmonitor, 512)) {
+ mixmonitor_free(mixmonitor);
+ return;
+ }
+
/* Setup the actual spy before creating our thread */
if (ast_audiohook_init(&mixmonitor->audiohook, AST_AUDIOHOOK_TYPE_SPY, mixmonitor_spy_type, 0)) {
mixmonitor_free(mixmonitor);
@@ -650,7 +832,6 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
}
ast_free(datastore_id);
-
mixmonitor->name = ast_strdup(ast_channel_name(chan));
if (!ast_strlen_zero(postprocess2)) {
@@ -669,6 +850,35 @@ static void launch_monitor_thread(struct ast_channel *chan, const char *filename
mixmonitor->filename_read = ast_strdup(filename_read);
}
+ if (!ast_strlen_zero(recipients)) {
+ char callerid[256];
+ struct ast_party_connected_line *connected;
+
+ ast_channel_lock(chan);
+
+ /* We use the connected line of the invoking channel for caller ID. */
+
+ connected = ast_channel_connected(chan);
+ ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", connected->id.name.valid,
+ connected->id.name.str, connected->id.number.valid,
+ connected->id.number.str);
+ ast_callerid_merge(callerid, sizeof(callerid),
+ S_COR(connected->id.name.valid, connected->id.name.str, NULL),
+ S_COR(connected->id.number.valid, connected->id.number.str, NULL),
+ "Unknown");
+
+ ast_string_field_set(mixmonitor, call_context, ast_channel_context(chan));
+ ast_string_field_set(mixmonitor, call_macrocontext, ast_channel_macrocontext(chan));
+ ast_string_field_set(mixmonitor, call_extension, ast_channel_exten(chan));
+ ast_string_field_set(mixmonitor, call_callerchan, ast_channel_name(chan));
+ ast_string_field_set(mixmonitor, call_callerid, callerid);
+ mixmonitor->call_priority = ast_channel_priority(chan);
+
+ ast_channel_unlock(chan);
+
+ add_vm_recipients_from_string(mixmonitor, recipients);
+ }
+
ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
if (readvol)
@@ -723,6 +933,7 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
char *uid_channel_var = NULL;
struct ast_flags flags = { 0 };
+ char *recipients = NULL;
char *parse;
AST_DECLARE_APP_ARGS(args,
AST_APP_ARG(filename);
@@ -774,6 +985,14 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
}
}
+ if (ast_test_flag(&flags, MUXFLAG_VMRECIPIENTS)) {
+ if (ast_strlen_zero(opts[OPT_ARG_VMRECIPIENTS])) {
+ ast_log(LOG_WARNING, "No voicemail recipients were specified for the vm copy ('m') option.\n");
+ } else {
+ recipients = ast_strdupa(opts[OPT_ARG_VMRECIPIENTS]);
+ }
+ }
+
if (ast_test_flag(&flags, MUXFLAG_WRITE)) {
filename_write = ast_strdupa(filename_parse(opts[OPT_ARG_WRITENAME], filename_buffer, sizeof(filename_buffer)));
}
@@ -799,7 +1018,16 @@ static int mixmonitor_exec(struct ast_channel *chan, const char *data)
}
pbx_builtin_setvar_helper(chan, "MIXMONITOR_FILENAME", args.filename);
- launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process, filename_write, filename_read, uid_channel_var);
+ launch_monitor_thread(chan,
+ args.filename,
+ flags.flags,
+ readvol,
+ writevol,
+ args.post_process,
+ filename_write,
+ filename_read,
+ uid_channel_var,
+ recipients);
return 0;
}
diff --git a/apps/app_queue.c b/apps/app_queue.c
index f19ca3fdbf..cb96749594 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -1703,13 +1703,19 @@ static int extensionstate2devicestate(int state)
return state;
}
-static int extension_state_cb(const char *context, const char *exten, enum ast_extension_states state, void *data)
+static int extension_state_cb(char *context, char *exten, struct ast_state_cb_info *info, void *data)
{
struct ao2_iterator miter, qiter;
struct member *m;
struct call_queue *q;
+ int state = info->exten_state;
int found = 0, device_state = extensionstate2devicestate(state);
+ /* only interested in extension state updates involving device states */
+ if (info->reason != AST_HINT_UPDATE_DEVICE) {
+ return 0;
+ }
+
qiter = ao2_iterator_init(queues, 0);
while ((q = ao2_t_iterator_next(&qiter, "Iterate through queues"))) {
ao2_lock(q);
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index eec08a484d..043d934759 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -113,12 +113,14 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/module.h"
#include "asterisk/adsi.h"
#include "asterisk/app.h"
+#include "asterisk/app_voicemail.h"
#include "asterisk/manager.h"
#include "asterisk/dsp.h"
#include "asterisk/localtime.h"
#include "asterisk/cli.h"
#include "asterisk/utils.h"
#include "asterisk/stringfields.h"
+#include "asterisk/strings.h"
#include "asterisk/smdi.h"
#include "asterisk/astobj2.h"
#include "asterisk/event.h"
@@ -334,6 +336,30 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+
+ Play a single voice mail msg from a mailbox by msg id.
+
+
+
+
+
+
+
+ The msg id of the msg to play back.
+
+
+
+ This application sets the following channel variable upon completion:
+
+
+ The status of the playback attempt as a text string.
+
+
+
+
+
+
Play the name of a voicemail user
@@ -469,7 +495,8 @@ static int save_body(BODY *body, struct vm_state *vms, char *section, char *form
static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream);
static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
-static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
+static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id);
+static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder);
static void update_messages_by_imapuser(const char *user, unsigned long number);
static int vm_delete(char *file);
@@ -552,7 +579,6 @@ static AST_LIST_HEAD_STATIC(vmstates, vmstate);
#define ERROR_LOCK_PATH -100
#define OPERATOR_EXIT 300
-
enum vm_box {
NEW_FOLDER,
OLD_FOLDER,
@@ -600,6 +626,25 @@ AST_APP_OPTIONS(vm_app_options, {
AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
});
+static const char * const mailbox_folders[] = {
+#ifdef IMAP_STORAGE
+ imapfolder,
+#else
+ "INBOX",
+#endif
+ "Old",
+ "Work",
+ "Family",
+ "Friends",
+ "Cust1",
+ "Cust2",
+ "Cust3",
+ "Cust4",
+ "Cust5",
+ "Deleted",
+ "Urgent",
+};
+
static int load_config(int reload);
#ifdef TEST_FRAMEWORK
static int load_config_from_memory(int reload, struct ast_config *cfg, struct ast_config *ucfg);
@@ -793,28 +838,31 @@ static char odbc_database[80];
static char odbc_table[80];
#define RETRIEVE(a,b,c,d) retrieve_file(a,b)
#define DISPOSE(a,b) remove_file(a,b)
-#define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
+#define STORE(a,b,c,d,e,f,g,h,i,j,k) store_file(a,b,c,d)
#define EXISTS(a,b,c,d) (message_exists(a,b))
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
#define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
#define DELETE(a,b,c,d) (delete_file(a,b))
+#define UPDATE_MSG_ID(a, b, c, d, e, f) (odbc_update_msg_id((a), (b), (c)))
#else
#ifdef IMAP_STORAGE
#define DISPOSE(a,b) (imap_remove_file(a,b))
-#define STORE(a,b,c,d,e,f,g,h,i,j) (imap_store_file(a,b,c,d,e,f,g,h,i,j))
+#define STORE(a,b,c,d,e,f,g,h,i,j,k) (imap_store_file(a,b,c,d,e,f,g,h,i,j,k))
#define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
#define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
#define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
#define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
+#define UPDATE_MSG_ID(a, b, c, d, e, f) (vm_imap_update_msg_id((a), (b), (c), (d), (e), (f)))
#else
#define RETRIEVE(a,b,c,d)
#define DISPOSE(a,b)
-#define STORE(a,b,c,d,e,f,g,h,i,j)
+#define STORE(a,b,c,d,e,f,g,h,i,j,k)
#define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
#define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
#define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
#define DELETE(a,b,c,d) (vm_delete(c))
+#define UPDATE_MSG_ID(a, b, c, d, e, f)
#endif
#endif
@@ -852,6 +900,8 @@ static char *app2 = "VoiceMailMain";
static char *app3 = "MailboxExists";
static char *app4 = "VMAuthenticate";
+static char *playmsg_app = "VoiceMailPlayMsg";
+
static char *sayname_app = "VMSayName";
static AST_LIST_HEAD_STATIC(users, ast_vm_user);
@@ -976,21 +1026,39 @@ static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
/* Forward declarations - generic */
static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
+static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu);
static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain);
static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
- signed char record_gain, struct vm_state *vms, char *flag);
+ signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id);
static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname, const char *flag);
-static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag);
+static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag, const char *msg_id);
static void apply_options(struct ast_vm_user *vmu, const char *options);
static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum);
static int is_valid_dtmf(const char *key);
static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
static int write_password_to_file(const char *secretfn, const char *password);
+struct ast_str *vm_mailbox_snapshot_str(const char *mailbox, const char *context);
static const char *substitute_escapes(const char *value);
+static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu);
+/*!
+ * Place a message in the indicated folder
+ *
+ * \param vmu Voicemail user
+ * \param vms Current voicemail state for the user
+ * \param msg The message number to save
+ * \param box The folder into which the message should be saved
+ * \param[out] newmsg The new message number of the saved message
+ * \param move Tells whether to copy or to move the message
+ *
+ * \note the "move" parameter is only honored for IMAP voicemail presently
+ * \retval 0 Success
+ * \revval other Failure
+ */
+static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move);
struct ao2_container *inprocess_container;
@@ -1790,25 +1858,6 @@ static int create_dirpath(char *dest, int len, const char *context, const char *
return 0;
}
-static const char * const mailbox_folders[] = {
-#ifdef IMAP_STORAGE
- imapfolder,
-#else
- "INBOX",
-#endif
- "Old",
- "Work",
- "Family",
- "Friends",
- "Cust1",
- "Cust2",
- "Cust3",
- "Cust4",
- "Cust5",
- "Deleted",
- "Urgent",
-};
-
static const char *mbox(struct ast_vm_user *vmu, int id)
{
#ifdef IMAP_STORAGE
@@ -1909,6 +1958,79 @@ static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
ast_mutex_unlock(&vms->lock);
}
+static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder)
+{
+ struct ast_channel *chan;
+ char *cid;
+ char *cid_name;
+ char *cid_num;
+ struct vm_state *vms;
+ const char *duration_str;
+ int duration = 0;
+
+ /*
+ * First, get things initially set up. If any of this fails, then
+ * back out before doing anything substantial
+ */
+ vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0);
+ if (!vms) {
+ return;
+ }
+
+ if (open_mailbox(vms, vmu, folder)) {
+ return;
+ }
+
+ chan = ast_dummy_channel_alloc();
+ if (!chan) {
+ close_mailbox(vms, vmu);
+ return;
+ }
+
+ /*
+ * We need to make sure the new message we save has the same
+ * callerid, flag, and duration as the original message
+ */
+ cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid"));
+
+ if (!ast_strlen_zero(cid)) {
+ ast_callerid_parse(cid, &cid_name, &cid_num);
+ ast_party_caller_init(ast_channel_caller(chan));
+ if (!ast_strlen_zero(cid_name)) {
+ ast_channel_caller(chan)->id.name.valid = 1;
+ ast_channel_caller(chan)->id.name.str = ast_strdup(cid_name);
+ }
+ if (!ast_strlen_zero(cid_num)) {
+ ast_channel_caller(chan)->id.number.valid = 1;
+ ast_channel_caller(chan)->id.number.str = ast_strdup(cid_num);
+ }
+ }
+
+ duration_str = ast_variable_retrieve(msg_cfg, "message", "duration");
+
+ if (!ast_strlen_zero(duration_str)) {
+ sscanf(duration_str, "%30d", &duration);
+ }
+
+ /*
+ * IMAP messages cannot be altered once delivered. So we have to delete the
+ * current message and then re-add it with the updated message ID.
+ *
+ * Furthermore, there currently is no atomic way to create a new message and to
+ * store it in an arbitrary folder. So we have to save it to the INBOX and then
+ * move to the appropriate folder.
+ */
+ if (!imap_store_file(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, vmfmts,
+ duration, vms, ast_variable_retrieve(msg_cfg, "message", "flag"), msg_id)) {
+ if (folder != NEW_FOLDER) {
+ save_to_folder(vmu, vms, msgnum, folder, NULL, 1);
+ }
+ vm_imap_delete(dir, msgnum, vmu);
+ }
+ close_mailbox(vms, vmu);
+ ast_channel_unref(chan);
+}
+
static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
{
struct vm_state *vms_p;
@@ -2086,20 +2208,30 @@ static int imap_retrieve_file(const char *dir, const int msgnum, const char *mai
fprintf(text_file_ptr, "%s\n", "[message]");
- get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
- fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
- fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
- fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
- fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
- fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
- fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
- get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
- fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
+ }
+ if (get_header_by_tag(header_content, "X-Asterisk-VM-Message-ID:", buf, sizeof(buf))) {
+ fprintf(text_file_ptr, "msg_id=%s\n", S_OR(buf, ""));
+ }
fclose(text_file_ptr);
exit:
@@ -2266,7 +2398,9 @@ static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, str
check_quota(vms, vmu->imapfolder);
if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
- ast_play_and_wait(chan, "vm-mailboxfull");
+ if (chan) {
+ ast_play_and_wait(chan, "vm-mailboxfull");
+ }
return -1;
}
@@ -2274,8 +2408,10 @@ static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, str
ast_debug(3, "Checking message number quota: mailbox has %d messages, maximum is set to %d, current messages %d\n", msgnum, vmu->maxmsg, inprocess_count(vmu->mailbox, vmu->context, 0));
if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) {
ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u >= %u)\n", msgnum, vmu->maxmsg);
- ast_play_and_wait(chan, "vm-mailboxfull");
- pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ if (chan) {
+ ast_play_and_wait(chan, "vm-mailboxfull");
+ pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
+ }
return -1;
}
@@ -2300,7 +2436,7 @@ static int messagecount(const char *context, const char *mailbox, const char *fo
}
}
-static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
+static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id)
{
char *myserveremail = serveremail;
char fn[PATH_MAX];
@@ -2389,7 +2525,7 @@ static int imap_store_file(const char *dir, const char *mailboxuser, const char
make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
- fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
+ fn, introfn, fmt, duration, 1, chan, NULL, 1, flag, msg_id);
/* read mail file to memory */
len = ftell(p);
rewind(p);
@@ -2567,7 +2703,7 @@ static int has_voicemail(const char *mailbox, const char *folder)
*
* \return zero on success, -1 on error.
*/
-static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag)
+static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag, const char *dest_folder)
{
struct vm_state *sendvms = NULL;
char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
@@ -3826,6 +3962,7 @@ struct insert_data {
const char *mailboxcontext;
const char *category;
const char *flag;
+ const char *msg_id;
};
static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
@@ -3852,8 +3989,9 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
+ SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msg_id), 0, (void *) data->msg_id, 0, NULL);
if (!ast_strlen_zero(data->category)) {
- SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
+ SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
}
res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
@@ -3894,7 +4032,7 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
struct ast_config *cfg = NULL;
struct odbc_obj *obj;
struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext,
- .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "" };
+ .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "", .msg_id = "" };
struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
delete_file(dir, msgnum);
@@ -3946,6 +4084,9 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
idata.flag = "";
}
+ if (!(idata.msg_id = ast_variable_retrieve(cfg, "message", "msg_id"))) {
+ idata.msg_id = "";
+ }
}
fdlen = lseek(fd, 0, SEEK_END);
if (fdlen < 0 || lseek(fd, 0, SEEK_SET) < 0) {
@@ -3963,9 +4104,9 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
idata.datalen = idata.indlen = fdlen;
if (!ast_strlen_zero(idata.category))
- snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
else
- snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
SQLFreeHandle (SQL_HANDLE_STMT, stmt);
@@ -3986,6 +4127,33 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
return res;
}
+static void odbc_update_msg_id(char *dir, int msg_num, char *msg_id)
+{
+ SQLHSTMT stmt;
+ char sql[PATH_MAX];
+ struct odbc_obj *obj;
+ char msg_num_str[20];
+ char *argv[] = { msg_id, dir, msg_num_str };
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 3, .argv = argv };
+
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (!obj) {
+ ast_log(LOG_WARNING, "Unable to update message ID for message %d in %s\n", msg_num, dir);
+ return;
+ }
+
+ snprintf(msg_num_str, sizeof(msg_num_str), "%d", msg_num);
+ snprintf(sql, sizeof(sql), "UPDATE %s SET msg_id=? WHERE dir=? AND msgnum=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ } else {
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ }
+ ast_odbc_release_obj(obj);
+ return;
+}
+
/*!
* \brief Renames a message in a mailbox folder.
* \param sdir The folder of the message to be renamed.
@@ -4614,7 +4782,25 @@ static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, con
*
* The email body, and base 64 encoded attachement (if any) are stored to the file identified by *p. This method does not actually send the email. That is done by invoking the configure 'mailcmd' and piping this generated file into it, or with the sendemail() function.
*/
-static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag)
+static void make_email_file(FILE *p,
+ char *srcemail,
+ struct ast_vm_user *vmu,
+ int msgnum,
+ char *context,
+ char *mailbox,
+ const char *fromfolder,
+ char *cidnum,
+ char *cidname,
+ char *attach,
+ char *attach2,
+ char *format,
+ int duration,
+ int attach_user_voicemail,
+ struct ast_channel *chan,
+ const char *category,
+ int imap,
+ const char *flag,
+ const char *msg_id)
{
char date[256];
char host[MAXHOSTNAMELEN] = "";
@@ -4758,8 +4944,8 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in
#endif
/* flag added for Urgent */
fprintf(p, "X-Asterisk-VM-Flag: %s" ENDL, flag);
- fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, ast_channel_priority(chan));
- fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, ast_channel_name(chan));
+ fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan ? ast_channel_priority(chan) : 0);
+ fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan ? ast_channel_name(chan) : "");
fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, enc_cidnum);
fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, enc_cidname);
fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
@@ -4771,6 +4957,7 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in
fprintf(p, "X-Asterisk-VM-Message-Type: %s" ENDL, msgnum > -1 ? "Message" : greeting_attachment);
fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long) time(NULL));
+ fprintf(p, "X-Asterisk-VM-Message-ID: %s" ENDL, msg_id);
}
if (!ast_strlen_zero(cidnum)) {
fprintf(p, "X-Asterisk-CallerID: %s" ENDL, enc_cidnum);
@@ -4937,7 +5124,23 @@ static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format,
return 0;
}
-static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, const char *flag)
+static int sendmail(char *srcemail,
+ struct ast_vm_user *vmu,
+ int msgnum,
+ char *context,
+ char *mailbox,
+ const char *fromfolder,
+ char *cidnum,
+ char *cidname,
+ char *attach,
+ char *attach2,
+ char *format,
+ int duration,
+ int attach_user_voicemail,
+ struct ast_channel *chan,
+ const char *category,
+ const char *flag,
+ const char *msg_id)
{
FILE *p = NULL;
char tmp[80] = "/tmp/astmail-XXXXXX";
@@ -4963,7 +5166,7 @@ static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *c
ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
return -1;
} else {
- make_email_file(p, srcemail, vmu, msgnum, context, mailbox, fromfolder, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0, flag);
+ make_email_file(p, srcemail, vmu, msgnum, context, mailbox, fromfolder, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0, flag, msg_id);
fclose(p);
snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
ast_safe_system(tmp2);
@@ -5407,7 +5610,7 @@ static int has_voicemail(const char *mailbox, const char *folder)
*
* \return zero on success, -1 on error.
*/
-static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag)
+static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, const char *flag, const char *dest_folder)
{
char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
const char *frombox = mbox(vmu, imbox);
@@ -5419,6 +5622,8 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i
if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) { /* If urgent, copy to Urgent folder */
userfolder = "Urgent";
+ } else if (!ast_strlen_zero(dest_folder)) {
+ userfolder = dest_folder;
} else {
userfolder = "INBOX";
}
@@ -5440,7 +5645,7 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i
if (recipmsgnum < recip->maxmsg - (imbox ? 0 : inprocess_count(vmu->mailbox, vmu->context, 0))) {
make_file(topath, sizeof(topath), todir, recipmsgnum);
#ifndef ODBC_STORAGE
- if (EXISTS(fromdir, msgnum, frompath, ast_channel_language(chan))) {
+ if (EXISTS(fromdir, msgnum, frompath, chan ? ast_channel_language(chan) : "")) {
COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
} else {
#endif
@@ -5448,7 +5653,7 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i
* exists in the database, but we want to force copying from the
* filesystem (since only the FS contains the prepend). */
copy_plain_file(frompath, topath);
- STORE(todir, recip->mailbox, recip->context, recipmsgnum, chan, recip, fmt, duration, NULL, NULL);
+ STORE(todir, recip->mailbox, recip->context, recipmsgnum, chan, recip, fmt, duration, NULL, NULL, NULL);
vm_delete(topath);
#ifndef ODBC_STORAGE
}
@@ -5458,11 +5663,14 @@ static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int i
res = -1;
}
ast_unlock_path(todir);
- notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt,
- S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
- S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
- flag);
-
+ if (chan) {
+ struct ast_party_caller *caller = ast_channel_caller(chan);
+ notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt,
+ S_COR(caller->id.number.valid, caller->id.number.str, NULL),
+ S_COR(caller->id.name.valid, caller->id.name.str, NULL),
+ flag);
+ }
+
return res;
}
#endif
@@ -5664,6 +5872,301 @@ struct leave_vm_options {
char *exitcontext;
};
+/*!
+ * \internal
+ * \brief Creates a voicemail based on a specified file to a mailbox.
+ * \param recdata A vm_recording_data containing filename and voicemail txt info.
+ * \retval -1 failure
+ * \retval 0 success
+ *
+ * This is installed to the app.h voicemail functions and accommodates all voicemail
+ * storage methods. It should probably be broken out along with leave_voicemail at
+ * some point in the future.
+ *
+ * This function currently only works for a single recipient and only uses the format
+ * specified in recording_ext.
+ */
+static int msg_create_from_file(struct ast_vm_recording_data *recdata)
+{
+ /* voicemail recipient structure */
+ struct ast_vm_user *recipient; /* points to svm once it's been created */
+ struct ast_vm_user svm; /* struct storing the voicemail recipient */
+
+ /* File paths */
+ char tmpdir[PATH_MAX]; /* directory temp files are stored in */
+ char tmptxtfile[PATH_MAX]; /* tmp file for voicemail txt file */
+ char desttxtfile[PATH_MAX]; /* final destination for txt file */
+ char tmpaudiofile[PATH_MAX]; /* tmp file where audio is stored */
+ char dir[PATH_MAX]; /* destination for tmp files on completion */
+ char destination[PATH_MAX]; /* destination with msgXXXX. Basically /msgXXXX */
+
+ /* stuff that only seems to be needed for IMAP */
+ #ifdef IMAP_STORAGE
+ struct vm_state *vms = NULL;
+ char ext_context[256] = "";
+ char *fmt = ast_strdupa(recdata->recording_ext);
+ int newmsgs = 0;
+ int oldmsgs = 0;
+ #endif
+
+ /* miscellaneous operational variables */
+ int res = 0; /* Used to store error codes from functions */
+ int txtdes /* File descriptor for the text file used to write the voicemail info */;
+ FILE *txt; /* FILE pointer to text file used to write the voicemail info */
+ char date[256]; /* string used to hold date of the voicemail (only used for ODBC) */
+ int msgnum; /* the 4 digit number designated to the voicemail */
+ int duration = 0; /* Length of the audio being recorded in seconds */
+ struct ast_filestream *recording_fs; /*used to read the recording to get duration data */
+
+ /* We aren't currently doing anything with category, since it comes from a channel variable and
+ * this function doesn't use channels, but this function could add that as an argument later. */
+ const char *category = NULL; /* pointless for now */
+ char msg_id[256];
+
+ /* Start by checking to see if the file actually exists... */
+ if (!(ast_fileexists(recdata->recording_file, recdata->recording_ext, NULL))) {
+ ast_log(LOG_ERROR, "File: %s not found.\n", recdata->recording_file);
+ return -1;
+ }
+
+ if (!(recipient = find_user(&svm, recdata->context, recdata->mailbox))) {
+ ast_log(LOG_ERROR, "No entry in voicemail config file for '%s@%s'\n", recdata->mailbox, recdata->context);
+ return -1;
+ }
+
+ /* determine duration in seconds */
+ if ((recording_fs = ast_readfile(recdata->recording_file, recdata->recording_ext, NULL, 0, 0, VOICEMAIL_DIR_MODE))) {
+ if (!ast_seekstream(recording_fs, 0, SEEK_END)) {
+ long framelength = ast_tellstream(recording_fs);
+ struct ast_format result;
+ /* XXX This use of ast_getformatbyname seems incorrect here. The file extension does not necessarily correspond
+ * to the name of the format. For instance, if "raw" were passed in, I don't think ast_getformatbyname would
+ * find the slinear format
+ */
+ duration = (int) (framelength / ast_format_rate(ast_getformatbyname(recdata->recording_ext, &result)));
+ }
+ }
+
+ /* If the duration was below the minimum duration for the user, let's just drop the whole thing now */
+ if (duration < recipient->minsecs) {
+ ast_log(LOG_NOTICE, "Copying recording to voicemail %s@%s skipped because duration was shorter than "
+ "minmessage of recipient\n", recdata->mailbox, recdata->context);
+ return -1;
+ }
+
+ /* Note that this number must be dropped back to a net sum of zero before returning from this function */
+
+ if ((res = create_dirpath(tmpdir, sizeof(tmpdir), recipient->context, recdata->mailbox, "tmp"))) {
+ ast_log(LOG_ERROR, "Failed to make directory.\n");
+ }
+
+ snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
+ txtdes = mkstemp(tmptxtfile);
+ if (txtdes < 0) {
+ chmod(tmptxtfile, VOICEMAIL_FILE_MODE & ~my_umask);
+ /* Something screwed up. Abort. */
+ ast_log(AST_LOG_ERROR, "Unable to create message file: %s\n", strerror(errno));
+ free_user(recipient);
+ return -1;
+ }
+
+ /* Store information */
+ txt = fdopen(txtdes, "w+");
+ if (txt) {
+ char msg_id_hash[256];
+
+ /* Every voicemail msg gets its own unique msg id. The msg id is the originate time
+ * plus a hash of the extension, context, and callerid of the channel leaving the msg */
+
+ snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", recdata->call_extension,
+ recdata->call_context, recdata->call_callerid);
+ snprintf(msg_id, sizeof(msg_id), "%ld-%d", (long) time(NULL), ast_str_hash(msg_id_hash));
+
+ get_date(date, sizeof(date));
+ fprintf(txt,
+ ";\n"
+ "; Message Information file\n"
+ ";\n"
+ "[message]\n"
+ "origmailbox=%s\n"
+ "context=%s\n"
+ "macrocontext=%s\n"
+ "exten=%s\n"
+ "rdnis=Unknown\n"
+ "priority=%d\n"
+ "callerchan=%s\n"
+ "callerid=%s\n"
+ "origdate=%s\n"
+ "origtime=%ld\n"
+ "category=%s\n"
+ "msg_id=%s\n"
+ "flag=\n" /* flags not supported in copy from file yet */
+ "duration=%d\n", /* Don't have any reliable way to get duration of file. */
+
+ recdata->mailbox,
+ S_OR(recdata->call_context, ""),
+ S_OR(recdata->call_macrocontext, ""),
+ S_OR(recdata->call_extension, ""),
+ recdata->call_priority,
+ S_OR(recdata->call_callerchan, "Unknown"),
+ S_OR(recdata->call_callerid, "Unknown"),
+ date, (long) time(NULL),
+ S_OR(category, ""),
+ msg_id,
+ duration);
+
+ /* Since we are recording from a file, we shouldn't need to do anything else with
+ * this txt file */
+ fclose(txt);
+
+ } else {
+ ast_log(LOG_WARNING, "Error opening text file for output\n");
+ if (ast_check_realtime("voicemail_data")) {
+ ast_destroy_realtime("voicemail_data", "filename", tmptxtfile, SENTINEL);
+ }
+ free_user(recipient);
+ return -1;
+ }
+
+ /* At this point, the actual creation of a voicemail message should be finished.
+ * Now we just need to copy the files being recorded into the receiving folder. */
+
+ create_dirpath(dir, sizeof(dir), recipient->context, recipient->mailbox, recdata->folder);
+
+#ifdef IMAP_STORAGE
+ /* make recipient info into an inboxcount friendly string */
+ snprintf(ext_context, sizeof(ext_context), "%s@%s", recipient->mailbox, recipient->context);
+
+ /* Is ext a mailbox? */
+ /* must open stream for this user to get info! */
+ res = inboxcount(ext_context, &newmsgs, &oldmsgs);
+ if (res < 0) {
+ ast_log(LOG_NOTICE, "Can not leave voicemail, unable to count messages\n");
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+ if (!(vms = get_vm_state_by_mailbox(recipient->mailbox, recipient->context, 0))) {
+ /* It is possible under certain circumstances that inboxcount did not
+ * create a vm_state when it was needed. This is a catchall which will
+ * rarely be used.
+ */
+ if (!(vms = create_vm_state_from_user(recipient))) {
+ ast_log(LOG_ERROR, "Couldn't allocate necessary space\n");
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+ }
+ vms->newmessages++;
+
+ /* here is a big difference! We add one to it later */
+ msgnum = newmsgs + oldmsgs;
+ ast_debug(3, "Messagecount set to %d\n", msgnum);
+ snprintf(destination, sizeof(destination), "%simap/msg%s%04d", VM_SPOOL_DIR, recipient->mailbox, msgnum);
+
+ /* Check to see if we have enough room in the mailbox. If not, spit out an error and end
+ * Note that imap_check_limits raises inprocess_count if successful */
+ if ((res = imap_check_limits(NULL, vms, recipient, msgnum))) {
+ ast_log(LOG_NOTICE, "Didn't copy to voicemail. Mailbox for %s@%s is full.\n", recipient->mailbox, recipient->context);
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+
+#else
+
+ /* Check to see if the mailbox is full for ODBC/File storage */
+ ast_debug(3, "mailbox = %d : inprocess = %d\n", count_messages(recipient, dir),
+ inprocess_count(recipient->mailbox, recipient->context, 0));
+ if (count_messages(recipient, dir) > recipient->maxmsg - inprocess_count(recipient->mailbox, recipient->context, +1)) {
+ ast_log(AST_LOG_WARNING, "Didn't copy to voicemail. Mailbox for %s@%s is full.\n", recipient->mailbox, recipient->context);
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+
+ msgnum = last_message_index(recipient, dir) + 1;
+#endif
+
+ /* Lock the directory receiving the voicemail since we want it to still exist when we attempt to copy the voicemail.
+ * We need to unlock it before we return. */
+ if (vm_lock_path(dir)) {
+ ast_log(LOG_ERROR, "Couldn't lock directory %s. Voicemail will be lost.\n", dir);
+ /* Delete files */
+ ast_filedelete(tmptxtfile, NULL);
+ unlink(tmptxtfile);
+ free_user(recipient);
+ return -1;
+ }
+
+ make_file(destination, sizeof(destination), dir, msgnum);
+
+ make_file(tmpaudiofile, sizeof(tmpaudiofile), tmpdir, msgnum);
+
+ if (ast_filecopy(recdata->recording_file, tmpaudiofile, recdata->recording_ext)) {
+ ast_log(LOG_ERROR, "Audio file failed to copy to tmp dir. Probably low disk space.\n");
+
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+ ast_unlock_path(dir);
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+
+ /* Alright, try to copy to the destination folder now. */
+ if (ast_filerename(tmpaudiofile, destination, recdata->recording_ext)) {
+ ast_log(LOG_ERROR, "Audio file failed to move to destination directory. Permissions/Overlap?\n");
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+ ast_unlock_path(dir);
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return -1;
+ }
+
+ snprintf(desttxtfile, sizeof(desttxtfile), "%s.txt", destination);
+ rename(tmptxtfile, desttxtfile);
+
+ if (chmod(desttxtfile, VOICEMAIL_FILE_MODE) < 0) {
+ ast_log(AST_LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", desttxtfile, strerror(errno));
+ }
+
+
+ ast_unlock_path(dir);
+ inprocess_count(recipient->mailbox, recipient->context, -1);
+
+ /* If we copied something, we should store it either to ODBC or IMAP if we are using those. The STORE macro allows us
+ * to do both with one line and is also safe to use with file storage mode. Also, if we are using ODBC, now is a good
+ * time to create the voicemail database entry. */
+ if (ast_fileexists(destination, NULL, NULL) > 0) {
+ if (ast_check_realtime("voicemail_data")) {
+ get_date(date, sizeof(date));
+ ast_store_realtime("voicemail_data",
+ "origmailbox", recdata->mailbox,
+ "context", S_OR(recdata->context, ""),
+ "macrocontext", S_OR(recdata->call_macrocontext, ""),
+ "exten", S_OR(recdata->call_extension, ""),
+ "priority", recdata->call_priority,
+ "callerchan", S_OR(recdata->call_callerchan, "Unknown"),
+ "callerid", S_OR(recdata->call_callerid, "Unknown"),
+ "origdate", date,
+ "origtime", time(NULL),
+ "category", S_OR(category, ""),
+ "filename", tmptxtfile,
+ "duration", duration,
+ SENTINEL);
+ }
+
+ STORE(dir, recipient->mailbox, recipient->context, msgnum, NULL, recipient, fmt, 0, vms, "", msg_id);
+ }
+
+ free_user(recipient);
+ unlink(tmptxtfile);
+ return 0;
+}
+
/*!
* \brief Prompts the user and records a voicemail to a mailbox.
* \param chan
@@ -5946,6 +6449,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
/* The meat of recording the message... All the announcements and beeps have been played*/
ast_copy_string(fmt, vmfmts, sizeof(fmt));
if (!ast_strlen_zero(fmt)) {
+ char msg_id[256] = "";
msgnum = 0;
#ifdef IMAP_STORAGE
@@ -6038,6 +6542,13 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
/* Store information */
txt = fdopen(txtdes, "w+");
if (txt) {
+ char msg_id_hash[256] = "";
+
+ /* Every voicemail msg gets its own unique msg id. The msg id is the originate time
+ * plus a hash of the extension, context, and callerid of the channel leaving the msg */
+ snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", ast_channel_exten(chan), ast_channel_context(chan), callerid);
+ snprintf(msg_id, sizeof(msg_id), "%ld-%d", (long) time(NULL), ast_str_hash(msg_id_hash));
+
get_date(date, sizeof(date));
ast_callerid_merge(callerid, sizeof(callerid),
S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
@@ -6058,7 +6569,8 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
"callerid=%s\n"
"origdate=%s\n"
"origtime=%ld\n"
- "category=%s\n",
+ "category=%s\n"
+ "msg_id=%s\n",
ext,
ast_channel_context(chan),
ast_channel_macrocontext(chan),
@@ -6069,7 +6581,8 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
ast_channel_name(chan),
callerid,
date, (long) time(NULL),
- category ? category : "");
+ category ? category : "",
+ msg_id);
} else {
ast_log(AST_LOG_WARNING, "Error opening text file for output\n");
inprocess_count(vmu->mailbox, vmu->context, -1);
@@ -6079,7 +6592,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
goto leave_vm_out;
}
- res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain, vms, flag);
+ res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain, vms, flag, msg_id);
if (txt) {
fprintf(txt, "flag=%s\n", flag);
@@ -6141,7 +6654,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
* ODBC storage does the entire copy with SQL.
*/
if (ast_fileexists(fn, NULL, NULL) > 0) {
- STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms, flag);
+ STORE(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, fmt, duration, vms, flag, msg_id);
}
/* Are there to be more recipients of this message? */
@@ -6156,7 +6669,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
cntx++;
}
if ((recip = find_user(&recipu, cntx, exten))) {
- copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag);
+ copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag, NULL);
free_user(recip);
}
}
@@ -6274,7 +6787,7 @@ static int say_and_wait(struct ast_channel *chan, int num, const char *language)
return d;
}
-static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box)
+static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move)
{
#ifdef IMAP_STORAGE
/* we must use mbox(x) folder names, and copy the message there */
@@ -6307,7 +6820,11 @@ static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg
ast_debug(5, "Folder exists.\n");
else
ast_log(AST_LOG_NOTICE, "Folder %s created!\n", mbox(vmu, box));
- res = !mail_copy(vms->mailstream, sequence, (char *) mbox(vmu, box));
+ if (move) {
+ res = !mail_move(vms->mailstream, sequence, (char *) mbox(vmu, box));
+ } else {
+ res = !mail_copy(vms->mailstream, sequence, (char *) mbox(vmu, box));
+ }
ast_mutex_unlock(&vms->lock);
return res;
#else
@@ -6349,6 +6866,10 @@ static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg
COPY(dir, msg, ddir, x, username, context, sfn, dfn);
}
ast_unlock_path(ddir);
+
+ if (newmsg) {
+ *newmsg = x;
+ }
return 0;
#endif
}
@@ -6953,6 +7474,8 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu,
struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
#ifndef IMAP_STORAGE
signed char zero_gain = 0;
+#else
+ const char *msg_id = NULL;
#endif
const char *duration_str;
@@ -6979,11 +7502,14 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu,
#ifdef IMAP_STORAGE
/* Record new intro file */
+ if (msg_cfg && msg_cfg != CONFIG_STATUS_FILEINVALID) {
+ msg_id = ast_variable_retrieve(msg_cfg, "message", "msg_id");
+ }
make_file(vms->introfn, sizeof(vms->introfn), curdir, curmsg);
strncat(vms->introfn, "intro", sizeof(vms->introfn));
ast_play_and_wait(chan, INTRO);
ast_play_and_wait(chan, "beep");
- cmd = play_record_review(chan, NULL, vms->introfn, vmu->maxsecs, vm_fmts, 1, vmu, (int *) duration, NULL, NULL, record_gain, vms, flag);
+ cmd = play_record_review(chan, NULL, vms->introfn, vmu->maxsecs, vm_fmts, 1, vmu, (int *) duration, NULL, NULL, record_gain, vms, flag, msg_id);
if (cmd == -1) {
break;
}
@@ -7173,12 +7699,25 @@ static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu,
if (!ast_strlen_zero(vmu->email)) {
int attach_user_voicemail = ast_test_flag(vmu, VM_ATTACH);
+ char *msg_id = NULL;
+#ifdef IMAP_STORAGE
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ char filename[PATH_MAX];
+
+ snprintf(filename, sizeof(filename), "%s.txt", fn);
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (msg_cfg && msg_cfg != CONFIG_STATUS_FILEINVALID) {
+ msg_id = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "msg_id"));
+ ast_config_destroy(msg_cfg);
+ }
+#endif
if (attach_user_voicemail)
RETRIEVE(todir, msgnum, vmu->mailbox, vmu->context);
/* XXX possible imap issue, should category be NULL XXX */
- sendmail(myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, mbox(vmu, 0), cidnum, cidname, fn, NULL, fmt, duration, attach_user_voicemail, chan, category, flag);
+ sendmail(myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, mbox(vmu, 0), cidnum, cidname, fn, NULL, fmt, duration, attach_user_voicemail, chan, category, flag, msg_id);
if (attach_user_voicemail)
DISPOSE(todir, msgnum);
@@ -7438,9 +7977,24 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st
long duration = 0;
struct vm_state vmstmp;
int copy_msg_result = 0;
+#ifdef IMAP_STORAGE
+ char filename[PATH_MAX];
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ const char *msg_id = NULL;
+ struct ast_config *msg_cfg;
+#endif
memcpy(&vmstmp, vms, sizeof(vmstmp));
- RETRIEVE(dir, curmsg, sender->mailbox, sender->context);
+ RETRIEVE(dir, curmsg, sender->mailbox, sender->context);
+#ifdef IMAP_STORAGE
+ make_file(filename, sizeof(filename), dir, curmsg);
+ strncat(filename, ".txt", sizeof(filename) - strlen(filename) - 1);
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (msg_cfg && msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ msg_id = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "msg_id"));
+ ast_config_destroy(msg_cfg);
+ }
+#endif
cmd = vm_forwardoptions(chan, sender, vmstmp.curdir, curmsg, vmfmts, S_OR(context, "default"), record_gain, &duration, &vmstmp, urgent_str);
if (!cmd) {
@@ -7459,7 +8013,7 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st
if (!dstvms->mailstream) {
ast_log(AST_LOG_ERROR, "IMAP mailstream for %s is NULL\n", vmtmp->mailbox);
} else {
- copy_msg_result = STORE(vmstmp.curdir, vmtmp->mailbox, vmtmp->context, dstvms->curmsg, chan, vmtmp, fmt, duration, dstvms, urgent_str);
+ copy_msg_result = STORE(vmstmp.curdir, vmtmp->mailbox, vmtmp->context, dstvms->curmsg, chan, vmtmp, fmt, duration, dstvms, urgent_str, msg_id);
run_externnotify(vmtmp->context, vmtmp->mailbox, urgent_str);
}
} else {
@@ -7474,9 +8028,9 @@ static int forward_message(struct ast_channel *chan, char *context, struct vm_st
S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
vmstmp.fn, vmstmp.introfn, fmt, duration, attach_user_voicemail, chan,
- NULL, urgent_str);
+ NULL, urgent_str, msg_id);
#else
- copy_msg_result = copy_message(chan, sender, 0, curmsg, duration, vmtmp, fmt, dir, urgent_str);
+ copy_msg_result = copy_message(chan, sender, 0, curmsg, duration, vmtmp, fmt, dir, urgent_str, NULL);
#endif
saved_messages++;
AST_LIST_REMOVE_CURRENT(list);
@@ -8088,7 +8642,7 @@ static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu)
}
} else if ((!strcasecmp(vms->curbox, "INBOX") || !strcasecmp(vms->curbox, "Urgent")) && vms->heard[x] && ast_test_flag(vmu, VM_MOVEHEARD) && !vms->deleted[x]) {
/* Move to old folder before deleting */
- res = save_to_folder(vmu, vms, x, 1);
+ res = save_to_folder(vmu, vms, x, 1, NULL, 0);
if (res == ERROR_LOCK_PATH) {
/* If save failed do not delete the message */
ast_log(AST_LOG_WARNING, "Save failed. Not moving message: %s.\n", res == ERROR_LOCK_PATH ? "unable to lock path" : "destination folder full");
@@ -8098,7 +8652,7 @@ static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu)
}
} else if (vms->deleted[x] && vmu->maxdeletedmsg) {
/* Move to deleted folder */
- res = save_to_folder(vmu, vms, x, 10);
+ res = save_to_folder(vmu, vms, x, 10, NULL, 0);
if (res == ERROR_LOCK_PATH) {
/* If save failed do not delete the message */
vms->deleted[x] = 0;
@@ -9323,7 +9877,7 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct
if (ast_test_flag(vmu, VM_FORCENAME)) {
snprintf(prefile, sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username);
if (ast_fileexists(prefile, NULL, NULL) < 1) {
- cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
if (cmd < 0 || cmd == 't' || cmd == '#')
return cmd;
}
@@ -9333,14 +9887,14 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct
if (ast_test_flag(vmu, VM_FORCEGREET)) {
snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username);
if (ast_fileexists(prefile, NULL, NULL) < 1) {
- cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
if (cmd < 0 || cmd == 't' || cmd == '#')
return cmd;
}
snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username);
if (ast_fileexists(prefile, NULL, NULL) < 1) {
- cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
if (cmd < 0 || cmd == 't' || cmd == '#')
return cmd;
}
@@ -9422,15 +9976,15 @@ static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct
switch (cmd) {
case '1': /* Record your unavailable message */
snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username);
- cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-unv", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
break;
case '2': /* Record your busy message */
snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username);
- cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-busy", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
break;
case '3': /* Record greeting */
snprintf(prefile, sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username);
- cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-name", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
break;
case '4': /* manage the temporary greeting */
cmd = vm_tempgreeting(chan, vmu, vms, fmtc, record_gain);
@@ -9564,7 +10118,7 @@ static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, st
retries = 0;
RETRIEVE(prefile, -1, vmu->mailbox, vmu->context);
if (ast_fileexists(prefile, NULL, NULL) <= 0) {
- cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
if (cmd == -1) {
break;
}
@@ -9572,7 +10126,7 @@ static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, st
} else {
switch (cmd) {
case '1':
- cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL);
+ cmd = play_record_review(chan, "vm-rec-temp", prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, NULL, record_gain, vms, NULL, NULL);
break;
case '2':
DELETE(prefile, -1, prefile, vmu);
@@ -9973,6 +10527,141 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
return 0;
}
+static int play_message_by_id_helper(struct ast_channel *chan,
+ struct ast_vm_user *vmu,
+ struct vm_state *vms,
+ const char *msg_id)
+{
+ if (message_range_and_existence_check(vms, &msg_id, 1, &vms->curmsg, vmu)) {
+ return -1;
+ }
+ /* Found the msg, so play it back */
+
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+
+#ifdef IMAP_STORAGE
+ /*IMAP storage stores any prepended message from a forward
+ * as a separate file from the rest of the message
+ */
+ if (!ast_strlen_zero(vms->introfn) && ast_fileexists(vms->introfn, NULL, NULL) > 0) {
+ wait_file(chan, vms, vms->introfn);
+ }
+#endif
+ if ((wait_file(chan, vms, vms->fn)) < 0) {
+ ast_log(AST_LOG_WARNING, "Playback of message %s failed\n", vms->fn);
+ } else {
+ vms->heard[vms->curmsg] = 1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \brief Finds a message in a specific mailbox by msg_id and plays it to the channel
+ *
+ * \retval 0 Success
+ * \retval -1 Failure
+ */
+static int play_message_by_id(struct ast_channel *chan, const char *mailbox, const char *context, const char *msg_id)
+{
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int res = 0;
+ int open = 0;
+ int played = 0;
+ int i;
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&vms, 0, sizeof(vms));
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ goto play_msg_cleanup;
+ }
+
+ /* Iterate through every folder, find the msg, and play it */
+ for (i = 0; i < AST_VM_FOLDER_NUMBER && !played; i++) {
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&vms, vmu, i)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ res = -1;
+ goto play_msg_cleanup;
+ }
+ open = 1;
+
+ /* play msg if it exists in this mailbox */
+ if ((vms.lastmsg != -1) && !(play_message_by_id_helper(chan, vmu, &vms, msg_id))) {
+ played = 1;
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) {
+ res = -1;
+ goto play_msg_cleanup;
+ }
+ open = 0;
+ }
+
+play_msg_cleanup:
+ if (!played) {
+ res = -1;
+ }
+
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ return res;
+}
+
+static int vm_playmsgexec(struct ast_channel *chan, const char *data)
+{
+ char *parse;
+ char *mailbox = NULL;
+ char *context = NULL;
+ int res;
+
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(mailbox);
+ AST_APP_ARG(msg_id);
+ );
+
+ if (ast_channel_state(chan) != AST_STATE_UP) {
+ ast_debug(1, "Before ast_answer\n");
+ ast_answer(chan);
+ }
+
+ if (ast_strlen_zero(data)) {
+ return -1;
+ }
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_strlen_zero(args.mailbox) || ast_strlen_zero(args.msg_id)) {
+ return -1;
+ }
+
+ if ((context = strchr(args.mailbox, '@'))) {
+ *context++ = '\0';
+ }
+ mailbox = args.mailbox;
+
+ res = play_message_by_id(chan, mailbox, context, args.msg_id);
+ pbx_builtin_setvar_helper(chan, "VOICEMAIL_PLAYBACKSTATUS", res ? "FAILED" : "SUCCESS");
+
+ return 0;
+}
+
static int vm_execmain(struct ast_channel *chan, const char *data)
{
/* XXX This is, admittedly, some pretty horrendous code. For some
@@ -10575,7 +11264,7 @@ static int vm_execmain(struct ast_channel *chan, const char *data)
break;
} else if (cmd > 0) {
box = cmd = cmd - '0';
- cmd = save_to_folder(vmu, &vms, vms.curmsg, cmd);
+ cmd = save_to_folder(vmu, &vms, vms.curmsg, cmd, NULL, 0);
if (cmd == ERROR_LOCK_PATH) {
res = cmd;
goto out;
@@ -10792,6 +11481,45 @@ static int vm_exec(struct ast_channel *chan, const char *data)
return res;
}
+static void generate_random_string(char *buf, size_t size)
+{
+ long val[4];
+ int x;
+
+ for (x=0; x<4; x++)
+ val[x] = ast_random();
+ snprintf(buf, size, "%08lx%08lx%08lx%08lx", val[0], val[1], val[2], val[3]);
+}
+
+static int add_message_id(struct ast_config *msg_cfg, char *dir, int msg, char *filename, char *id, size_t id_size, struct ast_vm_user *vmu, int folder)
+{
+ struct ast_variable *var;
+ struct ast_category *cat;
+ generate_random_string(id, id_size);
+
+ var = ast_variable_new("msg_id", id, "");
+ if (!var) {
+ return -1;
+ }
+
+ cat = ast_category_get(msg_cfg, "message");
+ if (!cat) {
+ ast_log(LOG_ERROR, "Voicemail data file %s/%d.txt has no [message] category?\n", dir, msg);
+ ast_variables_destroy(var);
+ return -1;
+ }
+
+ ast_variable_append(cat, var);
+
+ if (ast_config_text_file_save(filename, msg_cfg, "app_voicemail")) {
+ ast_log(LOG_WARNING, "Unable to update %s to have a message ID\n", filename);
+ return -1;
+ }
+
+ UPDATE_MSG_ID(dir, msg, id, vmu, msg_cfg, folder);
+ return 0;
+}
+
static struct ast_vm_user *find_or_create(const char *context, const char *box)
{
struct ast_vm_user *vmu;
@@ -12998,7 +13726,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount)
break;
}
open_mailbox(&vms, vmu, folder2mbox[i]);
- STORE(tmp[i].dir, testmailbox, testcontext, 0, chan, vmu, "gsm", 600, &vms, strcmp(folders[i], "Urgent") ? "" : "Urgent");
+ STORE(tmp[i].dir, testmailbox, testcontext, 0, chan, vmu, "gsm", 600, &vms, strcmp(folders[i], "Urgent") ? "" : "Urgent", NULL);
/* hasvm-old, hasvm-urgent, hasvm-new, ic-old, ic-urgent, ic-new, ic2-old, ic2-urgent, ic2-new, mc-old, mc-urgent, mc-new */
for (j = 0; j < 3; j++) {
@@ -13160,7 +13888,7 @@ AST_TEST_DEFINE(test_voicemail_notify_endl)
test_items[which].location = test_items[which].u.strval;
}
- make_email_file(file, from, vmu, 0, testcontext, testmailbox, "INBOX", cidnum, cidname, attach, attach2, format, 999, 1, chan, NULL, 0, NULL);
+ make_email_file(file, from, vmu, 0, testcontext, testmailbox, "INBOX", cidnum, cidname, attach, attach2, format, 999, 1, chan, NULL, 0, NULL, NULL);
rewind(file);
while (fgets(buf, sizeof(buf), file)) {
if (
@@ -13352,6 +14080,7 @@ static int unload_module(void)
res |= ast_unregister_application(app2);
res |= ast_unregister_application(app3);
res |= ast_unregister_application(app4);
+ res |= ast_unregister_application(playmsg_app);
res |= ast_unregister_application(sayname_app);
res |= ast_custom_function_unregister(&mailbox_exists_acf);
res |= ast_custom_function_unregister(&vm_info_acf);
@@ -13405,6 +14134,7 @@ static int load_module(void)
res |= ast_register_application_xml(app2, vm_execmain);
res |= ast_register_application_xml(app3, vm_box_exists);
res |= ast_register_application_xml(app4, vmauthenticate);
+ res |= ast_register_application_xml(playmsg_app, vm_playmsgexec);
res |= ast_register_application_xml(sayname_app, vmsayname_exec);
res |= ast_custom_function_register(&mailbox_exists_acf);
res |= ast_custom_function_register(&vm_info_acf);
@@ -13424,7 +14154,7 @@ static int load_module(void)
ast_cli_register_multiple(cli_voicemail, ARRAY_LEN(cli_voicemail));
ast_data_register_multiple(vm_data_providers, ARRAY_LEN(vm_data_providers));
- ast_install_vm_functions(has_voicemail, inboxcount, inboxcount2, messagecount, sayname);
+ ast_install_vm_functions(has_voicemail, inboxcount, inboxcount2, messagecount, sayname, msg_create_from_file);
ast_realtime_require_field("voicemail", "uniqueid", RQ_UINTEGER3, 11, "password", RQ_CHAR, 10, SENTINEL);
ast_realtime_require_field("voicemail_data", "filename", RQ_CHAR, 30, "duration", RQ_UINTEGER3, 5, SENTINEL);
@@ -13698,7 +14428,7 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s
static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
- signed char record_gain, struct vm_state *vms, char *flag)
+ signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id)
{
/* Record message & let caller review or re-record it, or set options if applicable */
int res = 0;
@@ -13743,7 +14473,7 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
ast_stream_and_wait(chan, "vm-msgsaved", "");
if (!outsidecaller) {
/* Saves to IMAP server only if imapgreeting=yes */
- STORE(recordfile, vmu->mailbox, vmu->context, -1, chan, vmu, fmt, *duration, vms, flag);
+ STORE(recordfile, vmu->mailbox, vmu->context, -1, chan, vmu, fmt, *duration, vms, flag, msg_id);
DISPOSE(recordfile, -1);
}
cmd = 't';
@@ -13933,11 +14663,899 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
return cmd;
}
+static struct ast_vm_msg_snapshot *vm_msg_snapshot_alloc(void)
+{
+ struct ast_vm_msg_snapshot *msg_snapshot;
+
+ if (!(msg_snapshot = ast_calloc(1, sizeof(*msg_snapshot)))) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(msg_snapshot, 512)) {
+ ast_free(msg_snapshot);
+ return NULL;
+ }
+
+ return msg_snapshot;
+}
+
+static struct ast_vm_msg_snapshot *vm_msg_snapshot_destroy(struct ast_vm_msg_snapshot *msg_snapshot)
+{
+ ast_string_field_free_memory(msg_snapshot);
+ ast_free(msg_snapshot);
+
+ return NULL;
+}
+
+#ifdef TEST_FRAMEWORK
+
+int ast_vm_test_destroy_user(const char *context, const char *mailbox)
+{
+ struct ast_vm_user *vmu;
+
+ AST_LIST_LOCK(&users);
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&users, vmu, list) {
+ if (!strncmp(context, vmu->context, sizeof(context))
+ && !strncmp(mailbox, vmu->mailbox, sizeof(mailbox))) {
+ AST_LIST_REMOVE_CURRENT(list);
+ ast_free(vmu);
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END
+ AST_LIST_UNLOCK(&users);
+ return 0;
+}
+
+int ast_vm_test_create_user(const char *context, const char *mailbox)
+{
+ struct ast_vm_user *vmu;
+
+ if (!(vmu = find_or_create(context, mailbox))) {
+ return -1;
+ }
+ populate_defaults(vmu);
+ return 0;
+}
+
+#endif
+
+/*!
+ * \brief Create and store off all the msgs in an open mailbox
+ *
+ * \note TODO XXX This function should work properly for all
+ * voicemail storage options, but is far more expensive for
+ * ODBC at the moment. This is because the RETRIEVE macro
+ * not only pulls out the message's meta data file from the
+ * database, but also the actual audio for each message, temporarily
+ * writing it to the file system. This is an area that needs
+ * to be made more efficient.
+ */
+static int vm_msg_snapshot_create(struct ast_vm_user *vmu,
+ struct vm_state *vms,
+ struct ast_vm_mailbox_snapshot *mailbox_snapshot,
+ int snapshot_index,
+ int mailbox_index,
+ int descending,
+ enum ast_vm_snapshot_sort_val sort_val)
+{
+ struct ast_vm_msg_snapshot *msg_snapshot;
+ struct ast_vm_msg_snapshot *msg_snapshot_tmp;
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ char filename[PATH_MAX];
+ const char *value;
+
+ for (vms->curmsg = 0; vms->curmsg <= vms->lastmsg; vms->curmsg++) {
+ int inserted = 0;
+ /* Find the msg */
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+ snprintf(filename, sizeof(filename), "%s.txt", vms->fn);
+ RETRIEVE(vms->curdir, vms->curmsg, vmu->mailbox, vmu->context);
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ DISPOSE(vms->curdir, vms->curmsg);
+ continue;
+ }
+
+ /* Create the snapshot object */
+ if (!(msg_snapshot = vm_msg_snapshot_alloc())) {
+ ast_config_destroy(msg_cfg);
+ return -1;
+ }
+
+ /* Fill in the snapshot object */
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "msg_id"))) {
+ ast_string_field_set(msg_snapshot, msg_id, value);
+ } else {
+ /* Message snapshots *really* should have a
+ * message ID. Add one to the message config
+ * if it does not already exist
+ */
+ char id[33];
+ if (!(add_message_id(msg_cfg, vms->curdir, vms->curmsg,
+ filename, id, sizeof(id), vmu, mailbox_index))) {
+ ast_string_field_set(msg_snapshot, msg_id, id);
+ } else {
+ ast_log(LOG_WARNING, "Unable to create a message ID for message %s/%d\n", vms->curdir, vms->curmsg);
+ }
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "callerid"))) {
+ ast_string_field_set(msg_snapshot, callerid, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "callerchan"))) {
+ ast_string_field_set(msg_snapshot, callerchan, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "exten"))) {
+ ast_string_field_set(msg_snapshot, exten, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "origdate"))) {
+ ast_string_field_set(msg_snapshot, origdate, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "origtime"))) {
+ ast_string_field_set(msg_snapshot, origtime, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) {
+ ast_string_field_set(msg_snapshot, duration, value);
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "flag"))) {
+ ast_string_field_set(msg_snapshot, flag, value);
+ }
+ msg_snapshot->msg_number = vms->curmsg;
+ ast_string_field_set(msg_snapshot, folder_name, mailbox_folders[mailbox_index]);
+
+ /* store msg snapshot in mailbox snapshot */
+ switch (sort_val) {
+ default:
+ case AST_VM_SNAPSHOT_SORT_BY_ID:
+ if (descending) {
+ AST_LIST_INSERT_HEAD(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg);
+ } else {
+ AST_LIST_INSERT_TAIL(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg);
+ }
+ inserted = 1;
+ break;
+ case AST_VM_SNAPSHOT_SORT_BY_TIME:
+ AST_LIST_TRAVERSE_SAFE_BEGIN(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot_tmp, msg) {
+ int val = strcmp(msg_snapshot->origtime, msg_snapshot_tmp->origtime);
+ if (descending && val >= 0) {
+ AST_LIST_INSERT_BEFORE_CURRENT(msg_snapshot, msg);
+ inserted = 1;
+ break;
+ } else if (!descending && val <= 0) {
+ AST_LIST_INSERT_BEFORE_CURRENT(msg_snapshot, msg);
+ inserted = 1;
+ break;
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ break;
+ }
+
+ if (!inserted) {
+ AST_LIST_INSERT_TAIL(&mailbox_snapshot->snapshots[snapshot_index], msg_snapshot, msg);
+ }
+
+ mailbox_snapshot->total_msg_num++;
+
+ /* cleanup configs and msg */
+ ast_config_destroy(msg_cfg);
+ DISPOSE(vms->curdir, vms->curmsg);
+ }
+
+ return 0;
+}
+
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_create(const char *mailbox,
+ const char *context,
+ const char *folder,
+ int descending,
+ enum ast_vm_snapshot_sort_val sort_val,
+ int combine_INBOX_and_OLD)
+{
+ struct ast_vm_mailbox_snapshot *mailbox_snapshot;
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int res;
+ int i;
+ int this_index_only = -1;
+ int open = 0;
+ int inbox_index = 0;
+ int old_index = 1;
+
+ if (ast_strlen_zero(mailbox)) {
+ ast_log(LOG_WARNING, "Cannot create a mailbox snapshot since no mailbox was specified\n");
+ return NULL;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+
+ if (!(ast_strlen_zero(folder))) {
+ /* find the folder index */
+ for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) {
+ if (!strcasecmp(mailbox_folders[i], folder)) {
+ this_index_only = i;
+ break;
+ }
+ }
+ if (this_index_only == -1) {
+ /* Folder was specified and it did not match any folder in our list */
+ return NULL;
+ }
+ }
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot for unknown voicemail user %s@%s\n", mailbox, context);
+ return NULL;
+ }
+
+ if (!(mailbox_snapshot = ast_calloc(1, sizeof(*mailbox_snapshot)))) {
+ ast_log(AST_LOG_ERROR, "Failed to allocate memory for mailbox snapshot\n");
+ return NULL;
+ }
+
+ for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) {
+ int combining_old = 0;
+ if ((i == old_index) && (combine_INBOX_and_OLD)) {
+ combining_old = 1;
+ }
+
+ /* This if statement is confusing looking. Here is what it means in english.
+ * - If a folder is given to the function and that folder's index is not the one we are iterating over, skip it...
+ * - Unless the folder provided is the INBOX folder and the current index is the OLD folder and we are combining OLD and INBOX msgs.
+ */
+ if ((this_index_only != -1) && (this_index_only != i) && !(combining_old && i == old_index && this_index_only == inbox_index)) {
+ continue;
+ }
+
+ memset(&vms, 0, sizeof(vms));
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+ open = 0;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&vms, vmu, i)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ goto snapshot_cleanup;
+ }
+ open = 1;
+
+ /* Iterate through each msg, storing off info */
+ if (vms.lastmsg != -1) {
+ if ((vm_msg_snapshot_create(vmu, &vms, mailbox_snapshot, combining_old ? inbox_index : i, i, descending, sort_val))) {
+ ast_log(LOG_WARNING, "Failed to create msg snapshots for %s@%s\n", mailbox, context);
+ goto snapshot_cleanup;
+ }
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) {
+ goto snapshot_cleanup;
+ }
+ open = 0;
+ }
+
+snapshot_cleanup:
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ return mailbox_snapshot;
+}
+
+struct ast_vm_mailbox_snapshot *ast_vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot)
+{
+ int i;
+ struct ast_vm_msg_snapshot *msg_snapshot;
+
+ for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) {
+ while ((msg_snapshot = AST_LIST_REMOVE_HEAD(&mailbox_snapshot->snapshots[i], msg))) {
+ msg_snapshot = vm_msg_snapshot_destroy(msg_snapshot);
+ }
+ }
+ ast_free(mailbox_snapshot);
+ return NULL;
+}
+
+struct ast_str *vm_mailbox_snapshot_str(const char *mailbox, const char *context)
+{
+ struct ast_vm_mailbox_snapshot *mailbox_snapshot = ast_vm_mailbox_snapshot_create(mailbox, context, NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0);
+ struct ast_vm_msg_snapshot *msg_snapshot;
+ int i;
+ struct ast_str *str;
+
+ if (!mailbox_snapshot) {
+ return NULL;
+ }
+
+ if (!(str = ast_str_create(512))) {
+ return NULL;
+ mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+ }
+
+ for (i = 0; i < AST_VM_FOLDER_NUMBER; i++) {
+ ast_str_append(&str, 0, "FOLDER: %s\n", mailbox_folders[i]);
+ AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[i], msg_snapshot, msg) {
+ ast_str_append(&str, 0, "MSG Number: %d\n", msg_snapshot->msg_number);
+ ast_str_append(&str, 0, "MSG ID: %s\n", msg_snapshot->msg_id);
+ ast_str_append(&str, 0, "CALLER ID: %s\n", msg_snapshot->callerid);
+ ast_str_append(&str, 0, "CALLER CHAN: %s\n", msg_snapshot->callerchan);
+ ast_str_append(&str, 0, "CALLER EXTEN: %s\n", msg_snapshot->exten);
+ ast_str_append(&str, 0, "DATE: %s\n", msg_snapshot->origdate);
+ ast_str_append(&str, 0, "TIME: %s\n", msg_snapshot->origtime);
+ ast_str_append(&str, 0, "DURATION: %s\n", msg_snapshot->duration);
+ ast_str_append(&str, 0, "FOLDER NAME: %s\n", msg_snapshot->folder_name);
+ ast_str_append(&str, 0, "FLAG: %s\n", msg_snapshot->flag);
+ ast_str_append(&str, 0, "\n");
+ }
+ }
+
+ mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+ return str;
+}
+
+/*!
+ * \brief common bounds checking and existence check for Voicemail API functions.
+ *
+ * \details
+ * This is called by ast_vm_msg_move, ast_vm_msg_remove, and ast_vm_msg_forward to
+ * ensure that data passed in are valid. This ensures that given the
+ * desired message IDs, they can be found.
+ *
+ * \param vms The voicemail state corresponding to an open mailbox
+ * \param msg_ids An array of message identifiers
+ * \param num_msgs The number of identifiers in msg_ids
+ * \param msg_nums [out] The message indexes corresponding to the given
+ * message IDs
+ * \pre vms must have open_mailbox() called on it prior to this function.
+ *
+ * \retval -1 Failure
+ * \retval 0 Success
+ */
+static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu)
+{
+ int i;
+ int res = 0;
+ for (i = 0; i < num_msgs; ++i) {
+ const char *msg_id = msg_ids[i];
+ int found = 0;
+ for (vms->curmsg = 0; vms->curmsg <= vms->lastmsg; vms->curmsg++) {
+ const char *other_msg_id;
+ char filename[PATH_MAX];
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+
+ make_file(vms->fn, sizeof(vms->fn), vms->curdir, vms->curmsg);
+ snprintf(filename, sizeof(filename), "%s.txt", vms->fn);
+ RETRIEVE(vms->curdir, vms->curmsg, vmu->mailbox, vmu->context);
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ DISPOSE(vms->curdir, vms->curmsg);
+ res = -1;
+ goto done;
+ }
+
+ other_msg_id = ast_variable_retrieve(msg_cfg, "message", "msg_id");
+
+ if (!ast_strlen_zero(other_msg_id) && !strcmp(other_msg_id, msg_id)) {
+ /* Message found. We can get out of this inner loop
+ * and move on to the next message to find
+ */
+ found = 1;
+ msg_nums[i] = vms->curmsg;
+ ast_config_destroy(msg_cfg);
+ DISPOSE(vms->curdir, vms->curmsg);
+ break;
+ }
+ DISPOSE(vms->curdir, vms->curmsg);
+ }
+ if (!found) {
+ /* If we can't find one of the message IDs requested, then OH NO! */
+ res = -1;
+ goto done;
+ }
+ }
+
+done:
+ return res;
+}
+
+static void notify_new_state(struct ast_vm_user *vmu)
+{
+ int new = 0, old = 0, urgent = 0;
+ char ext_context[1024];
+
+ snprintf(ext_context, sizeof(ext_context), "%s@%s", vmu->mailbox, vmu->context);
+ run_externnotify(vmu->context, vmu->mailbox, NULL);
+ ast_app_inboxcount2(ext_context, &urgent, &new, &old);
+ queue_mwi_event(ext_context, urgent, new, old);
+}
+
+int ast_vm_msg_forward(const char *from_mailbox,
+ const char *from_context,
+ const char *from_folder,
+ const char *to_mailbox,
+ const char *to_context,
+ const char *to_folder,
+ size_t num_msgs,
+ const char *msg_ids [],
+ int delete_old)
+{
+ struct vm_state from_vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ struct ast_vm_user *to_vmu = NULL, to_vmus;
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ char filename[PATH_MAX];
+ int from_folder_index;
+ int open = 0;
+ int res = 0;
+ int i;
+ int *msg_nums;
+
+ if (ast_strlen_zero(from_mailbox) || ast_strlen_zero(to_mailbox)) {
+ ast_log(LOG_WARNING, "Cannot forward message because either the from or to mailbox was not specified\n");
+ return -1;
+ }
+
+ if (!num_msgs) {
+ ast_log(LOG_WARNING, "Invalid number of messages specified to forward: %zu\n", num_msgs);
+ return -1;
+ }
+
+ if (ast_strlen_zero(from_folder) || ast_strlen_zero(to_folder)) {
+ ast_log(LOG_WARNING, "Cannot forward message because the from_folder or to_folder was not specified\n");
+ return -1;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&to_vmus, 0, sizeof(to_vmus));
+ memset(&from_vms, 0, sizeof(from_vms));
+
+ from_folder_index = get_folder_by_name(from_folder);
+ if (from_folder_index == -1) {
+ return -1;
+ }
+
+ if (get_folder_by_name(to_folder) == -1) {
+ return -1;
+ }
+
+ if (!(vmu = find_user(&vmus, from_context, from_mailbox))) {
+ ast_log(LOG_WARNING, "Can't find voicemail user to forward from (%s@%s)\n", from_mailbox, from_context);
+ return -1;
+ }
+
+ if (!(to_vmu = find_user(&to_vmus, to_context, to_mailbox))) {
+ ast_log(LOG_WARNING, "Can't find voicemail user to forward to (%s@%s)\n", to_mailbox, to_context);
+ return -1;
+ }
+
+ ast_copy_string(from_vms.username, from_mailbox, sizeof(from_vms.username));
+ from_vms.lastmsg = -1;
+ open = 0;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&from_vms, vmu, from_folder_index)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", from_mailbox);
+ res = -1;
+ goto vm_forward_cleanup;
+ }
+
+ open = 1;
+
+ if ((from_vms.lastmsg + 1) < num_msgs) {
+ ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", from_folder, num_msgs);
+ res = -1;
+ goto vm_forward_cleanup;
+ }
+
+ if (!(msg_nums = alloca(sizeof(int) * num_msgs)))
+ {
+ ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things!\n");
+ res = -1;
+ goto vm_forward_cleanup;
+ }
+
+ if ((res = message_range_and_existence_check(&from_vms, msg_ids, num_msgs, msg_nums, vmu) < 0)) {
+ goto vm_forward_cleanup;
+ }
+
+ /* Now we actually forward the messages */
+ for (i = 0; i < num_msgs; i++) {
+ int cur_msg = msg_nums[i];
+ int duration = 0;
+ const char *value;
+
+ make_file(from_vms.fn, sizeof(from_vms.fn), from_vms.curdir, cur_msg);
+ snprintf(filename, sizeof(filename), "%s.txt", from_vms.fn);
+ RETRIEVE(from_vms.curdir, cur_msg, vmu->mailbox, vmu->context);
+ msg_cfg = ast_config_load(filename, config_flags);
+ /* XXX This likely will not fail since we previously ensured that the
+ * message we are looking for exists. However, there still could be some
+ * circumstance where this fails, so atomicity is not guaranteed.
+ */
+ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ DISPOSE(from_vms.curdir, cur_msg);
+ continue;
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) {
+ duration = atoi(value);
+ }
+
+ copy_message(NULL, vmu, from_folder_index, cur_msg, duration, to_vmu, vmfmts, from_vms.curdir, "", to_folder);
+
+ if (delete_old) {
+ from_vms.deleted[cur_msg] = 1;
+ }
+ ast_config_destroy(msg_cfg);
+ DISPOSE(from_vms.curdir, cur_msg);
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&from_vms, vmu) == ERROR_LOCK_PATH)) {
+ res = -1;
+ goto vm_forward_cleanup;
+ }
+ open = 0;
+
+vm_forward_cleanup:
+ if (vmu && open) {
+ close_mailbox(&from_vms, vmu);
+ }
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&from_vms);
+ }
+#endif
+
+ if (!res) {
+ notify_new_state(to_vmu);
+ }
+
+ return res;
+}
+
+int ast_vm_msg_move(const char *mailbox,
+ const char *context,
+ size_t num_msgs,
+ const char *oldfolder,
+ const char *old_msg_ids [],
+ const char *newfolder)
+{
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int old_folder_index;
+ int new_folder_index;
+ int open = 0;
+ int res = 0;
+ int i;
+ int *old_msg_nums;
+
+ if (ast_strlen_zero(mailbox)) {
+ ast_log(LOG_WARNING, "Cannot move message because no mailbox was specified\n");
+ return -1;
+ }
+
+ if (!num_msgs) {
+ ast_log(LOG_WARNING, "Invalid number of messages specified to move: %zu\n", num_msgs);
+ return -1;
+ }
+
+ if (ast_strlen_zero(oldfolder) || ast_strlen_zero(newfolder)) {
+ ast_log(LOG_WARNING, "Cannot move message because either oldfolder or newfolder was not specified\n");
+ return -1;
+ }
+
+ old_folder_index = get_folder_by_name(oldfolder);
+ new_folder_index = get_folder_by_name(newfolder);
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&vms, 0, sizeof(vms));
+
+ if (old_folder_index == -1 || new_folder_index == -1) {
+ return -1;
+ }
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ return -1;
+ }
+
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+ open = 0;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&vms, vmu, old_folder_index)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ res = -1;
+ goto vm_move_cleanup;
+ }
+
+ open = 1;
+
+ if ((vms.lastmsg + 1) < num_msgs) {
+ ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", oldfolder, num_msgs);
+ res = -1;
+ goto vm_move_cleanup;
+ }
+
+ if (!(old_msg_nums = alloca(sizeof(int) * num_msgs))) {
+ ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things!\n");
+ res = -1;
+ goto vm_move_cleanup;
+ }
+
+ if ((res = message_range_and_existence_check(&vms, old_msg_ids, num_msgs, old_msg_nums, vmu)) < 0) {
+ goto vm_move_cleanup;
+ }
+
+ /* Now actually move the message */
+ for (i = 0; i < num_msgs; ++i) {
+ if (save_to_folder(vmu, &vms, old_msg_nums[i], new_folder_index, NULL, 0)) {
+ res = -1;
+ goto vm_move_cleanup;
+ }
+ vms.deleted[old_msg_nums[i]] = 1;
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) {
+ res = -1;
+ goto vm_move_cleanup;
+ }
+ open = 0;
+
+vm_move_cleanup:
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ if (!res) {
+ notify_new_state(vmu);
+ }
+
+ return res;
+}
+
+int ast_vm_msg_remove(const char *mailbox,
+ const char *context,
+ size_t num_msgs,
+ const char *folder,
+ const char *msgs[])
+{
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int folder_index;
+ int open = 0;
+ int res = 0;
+ int i;
+ int *msg_nums;
+
+ if (ast_strlen_zero(mailbox)) {
+ ast_log(LOG_WARNING, "Cannot remove message because no mailbox was specified\n");
+ return -1;
+ }
+
+ if (!num_msgs) {
+ ast_log(LOG_WARNING, "Invalid number of messages specified to remove: %zu\n", num_msgs);
+ return -1;
+ }
+
+ if (ast_strlen_zero(folder)) {
+ ast_log(LOG_WARNING, "Cannot remove message because no folder was specified\n");
+ return -1;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&vms, 0, sizeof(vms));
+
+ folder_index = get_folder_by_name(folder);
+ if (folder_index == -1) {
+ ast_log(LOG_WARNING, "Could not remove msgs from unknown folder %s\n", folder);
+ return -1;
+ }
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ ast_log(LOG_WARNING, "Can't find voicemail user to remove msg from (%s@%s)\n", mailbox, context);
+ return -1;
+ }
+
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+ open = 0;
+
+ /* open the mailbox state */
+ if ((res = open_mailbox(&vms, vmu, folder_index)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ res = -1;
+ goto vm_remove_cleanup;
+ }
+
+ open = 1;
+
+ if ((vms.lastmsg + 1) < num_msgs) {
+ ast_log(LOG_WARNING, "Folder %s has less than %zu messages\n", folder, num_msgs);
+ res = -1;
+ goto vm_remove_cleanup;
+ }
+
+ if (!(msg_nums = alloca(sizeof(int) * num_msgs))) {
+ ast_log(LOG_ERROR, "Unable to allocate stack space! Expect awful things\n");
+ res = -1;
+ goto vm_remove_cleanup;
+ }
+
+ if ((res = message_range_and_existence_check(&vms, msgs, num_msgs, msg_nums, vmu)) < 0) {
+ goto vm_remove_cleanup;
+ }
+
+ for (i = 0; i < num_msgs; i++) {
+ vms.deleted[msg_nums[i]] = 1;
+ }
+
+ /* close mailbox */
+ if ((res = close_mailbox(&vms, vmu) == ERROR_LOCK_PATH)) {
+ res = -1;
+ ast_log(AST_LOG_ERROR, "Failed to close mailbox folder %s while removing msgs\n", folder);
+ goto vm_remove_cleanup;
+ }
+ open = 0;
+
+vm_remove_cleanup:
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ if (!res) {
+ notify_new_state(vmu);
+ }
+
+ return res;
+}
+
+const char *ast_vm_index_to_foldername(unsigned int index)
+{
+ if (index >= AST_VM_FOLDER_NUMBER) {
+ return "";
+ }
+ return mailbox_folders[index];
+}
+
+int ast_vm_msg_play(struct ast_channel *chan,
+ const char *mailbox,
+ const char *context,
+ const char *folder,
+ const char *msg_id,
+ ast_vm_msg_play_cb cb)
+{
+ struct vm_state vms;
+ struct ast_vm_user *vmu = NULL, vmus;
+ int res = 0;
+ int open = 0;
+ int i;
+ char filename[PATH_MAX];
+ struct ast_config *msg_cfg;
+ struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+ int duration = 0;
+ const char *value;
+
+ if (ast_strlen_zero(mailbox)) {
+ ast_log(LOG_WARNING, "Cannot play message because no mailbox was specified\n");
+ return -1;
+ }
+
+ if (ast_strlen_zero(folder)) {
+ ast_log(LOG_WARNING, "Cannot play message because no folder was specified\n");
+ return -1;
+ }
+
+ if (ast_strlen_zero(msg_id)) {
+ ast_log(LOG_WARNING, "Cannot play message because no message number was specified\n");
+ return -1;
+ }
+
+ memset(&vmus, 0, sizeof(vmus));
+ memset(&vms, 0, sizeof(vms));
+
+ if (ast_strlen_zero(context)) {
+ context = "default";
+ }
+
+ if (!(vmu = find_user(&vmus, context, mailbox))) {
+ return -1;
+ }
+
+ i = get_folder_by_name(folder);
+ ast_copy_string(vms.username, mailbox, sizeof(vms.username));
+ vms.lastmsg = -1;
+ if ((res = open_mailbox(&vms, vmu, i)) < 0) {
+ ast_log(LOG_WARNING, "Could not open mailbox %s\n", mailbox);
+ goto play2_msg_cleanup;
+ }
+ open = 1;
+
+ if (message_range_and_existence_check(&vms, &msg_id, 1, &vms.curmsg, vmu)) {
+ res = -1;
+ goto play2_msg_cleanup;
+ }
+
+ /* Find the msg */
+ make_file(vms.fn, sizeof(vms.fn), vms.curdir, vms.curmsg);
+ snprintf(filename, sizeof(filename), "%s.txt", vms.fn);
+ RETRIEVE(vms.curdir, vms.curmsg, vmu->mailbox, vmu->context);
+
+ msg_cfg = ast_config_load(filename, config_flags);
+ if (!msg_cfg || msg_cfg == CONFIG_STATUS_FILEINVALID) {
+ DISPOSE(vms.curdir, vms.curmsg);
+ res = -1;
+ goto play2_msg_cleanup;
+ }
+ if ((value = ast_variable_retrieve(msg_cfg, "message", "duration"))) {
+ duration = atoi(value);
+ }
+ ast_config_destroy(msg_cfg);
+
+#ifdef IMAP_STORAGE
+ /*IMAP storage stores any prepended message from a forward
+ * as a separate file from the rest of the message
+ */
+ if (!ast_strlen_zero(vms.introfn) && ast_fileexists(vms.introfn, NULL, NULL) > 0) {
+ wait_file(chan, &vms, vms.introfn);
+ }
+#endif
+ if (cb) {
+ cb(chan, vms.fn, duration);
+ } else if ((wait_file(chan, &vms, vms.fn)) < 0) {
+ ast_log(AST_LOG_WARNING, "Playback of message %s failed\n", vms.fn);
+ } else {
+ res = 0;
+ }
+
+ vms.heard[vms.curmsg] = 1;
+
+ /* cleanup configs and msg */
+ DISPOSE(vms.curdir, vms.curmsg);
+
+play2_msg_cleanup:
+ if (vmu && open) {
+ close_mailbox(&vms, vmu);
+ }
+
+#ifdef IMAP_STORAGE
+ if (vmu) {
+ vmstate_delete(&vms);
+ }
+#endif
+
+ if (!res) {
+ notify_new_state(vmu);
+ }
+
+ return res;
+}
+
/* This is a workaround so that menuselect displays a proper description
* AST_MODULE_INFO(, , "Comedian Mail (Voicemail System)"
*/
-AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, tdesc,
+AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, tdesc,
.load = load_module,
.unload = unload_module,
.reload = reload,
diff --git a/apps/app_voicemail.exports.in b/apps/app_voicemail.exports.in
index 3520d22166..e66bb359c8 100644
--- a/apps/app_voicemail.exports.in
+++ b/apps/app_voicemail.exports.in
@@ -15,6 +15,15 @@
LINKER_SYMBOL_PREFIXmm_notify;
LINKER_SYMBOL_PREFIXmm_searched;
LINKER_SYMBOL_PREFIXmm_status;
+ LINKER_SYMBOL_PREFIXast_vm_mailbox_snapshot_create;
+ LINKER_SYMBOL_PREFIXast_vm_mailbox_snapshot_destroy;
+ LINKER_SYMBOL_PREFIXast_vm_msg_move;
+ LINKER_SYMBOL_PREFIXast_vm_msg_remove;
+ LINKER_SYMBOL_PREFIXast_vm_msg_forward;
+ LINKER_SYMBOL_PREFIXast_vm_index_to_foldername;
+ LINKER_SYMBOL_PREFIXast_vm_msg_play;
+ LINKER_SYMBOL_PREFIXast_vm_test_create_user;
+ LINKER_SYMBOL_PREFIXast_vm_test_destroy_user;
local:
*;
};
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index 289a807cf4..8456b0b998 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -230,6 +230,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
affect the speed of the program at all. They can be considered to be documentation.
*/
/* #define REF_DEBUG 1 */
+
#include "asterisk/lock.h"
#include "asterisk/config.h"
#include "asterisk/module.h"
@@ -277,7 +278,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "sip/include/dialog.h"
#include "sip/include/dialplan_functions.h"
#include "sip/include/security_events.h"
-
+#include "asterisk/sip_api.h"
/*** DOCUMENTATION
@@ -339,6 +340,20 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
Always returns 0.
+
+
+ Send a custom INFO frame on specified channels.
+
+
+
+
+
+
+ SIPSendCustomINFO() allows you to send a custom INFO message on all
+ active SIP channels or on channels with the specified User Agent. This
+ application is only available if TEST_FRAMEWORK is defined.
+
+
Gets the specified SIP header from an incoming INVITE message.
@@ -671,7 +686,8 @@ static const struct sip_reasons {
{ AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
{ AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
{ AST_REDIRECTING_REASON_AWAY, "away" },
- { AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"}
+ { AST_REDIRECTING_REASON_CALL_FWD_DTE, "unknown"},
+ { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm"},
};
@@ -1090,6 +1106,13 @@ static void destroy_escs(void)
}
}
+struct state_notify_data {
+ int state;
+ int presence_state;
+ const char *presence_subtype;
+ const char *presence_message;
+};
+
/*!
* \details
* Here we implement the container for dialogs which are in the
@@ -1373,7 +1396,8 @@ static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target
static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context);
/*--- Device monitoring and Device/extension state/event handling */
-static int cb_extensionstate(const char *context, const char *exten, enum ast_extension_states state, void *data);
+static int extensionstate_update(char *context, char *exten, struct state_notify_data *data, struct sip_pvt *p);
+static int cb_extensionstate(char *context, char *exten, struct ast_state_cb_info *info, void *data);
static int sip_poke_noanswer(const void *data);
static int sip_poke_peer(struct sip_peer *peer, int force);
static void sip_poke_all_peers(void);
@@ -1505,7 +1529,7 @@ static int get_rpid(struct sip_pvt *p, struct sip_request *oreq);
static int get_rdnis(struct sip_pvt *p, struct sip_request *oreq, char **name, char **number, int *reason);
static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id);
static int get_msg_text(char *buf, int len, struct sip_request *req);
-static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout);
+static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout);
static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen);
static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen);
static int get_domain(const char *str, char *domain, int len);
@@ -3865,7 +3889,10 @@ static int __sip_autodestruct(const void *data)
/* If this is a subscription, tell the phone that we got a timeout */
if (p->subscribed && p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION) {
- transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
+ struct state_notify_data data = { 0, };
+ data.state = AST_EXTENSION_DEACTIVATED;
+
+ transmit_state_notify(p, &data, 1, TRUE); /* Send last notification */
p->subscribed = NONE;
append_history(p, "Subscribestatus", "timeout");
ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "");
@@ -6928,6 +6955,52 @@ static int initialize_udptl(struct sip_pvt *p)
return 0;
}
+int ast_sipinfo_send(
+ struct ast_channel *chan,
+ struct ast_variable *headers,
+ const char *content_type,
+ const char *content,
+ const char *useragent_filter)
+{
+ struct sip_pvt *p;
+ struct ast_variable *var;
+ struct sip_request req;
+ int res = -1;
+
+ ast_channel_lock(chan);
+
+ if (ast_channel_tech(chan) != &sip_tech) {
+ ast_log(LOG_WARNING, "Attempted to send a custom INFO on a non-SIP channel %s\n", ast_channel_name(chan));
+ ast_channel_unlock(chan);
+ return res;
+ }
+
+ p = ast_channel_tech_pvt(chan);
+ sip_pvt_lock(p);
+
+ if (!(ast_strlen_zero(useragent_filter))) {
+ int match = (strstr(p->useragent, useragent_filter)) ? 1 : 0;
+ if (!match) {
+ goto cleanup;
+ }
+ }
+
+ reqprep(&req, p, SIP_INFO, 0, 1);
+ for (var = headers; var; var = var->next) {
+ add_header(&req, var->name, var->value);
+ }
+ if (!ast_strlen_zero(content) && !ast_strlen_zero(content_type)) {
+ add_header(&req, "Content-Type", content_type);
+ add_content(&req, content);
+ }
+
+ res = send_request(p, &req, XMIT_RELIABLE, p->ocseq);
+
+cleanup:
+ sip_pvt_unlock(p);
+ ast_channel_unlock(chan);
+ return res;
+}
/*! \brief Play indication to user
* With SIP a lot of indications is sent as messages, letting the device play
the indication - busy signal, congestion etc
@@ -13070,8 +13143,14 @@ static int find_calling_channel(void *obj, void *arg, void *data, int flags)
return res ? CMP_MATCH | CMP_STOP : 0;
}
+/* XXX Candidate for moving into its own file */
+static int allow_notify_user_presence(struct sip_pvt *p)
+{
+ return (strstr(p->useragent, "Digium")) ? 1 : 0;
+}
+
/*! \brief Builds XML portion of NOTIFY messages for presence or dialog updates */
-static void state_notify_build_xml(int state, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto)
+static void state_notify_build_xml(struct state_notify_data *data, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto)
{
enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN;
const char *statestring = "terminated";
@@ -13079,7 +13158,7 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
const char *pidfnote= "Ready";
char hint[AST_MAX_EXTENSION];
- switch (state) {
+ switch (data->state) {
case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE):
statestring = (sip_cfg.notifyringing) ? "early" : "confirmed";
local_state = NOTIFY_INUSE;
@@ -13124,9 +13203,16 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
/* Check which device/devices we are watching and if they are registered */
if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) {
- char *hint2 = hint, *individual_hint = NULL;
+ char *hint2;
+ char *individual_hint = NULL;
int hint_count = 0, unavailable_count = 0;
+ /* strip off any possible PRESENCE providers from hint */
+ if ((hint2 = strrchr(hint, ','))) {
+ *hint2 = '\0';
+ }
+ hint2 = hint;
+
while ((individual_hint = strsep(&hint2, "&"))) {
hint_count++;
@@ -13174,12 +13260,30 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
ast_str_append(tmp, 0, "open\n");
else
ast_str_append(tmp, 0, "%s\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed");
+
+ if (allow_notify_user_presence(p) && (data->presence_state > 0)) {
+ ast_str_append(tmp, 0, "\n");
+ ast_str_append(tmp, 0, "\n");
+ ast_str_append(tmp, 0, "\n");
+ ast_str_append(tmp, 0, "%s\n",
+ ast_presence_state2str(data->presence_state),
+ S_OR(data->presence_subtype, ""),
+ S_OR(data->presence_message, ""));
+ ast_str_append(tmp, 0, "\n");
+ ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT",
+ "PresenceState: %s\r\n"
+ "Subtype: %s\r\n"
+ "Message: %s",
+ ast_presence_state2str(data->presence_state),
+ S_OR(data->presence_subtype, ""),
+ S_OR(data->presence_message, ""));
+ }
ast_str_append(tmp, 0, "\n\n");
break;
case DIALOG_INFO_XML: /* SNOM subscribes in this format */
ast_str_append(tmp, 0, "\n");
ast_str_append(tmp, 0, "\n", p->dialogver, full ? "full" : "partial", mto);
- if ((state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
+ if ((data->state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) {
const char *local_display = exten;
char *local_target = ast_strdupa(mto);
const char *remote_display = exten;
@@ -13256,7 +13360,7 @@ static void state_notify_build_xml(int state, int full, const char *exten, const
ast_str_append(tmp, 0, "
+
+
+ Send an out of call message to an endpoint.
+
+
+
+
+ The URI the message is to be sent to.
+
+
+ A From URI for the message if needed for the
+ message technology being used to send this message.
+
+ For SIP the from parameter can be a configured peer name
+ or in the form of "display-name" <URI>.
+
+
+
+ The message body text. This must not contain any newlines as that
+ conflicts with the AMI protocol.
+
+
+ Text bodies requiring the use of newlines have to be base64 encoded
+ in this field. Base64Body will be decoded before being sent out.
+ Base64Body takes precedence over Body.
+
+
+ Message variable to set, multiple Variable: headers are
+ allowed. The header value is a comma separated list of
+ name=value pairs.
+
+
+
***/
struct msg_data {
@@ -393,6 +439,12 @@ struct ast_msg *ast_msg_alloc(void)
return msg;
}
+struct ast_msg *ast_msg_ref(struct ast_msg *msg)
+{
+ ao2_ref(msg, 1);
+ return msg;
+}
+
struct ast_msg *ast_msg_destroy(struct ast_msg *msg)
{
ao2_ref(msg, -1);
@@ -519,7 +571,7 @@ static int msg_set_var_full(struct ast_msg *msg, const char *name, const char *v
return 0;
}
-static int msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value)
+int ast_msg_set_var_outbound(struct ast_msg *msg, const char *name, const char *value)
{
return msg_set_var_full(msg, name, value, 1);
}
@@ -850,6 +902,26 @@ static int msg_func_write(struct ast_channel *chan, const char *function,
ast_msg_set_from(msg, "%s", value);
} else if (!strcasecmp(data, "body")) {
ast_msg_set_body(msg, "%s", value);
+ } else if (!strcasecmp(data, "custom_data")) {
+ int outbound = -1;
+ if (!strcasecmp(value, "mark_all_outbound")) {
+ outbound = 1;
+ } else if (!strcasecmp(value, "clear_all_outbound")) {
+ outbound = 0;
+ } else {
+ ast_log(LOG_WARNING, "'%s' is not a valid value for custom_data\n", value);
+ }
+
+ if (outbound != -1) {
+ struct msg_data *hdr_data;
+ struct ao2_iterator iter = ao2_iterator_init(msg->vars, 0);
+
+ while ((hdr_data = ao2_iterator_next(&iter))) {
+ hdr_data->send = outbound;
+ ao2_ref(hdr_data, -1);
+ }
+ ao2_iterator_destroy(&iter);
+ }
} else {
ast_log(LOG_WARNING, "'%s' is not a valid write argument.\n", data);
}
@@ -910,7 +982,7 @@ static int msg_data_func_write(struct ast_channel *chan, const char *function,
ao2_lock(msg);
- msg_set_var_outbound(msg, data, value);
+ ast_msg_set_var_outbound(msg, data, value);
ao2_unlock(msg);
ao2_ref(msg, -1);
@@ -1041,6 +1113,120 @@ exit_cleanup:
return 0;
}
+static int action_messagesend(struct mansession *s, const struct message *m)
+{
+ const char *to = ast_strdupa(astman_get_header(m, "To"));
+ const char *from = astman_get_header(m, "From");
+ const char *body = astman_get_header(m, "Body");
+ const char *base64body = astman_get_header(m, "Base64Body");
+ char base64decoded[1301] = { 0, };
+ char *tech_name = NULL;
+ struct ast_variable *vars = NULL;
+ struct ast_variable *data = NULL;
+ struct ast_msg_tech_holder *tech_holder = NULL;
+ struct ast_msg *msg;
+ int res = -1;
+
+ if (ast_strlen_zero(to)) {
+ astman_send_error(s, m, "No 'To' address specified.");
+ return -1;
+ }
+
+ if (!ast_strlen_zero(base64body)) {
+ ast_base64decode((unsigned char *) base64decoded, base64body, sizeof(base64decoded) - 1);
+ body = base64decoded;
+ }
+
+ tech_name = ast_strdupa(to);
+ tech_name = strsep(&tech_name, ":");
+ {
+ struct ast_msg_tech tmp_msg_tech = {
+ .name = tech_name,
+ };
+ struct ast_msg_tech_holder tmp_tech_holder = {
+ .tech = &tmp_msg_tech,
+ };
+
+ tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER);
+ }
+
+ if (!tech_holder) {
+ astman_send_error(s, m, "Message technology not found.");
+ return -1;
+ }
+
+ if (!(msg = ast_msg_alloc())) {
+ ao2_ref(tech_holder, -1);
+ astman_send_error(s, m, "Internal failure\n");
+ return -1;
+ }
+
+ data = astman_get_variables(m);
+ for (vars = data; vars; vars = vars->next) {
+ ast_msg_set_var_outbound(msg, vars->name, vars->value);
+ }
+
+ ast_msg_set_body(msg, "%s", body);
+
+ ast_rwlock_rdlock(&tech_holder->tech_lock);
+ if (tech_holder->tech) {
+ res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, ""));
+ }
+ ast_rwlock_unlock(&tech_holder->tech_lock);
+
+ ast_variables_destroy(vars);
+ ao2_ref(tech_holder, -1);
+ ao2_ref(msg, -1);
+
+ if (res) {
+ astman_send_error(s, m, "Message failed to send.");
+ } else {
+ astman_send_ack(s, m, "Message successfully sent");
+ }
+ return res;
+}
+
+int ast_msg_send(struct ast_msg *msg, const char *to, const char *from)
+{
+ char *tech_name = NULL;
+ struct ast_msg_tech_holder *tech_holder = NULL;
+ int res = -1;
+
+ if (ast_strlen_zero(to)) {
+ ao2_ref(msg, -1);
+ return -1;
+ }
+
+ tech_name = ast_strdupa(to);
+ tech_name = strsep(&tech_name, ":");
+ {
+ struct ast_msg_tech tmp_msg_tech = {
+ .name = tech_name,
+ };
+ struct ast_msg_tech_holder tmp_tech_holder = {
+ .tech = &tmp_msg_tech,
+ };
+
+ tech_holder = ao2_find(msg_techs, &tmp_tech_holder, OBJ_POINTER);
+ }
+
+ if (!tech_holder) {
+ ao2_ref(msg, -1);
+ return -1;
+ }
+
+ ast_rwlock_rdlock(&tech_holder->tech_lock);
+ if (tech_holder->tech) {
+ res = tech_holder->tech->msg_send(msg, S_OR(to, ""), S_OR(from, ""));
+ }
+ ast_rwlock_unlock(&tech_holder->tech_lock);
+
+ ao2_ref(tech_holder, -1);
+ ao2_ref(msg, -1);
+
+ return res;
+}
+
int ast_msg_tech_register(const struct ast_msg_tech *tech)
{
struct ast_msg_tech_holder tmp_tech_holder = {
@@ -1125,6 +1311,7 @@ int ast_msg_init(void)
res = __ast_custom_function_register(&msg_function, NULL);
res |= __ast_custom_function_register(&msg_data_function, NULL);
res |= ast_register_application2(app_msg_send, msg_send_exec, NULL, NULL, NULL);
+ res |= ast_manager_register_xml_core("MessageSend", EVENT_FLAG_MESSAGE, action_messagesend);
return res;
}
diff --git a/main/pbx.c b/main/pbx.c
index bdaea72884..3ed7df4ed8 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -59,6 +59,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/musiconhold.h"
#include "asterisk/app.h"
#include "asterisk/devicestate.h"
+#include "asterisk/presencestate.h"
#include "asterisk/event.h"
#include "asterisk/hashtab.h"
#include "asterisk/module.h"
@@ -801,7 +802,7 @@ AST_APP_OPTIONS(waitexten_opts, {
struct ast_context;
struct ast_app;
-static struct ast_taskprocessor *device_state_tps;
+static struct ast_taskprocessor *extension_state_tps;
AST_THREADSTORAGE(switch_data);
AST_THREADSTORAGE(extensionstate_buf);
@@ -946,8 +947,16 @@ struct ast_hint {
* Will never be NULL while the hint is in the hints container.
*/
struct ast_exten *exten;
- struct ao2_container *callbacks; /*!< Callback container for this extension */
- int laststate; /*!< Last known state */
+ struct ao2_container *callbacks; /*!< Device state callback container for this extension */
+
+ /*! Dev state variables */
+ int laststate; /*!< Last known device state */
+
+ /*! Presence state variables */
+ int last_presence_state; /*!< Last known presence state */
+ char *last_presence_subtype; /*!< Last known presence subtype string */
+ char *last_presence_message; /*!< Last known presence message string */
+
char context_name[AST_MAX_CONTEXT];/*!< Context of destroyed hint extension. */
char exten_name[AST_MAX_EXTENSION];/*!< Extension of destroyed hint extension. */
};
@@ -1024,6 +1033,7 @@ static int remove_hintdevice(struct ast_hint *hint)
return 0;
}
+static char *parse_hint_device(struct ast_str *hint_args);
/*!
* \internal
* \brief Destroy the given hintdevice object.
@@ -1060,7 +1070,7 @@ static int add_hintdevice(struct ast_hint *hint, const char *devicelist)
return -1;
}
ast_str_set(&str, 0, "%s", devicelist);
- parse = ast_str_buffer(str);
+ parse = parse_hint_device(str);
while ((cur = strsep(&parse, "&"))) {
devicelength = strlen(cur);
@@ -1094,6 +1104,13 @@ static const struct cfextension_states {
{ AST_EXTENSION_INUSE | AST_EXTENSION_ONHOLD, "InUse&Hold" }
};
+struct presencechange {
+ char *provider;
+ int state;
+ char *subtype;
+ char *message;
+};
+
struct statechange {
AST_LIST_ENTRY(statechange) entry;
char dev[0];
@@ -1264,6 +1281,8 @@ static char *overrideswitch = NULL;
/*! \brief Subscription for device state change events */
static struct ast_event_sub *device_state_sub;
+/*! \brief Subscription for presence state change events */
+static struct ast_event_sub *presence_state_sub;
AST_MUTEX_DEFINE_STATIC(maxcalllock);
static int countcalls;
@@ -4474,6 +4493,42 @@ enum ast_extension_states ast_devstate_to_extenstate(enum ast_device_state devst
return AST_EXTENSION_NOT_INUSE;
}
+/*!
+ * \internal
+ * \brief Parse out the presence portion of the hint string
+ */
+static char *parse_hint_presence(struct ast_str *hint_args)
+{
+ char *copy = ast_strdupa(ast_str_buffer(hint_args));
+ char *tmp = "";
+
+ if ((tmp = strrchr(copy, ','))) {
+ *tmp = '\0';
+ tmp++;
+ } else {
+ return NULL;
+ }
+ ast_str_set(&hint_args, 0, "%s", tmp);
+ return ast_str_buffer(hint_args);
+}
+
+/*!
+ * \internal
+ * \brief Parse out the device portion of the hint string
+ */
+static char *parse_hint_device(struct ast_str *hint_args)
+{
+ char *copy = ast_strdupa(ast_str_buffer(hint_args));
+ char *tmp;
+
+ if ((tmp = strrchr(copy, ','))) {
+ *tmp = '\0';
+ }
+
+ ast_str_set(&hint_args, 0, "%s", copy);
+ return ast_str_buffer(hint_args);
+}
+
static int ast_extension_state3(struct ast_str *hint_app)
{
char *cur;
@@ -4481,7 +4536,7 @@ static int ast_extension_state3(struct ast_str *hint_app)
struct ast_devstate_aggregate agg;
/* One or more devices separated with a & character */
- rest = ast_str_buffer(hint_app);
+ rest = parse_hint_device(hint_app);
ast_devstate_aggregate_init(&agg);
while ((cur = strsep(&rest, "&"))) {
@@ -4539,6 +4594,209 @@ int ast_extension_state(struct ast_channel *c, const char *context, const char *
return ast_extension_state2(e); /* Check all devices in the hint */
}
+static int extension_presence_state_helper(struct ast_exten *e, char **subtype, char **message)
+{
+ struct ast_str *hint_app = ast_str_thread_get(&extensionstate_buf, 32);
+ char *presence_provider;
+ const char *app;
+
+ if (!e || !hint_app) {
+ return -1;
+ }
+
+ app = ast_get_extension_app(e);
+ if (ast_strlen_zero(app)) {
+ return -1;
+ }
+
+ ast_str_set(&hint_app, 0, "%s", app);
+ presence_provider = parse_hint_presence(hint_app);
+
+ if (ast_strlen_zero(presence_provider)) {
+ /* No presence string in the hint */
+ return 0;
+ }
+
+ return ast_presence_state(presence_provider, subtype, message);
+}
+
+int ast_hint_presence_state(struct ast_channel *c, const char *context, const char *exten, char **subtype, char **message)
+{
+ struct ast_exten *e;
+
+ if (!(e = ast_hint_extension(c, context, exten))) { /* Do we have a hint for this extension ? */
+ return -1; /* No hint, return -1 */
+ }
+
+ if (e->exten[0] == '_') {
+ /* Create this hint on-the-fly */
+ ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
+ e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
+ e->registrar);
+ if (!(e = ast_hint_extension(c, context, exten))) {
+ /* Improbable, but not impossible */
+ return -1;
+ }
+ }
+
+ return extension_presence_state_helper(e, subtype, message);
+}
+
+static int execute_state_callback(ast_state_cb_type cb,
+ const char *context,
+ const char *exten,
+ void *data,
+ enum ast_state_cb_update_reason reason,
+ struct ast_hint *hint)
+{
+ int res = 0;
+ struct ast_state_cb_info info = { 0, };
+
+ info.reason = reason;
+
+ /* Copy over current hint data */
+ if (hint) {
+ ao2_lock(hint);
+ info.exten_state = hint->laststate;
+ info.presence_state = hint->last_presence_state;
+ if (!(ast_strlen_zero(hint->last_presence_subtype))) {
+ info.presence_subtype = ast_strdupa(hint->last_presence_subtype);
+ } else {
+ info.presence_subtype = "";
+ }
+ if (!(ast_strlen_zero(hint->last_presence_message))) {
+ info.presence_message = ast_strdupa(hint->last_presence_message);
+ } else {
+ info.presence_message = "";
+ }
+ ao2_unlock(hint);
+ } else {
+ info.exten_state = AST_EXTENSION_REMOVED;
+ }
+
+ /* NOTE: The casts will not be needed for v10 and later */
+ res = cb((char *) context, (char *) exten, &info, data);
+
+ return res;
+}
+
+static int handle_presencechange(void *datap)
+{
+ struct ast_hint *hint;
+ struct ast_str *hint_app = NULL;
+ struct presencechange *pc = datap;
+ struct ao2_iterator i;
+ struct ao2_iterator cb_iter;
+ char context_name[AST_MAX_CONTEXT];
+ char exten_name[AST_MAX_EXTENSION];
+ int res = -1;
+
+ hint_app = ast_str_create(1024);
+ if (!hint_app) {
+ goto presencechange_cleanup;
+ }
+
+ ast_mutex_lock(&context_merge_lock);/* Hold off ast_merge_contexts_and_delete */
+ i = ao2_iterator_init(hints, 0);
+ for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
+ struct ast_state_cb *state_cb;
+ const char *app;
+ char *parse;
+
+ ao2_lock(hint);
+
+ if (!hint->exten) {
+ /* The extension has already been destroyed */
+ ao2_unlock(hint);
+ continue;
+ }
+
+ /* Does this hint monitor the device that changed state? */
+ app = ast_get_extension_app(hint->exten);
+ if (ast_strlen_zero(app)) {
+ /* The hint does not monitor presence at all. */
+ ao2_unlock(hint);
+ continue;
+ }
+
+ ast_str_set(&hint_app, 0, "%s", app);
+ parse = parse_hint_presence(hint_app);
+ if (ast_strlen_zero(parse)) {
+ ao2_unlock(hint);
+ continue;
+ }
+ if (strcasecmp(parse, pc->provider)) {
+ /* The hint does not monitor the presence provider. */
+ ao2_unlock(hint);
+ continue;
+ }
+
+ /*
+ * Save off strings in case the hint extension gets destroyed
+ * while we are notifying the watchers.
+ */
+ ast_copy_string(context_name,
+ ast_get_context_name(ast_get_extension_context(hint->exten)),
+ sizeof(context_name));
+ ast_copy_string(exten_name, ast_get_extension_name(hint->exten),
+ sizeof(exten_name));
+ ast_str_set(&hint_app, 0, "%s", ast_get_extension_app(hint->exten));
+
+ /* Check to see if update is necessary */
+ if ((hint->last_presence_state == pc->state) &&
+ ((hint->last_presence_subtype && pc->subtype && !strcmp(hint->last_presence_subtype, pc->subtype)) || (!hint->last_presence_subtype && !pc->subtype)) &&
+ ((hint->last_presence_message && pc->message && !strcmp(hint->last_presence_message, pc->message)) || (!hint->last_presence_message && !pc->message))) {
+
+ /* this update is the same as the last, do nothing */
+ ao2_unlock(hint);
+ continue;
+ }
+
+ /* update new values */
+ ast_free(hint->last_presence_subtype);
+ ast_free(hint->last_presence_message);
+ hint->last_presence_state = pc->state;
+ hint->last_presence_subtype = pc->subtype ? ast_strdup(pc->subtype) : NULL;
+ hint->last_presence_message = pc->message ? ast_strdup(pc->message) : NULL;
+
+ ao2_unlock(hint);
+
+ /* For general callbacks */
+ cb_iter = ao2_iterator_init(statecbs, 0);
+ for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_PRESENCE,
+ hint);
+ }
+ ao2_iterator_destroy(&cb_iter);
+
+ /* For extension callbacks */
+ cb_iter = ao2_iterator_init(hint->callbacks, 0);
+ for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_PRESENCE,
+ hint);
+ }
+ ao2_iterator_destroy(&cb_iter);
+ }
+ ao2_iterator_destroy(&i);
+ ast_mutex_unlock(&context_merge_lock);
+
+ res = 0;
+
+presencechange_cleanup:
+ ast_free(hint_app);
+ ao2_ref(pc, -1);
+
+ return res;
+}
+
static int handle_statechange(void *datap)
{
struct ast_hint *hint;
@@ -4626,14 +4884,24 @@ static int handle_statechange(void *datap)
/* For general callbacks */
cb_iter = ao2_iterator_init(statecbs, 0);
for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
- state_cb->change_cb(context_name, exten_name, state, state_cb->data);
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_DEVICE,
+ hint);
}
ao2_iterator_destroy(&cb_iter);
/* For extension callbacks */
cb_iter = ao2_iterator_init(hint->callbacks, 0);
for (; (state_cb = ao2_iterator_next(&cb_iter)); ao2_ref(state_cb, -1)) {
- state_cb->change_cb(context_name, exten_name, state, state_cb->data);
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_DEVICE,
+ hint);
}
ao2_iterator_destroy(&cb_iter);
}
@@ -4805,7 +5073,6 @@ int ast_extension_state_del(int id, ast_state_cb_type change_cb)
return ret;
}
-
static int hint_id_cmp(void *obj, void *arg, int flags)
{
const struct ast_state_cb *cb = obj;
@@ -4840,14 +5107,21 @@ static void destroy_hint(void *obj)
context_name = hint->context_name;
exten_name = hint->exten_name;
}
+ hint->laststate = AST_EXTENSION_DEACTIVATED;
while ((state_cb = ao2_callback(hint->callbacks, OBJ_UNLINK, NULL, NULL))) {
/* Notify with -1 and remove all callbacks */
- state_cb->change_cb(context_name, exten_name, AST_EXTENSION_DEACTIVATED,
- state_cb->data);
+ execute_state_callback(state_cb->change_cb,
+ context_name,
+ exten_name,
+ state_cb->data,
+ AST_HINT_UPDATE_DEVICE,
+ hint);
ao2_ref(state_cb, -1);
}
ao2_ref(hint->callbacks, -1);
}
+ ast_free(hint->last_presence_subtype);
+ ast_free(hint->last_presence_message);
}
/*! \brief Remove hint from extension */
@@ -4890,6 +5164,9 @@ static int ast_add_hint(struct ast_exten *e)
{
struct ast_hint *hint_new;
struct ast_hint *hint_found;
+ char *message = NULL;
+ char *subtype = NULL;
+ int presence_state;
if (!e) {
return -1;
@@ -4913,6 +5190,12 @@ static int ast_add_hint(struct ast_exten *e)
}
hint_new->exten = e;
hint_new->laststate = ast_extension_state2(e);
+ if ((presence_state = extension_presence_state_helper(e, &subtype, &message)) > 0) {
+ hint_new->last_presence_state = presence_state;
+ hint_new->last_presence_subtype = subtype;
+ hint_new->last_presence_message = message;
+ message = subtype = NULL;
+ }
/* Prevent multiple add hints from adding the same hint at the same time. */
ao2_lock(hints);
@@ -7432,6 +7715,10 @@ struct store_hint {
char *exten;
AST_LIST_HEAD_NOLOCK(, ast_state_cb) callbacks;
int laststate;
+ int last_presence_state;
+ char *last_presence_subtype;
+ char *last_presence_message;
+
AST_LIST_ENTRY(store_hint) list;
char data[1];
};
@@ -7633,6 +7920,13 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
strcpy(saved_hint->data, hint->exten->parent->name);
saved_hint->exten = saved_hint->data + strlen(saved_hint->context) + 1;
strcpy(saved_hint->exten, hint->exten->exten);
+ if (hint->last_presence_subtype) {
+ saved_hint->last_presence_subtype = ast_strdup(hint->last_presence_subtype);
+ }
+ if (hint->last_presence_message) {
+ saved_hint->last_presence_message = ast_strdup(hint->last_presence_message);
+ }
+ saved_hint->last_presence_state = hint->last_presence_state;
ao2_unlock(hint);
AST_LIST_INSERT_HEAD(&hints_stored, saved_hint, list);
}
@@ -7686,8 +7980,15 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
ao2_ref(thiscb, -1);
}
hint->laststate = saved_hint->laststate;
+ hint->last_presence_state = saved_hint->last_presence_state;
+ hint->last_presence_subtype = saved_hint->last_presence_subtype;
+ hint->last_presence_message = saved_hint->last_presence_message;
ao2_unlock(hint);
ao2_ref(hint, -1);
+ /*
+ * The free of saved_hint->last_presence_subtype and
+ * saved_hint->last_presence_message is not necessary here.
+ */
ast_free(saved_hint);
}
}
@@ -7702,11 +8003,17 @@ void ast_merge_contexts_and_delete(struct ast_context **extcontexts, struct ast_
while ((saved_hint = AST_LIST_REMOVE_HEAD(&hints_removed, list))) {
/* this hint has been removed, notify the watchers */
while ((thiscb = AST_LIST_REMOVE_HEAD(&saved_hint->callbacks, entry))) {
- thiscb->change_cb(saved_hint->context, saved_hint->exten,
- AST_EXTENSION_REMOVED, thiscb->data);
+ execute_state_callback(thiscb->change_cb,
+ saved_hint->context,
+ saved_hint->exten,
+ thiscb->data,
+ AST_HINT_UPDATE_DEVICE,
+ NULL);
/* Ref that we added when putting into saved_hint->callbacks */
ao2_ref(thiscb, -1);
}
+ ast_free(saved_hint->last_presence_subtype);
+ ast_free(saved_hint->last_presence_message);
ast_free(saved_hint);
}
@@ -10469,6 +10776,51 @@ static int pbx_builtin_sayphonetic(struct ast_channel *chan, const char *data)
return res;
}
+static void presencechange_destroy(void *data)
+{
+ struct presencechange *pc = data;
+ ast_free(pc->provider);
+ ast_free(pc->subtype);
+ ast_free(pc->message);
+}
+
+static void presence_state_cb(const struct ast_event *event, void *unused)
+{
+ struct presencechange *pc;
+ const char *tmp;
+
+ if (!(pc = ao2_alloc(sizeof(*pc), presencechange_destroy))) {
+ return;
+ }
+
+ tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER);
+ if (ast_strlen_zero(tmp)) {
+ ast_log(LOG_ERROR, "Received invalid event that had no presence provider IE\n");
+ ao2_ref(pc, -1);
+ return;
+ }
+ pc->provider = ast_strdup(tmp);
+
+ pc->state = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
+ if (pc->state < 0) {
+ ao2_ref(pc, -1);
+ return;
+ }
+
+ if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE))) {
+ pc->subtype = ast_strdup(tmp);
+ }
+
+ if ((tmp = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE))) {
+ pc->message = ast_strdup(tmp);
+ }
+
+ /* The task processor thread is taking our reference to the presencechange object. */
+ if (ast_taskprocessor_push(extension_state_tps, handle_presencechange, pc) < 0) {
+ ao2_ref(pc, -1);
+ }
+}
+
static void device_state_cb(const struct ast_event *event, void *unused)
{
const char *device;
@@ -10483,7 +10835,7 @@ static void device_state_cb(const struct ast_event *event, void *unused)
if (!(sc = ast_calloc(1, sizeof(*sc) + strlen(device) + 1)))
return;
strcpy(sc->dev, device);
- if (ast_taskprocessor_push(device_state_tps, handle_statechange, sc) < 0) {
+ if (ast_taskprocessor_push(extension_state_tps, handle_statechange, sc) < 0) {
ast_free(sc);
}
}
@@ -10515,6 +10867,9 @@ static int hints_data_provider_get(const struct ast_data_search *search,
ast_data_add_str(data_hint, "context", ast_get_context_name(ast_get_extension_context(hint->exten)));
ast_data_add_str(data_hint, "application", ast_get_extension_app(hint->exten));
ast_data_add_str(data_hint, "state", ast_extension_state2str(hint->laststate));
+ ast_data_add_str(data_hint, "presence_state", ast_presence_state2str(hint->last_presence_state));
+ ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_subtype, ""));
+ ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_message, ""));
ast_data_add_int(data_hint, "watchers", watchers);
if (!ast_data_search_match(search, data_hint)) {
@@ -10541,7 +10896,7 @@ int load_pbx(void)
/* Initialize the PBX */
ast_verb(1, "Asterisk PBX Core Initializing\n");
- if (!(device_state_tps = ast_taskprocessor_get("pbx-core", 0))) {
+ if (!(extension_state_tps = ast_taskprocessor_get("pbx-core", 0))) {
ast_log(LOG_WARNING, "failed to create pbx-core taskprocessor\n");
}
@@ -10568,6 +10923,11 @@ int load_pbx(void)
return -1;
}
+ if (!(presence_state_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE, presence_state_cb, "pbx Presence State Change", NULL,
+ AST_EVENT_IE_END))) {
+ return -1;
+ }
+
return 0;
}
diff --git a/main/presencestate.c b/main/presencestate.c
new file mode 100644
index 0000000000..df64dab246
--- /dev/null
+++ b/main/presencestate.c
@@ -0,0 +1,317 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2011-2012, Digium, Inc.
+ *
+ * David Vossel
+ *
+ * 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 Presence state management
+ */
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/_private.h"
+#include "asterisk/utils.h"
+#include "asterisk/lock.h"
+#include "asterisk/linkedlists.h"
+#include "asterisk/presencestate.h"
+#include "asterisk/pbx.h"
+#include "asterisk/app.h"
+#include "asterisk/event.h"
+
+/*! \brief Device state strings for printing */
+static const struct {
+ const char *string;
+ enum ast_presence_state state;
+
+} state2string[] = {
+ { "not_set", AST_PRESENCE_NOT_SET},
+ { "unavailable", AST_PRESENCE_UNAVAILABLE },
+ { "available", AST_PRESENCE_AVAILABLE},
+ { "away", AST_PRESENCE_AWAY},
+ { "xa", AST_PRESENCE_XA},
+ { "chat", AST_PRESENCE_CHAT},
+ { "dnd", AST_PRESENCE_DND},
+};
+
+/*! \brief Flag for the queue */
+static ast_cond_t change_pending;
+
+struct state_change {
+ AST_LIST_ENTRY(state_change) list;
+ char provider[1];
+};
+
+/*! \brief A presence state provider */
+struct presence_state_provider {
+ char label[40];
+ ast_presence_state_prov_cb_type callback;
+ AST_RWLIST_ENTRY(presence_state_provider) list;
+};
+
+/*! \brief A list of providers */
+static AST_RWLIST_HEAD_STATIC(presence_state_providers, presence_state_provider);
+
+/*! \brief The state change queue. State changes are queued
+ for processing by a separate thread */
+static AST_LIST_HEAD_STATIC(state_changes, state_change);
+
+/*! \brief The presence state change notification thread */
+static pthread_t change_thread = AST_PTHREADT_NULL;
+
+const char *ast_presence_state2str(enum ast_presence_state state)
+{
+ int i;
+ for (i = 0; i < ARRAY_LEN(state2string); i++) {
+ if (state == state2string[i].state) {
+ return state2string[i].string;
+ }
+ }
+ return "";
+}
+
+enum ast_presence_state ast_presence_state_val(const char *val)
+{
+ int i;
+ for (i = 0; i < ARRAY_LEN(state2string); i++) {
+ if (!strcasecmp(val, state2string[i].string)) {
+ return state2string[i].state;
+ }
+ }
+ return AST_PRESENCE_INVALID;
+}
+
+static enum ast_presence_state presence_state_cached(const char *presence_provider, char **subtype, char **message)
+{
+ enum ast_presence_state res = AST_PRESENCE_INVALID;
+ struct ast_event *event;
+ const char *_subtype;
+ const char *_message;
+
+ event = ast_event_get_cached(AST_EVENT_PRESENCE_STATE,
+ AST_EVENT_IE_PRESENCE_PROVIDER, AST_EVENT_IE_PLTYPE_STR, presence_provider,
+ AST_EVENT_IE_END);
+
+ if (!event) {
+ return res;
+ }
+
+ res = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
+ _subtype = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE);
+ _message = ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE);
+
+ *subtype = !ast_strlen_zero(_subtype) ? ast_strdup(_subtype) : NULL;
+ *message = !ast_strlen_zero(_message) ? ast_strdup(_message) : NULL;
+ ast_event_destroy(event);
+
+ return res;
+}
+
+static enum ast_presence_state ast_presence_state_helper(const char *presence_provider, char **subtype, char **message, int check_cache)
+{
+ struct presence_state_provider *provider;
+ char *address;
+ char *label = ast_strdupa(presence_provider);
+ int res = AST_PRESENCE_INVALID;
+
+ if (check_cache) {
+ res = presence_state_cached(presence_provider, subtype, message);
+ if (res != AST_PRESENCE_INVALID) {
+ return res;
+ }
+ }
+
+ if ((address = strchr(label, ':'))) {
+ *address = '\0';
+ address++;
+ } else {
+ ast_log(LOG_WARNING, "No label found for presence state provider: %s\n", presence_provider);
+ return res;
+ }
+
+ AST_RWLIST_RDLOCK(&presence_state_providers);
+ AST_RWLIST_TRAVERSE(&presence_state_providers, provider, list) {
+ ast_debug(5, "Checking provider %s with %s\n", provider->label, label);
+
+ if (!strcasecmp(provider->label, label)) {
+ res = provider->callback(address, subtype, message);
+ break;
+ }
+ }
+ AST_RWLIST_UNLOCK(&presence_state_providers);
+
+
+ return res;
+}
+
+enum ast_presence_state ast_presence_state(const char *presence_provider, char **subtype, char **message)
+{
+ return ast_presence_state_helper(presence_provider, subtype, message, 1);
+}
+
+enum ast_presence_state ast_presence_state_nocache(const char *presence_provider, char **subtype, char **message)
+{
+ return ast_presence_state_helper(presence_provider, subtype, message, 0);
+}
+
+int ast_presence_state_prov_add(const char *label, ast_presence_state_prov_cb_type callback)
+{
+ struct presence_state_provider *provider;
+
+ if (!callback || !(provider = ast_calloc(1, sizeof(*provider)))) {
+ return -1;
+ }
+
+ provider->callback = callback;
+ ast_copy_string(provider->label, label, sizeof(provider->label));
+
+ AST_RWLIST_WRLOCK(&presence_state_providers);
+ AST_RWLIST_INSERT_HEAD(&presence_state_providers, provider, list);
+ AST_RWLIST_UNLOCK(&presence_state_providers);
+
+ return 0;
+}
+int ast_presence_state_prov_del(const char *label)
+{
+ struct presence_state_provider *provider;
+ int res = -1;
+
+ AST_RWLIST_WRLOCK(&presence_state_providers);
+ AST_RWLIST_TRAVERSE_SAFE_BEGIN(&presence_state_providers, provider, list) {
+ if (!strcasecmp(provider->label, label)) {
+ AST_RWLIST_REMOVE_CURRENT(list);
+ ast_free(provider);
+ res = 0;
+ break;
+ }
+ }
+ AST_RWLIST_TRAVERSE_SAFE_END;
+ AST_RWLIST_UNLOCK(&presence_state_providers);
+
+ return res;
+}
+
+static void presence_state_event(const char *provider,
+ enum ast_presence_state state,
+ const char *subtype,
+ const char *message)
+{
+ struct ast_event *event;
+
+ if (!(event = ast_event_new(AST_EVENT_PRESENCE_STATE,
+ AST_EVENT_IE_PRESENCE_PROVIDER, AST_EVENT_IE_PLTYPE_STR, provider,
+ AST_EVENT_IE_PRESENCE_STATE, AST_EVENT_IE_PLTYPE_UINT, state,
+ AST_EVENT_IE_PRESENCE_SUBTYPE, AST_EVENT_IE_PLTYPE_STR, S_OR(subtype, ""),
+ AST_EVENT_IE_PRESENCE_MESSAGE, AST_EVENT_IE_PLTYPE_STR, S_OR(message, ""),
+ AST_EVENT_IE_END))) {
+ return;
+ }
+
+ ast_event_queue_and_cache(event);
+}
+
+static void do_presence_state_change(const char *provider)
+{
+ char *subtype = NULL;
+ char *message = NULL;
+ enum ast_presence_state state;
+
+ state = ast_presence_state_helper(provider, &subtype, &message, 0);
+
+ if (state < 0) {
+ return;
+ }
+
+ presence_state_event(provider, state, subtype, message);
+ ast_free(subtype);
+ ast_free(message);
+}
+
+int ast_presence_state_changed_literal(enum ast_presence_state state,
+ const char *subtype,
+ const char *message,
+ const char *presence_provider)
+{
+ struct state_change *change;
+
+ if (state != AST_PRESENCE_NOT_SET) {
+ presence_state_event(presence_provider, state, subtype, message);
+ } else if ((change_thread == AST_PTHREADT_NULL) ||
+ !(change = ast_calloc(1, sizeof(*change) + strlen(presence_provider)))) {
+ do_presence_state_change(presence_provider);
+ } else {
+ strcpy(change->provider, presence_provider);
+ AST_LIST_LOCK(&state_changes);
+ AST_LIST_INSERT_TAIL(&state_changes, change, list);
+ ast_cond_signal(&change_pending);
+ AST_LIST_UNLOCK(&state_changes);
+ }
+
+ return 0;
+}
+
+int ast_presence_state_changed(enum ast_presence_state state,
+ const char *subtype,
+ const char *message,
+ const char *fmt, ...)
+{
+ char buf[AST_MAX_EXTENSION];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ va_end(ap);
+
+ return ast_presence_state_changed_literal(state, subtype, message, buf);
+}
+
+/*! \brief Go through the presence state change queue and update changes in the presence state thread */
+static void *do_presence_changes(void *data)
+{
+ struct state_change *next, *current;
+
+ for (;;) {
+ /* This basically pops off any state change entries, resets the list back to NULL, unlocks, and processes each state change */
+ AST_LIST_LOCK(&state_changes);
+ if (AST_LIST_EMPTY(&state_changes))
+ ast_cond_wait(&change_pending, &state_changes.lock);
+ next = AST_LIST_FIRST(&state_changes);
+ AST_LIST_HEAD_INIT_NOLOCK(&state_changes);
+ AST_LIST_UNLOCK(&state_changes);
+
+ /* Process each state change */
+ while ((current = next)) {
+ next = AST_LIST_NEXT(current, list);
+ do_presence_state_change(current->provider);
+ ast_free(current);
+ }
+ }
+
+ return NULL;
+}
+
+int ast_presence_state_engine_init(void)
+{
+ ast_cond_init(&change_pending, NULL);
+ if (ast_pthread_create_background(&change_thread, NULL, do_presence_changes, NULL) < 0) {
+ ast_log(LOG_ERROR, "Unable to start presence state change thread.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
diff --git a/tests/test_config.c b/tests/test_config.c
index c8f7a077b2..57d2bbdedd 100644
--- a/tests/test_config.c
+++ b/tests/test_config.c
@@ -1,9 +1,9 @@
/*
* Asterisk -- An open source telephony toolkit.
*
- * Copyright (C) 2011, Digium, Inc.
+ * Copyright (C) 2010, Digium, Inc.
*
- * Terry Wilson
+ * Mark Michelson
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
@@ -18,9 +18,9 @@
/*!
* \file
- * \brief Config-related tests
+ * \brief Configuration unit tests
*
- * \author Terry Wilson
+ * \author Mark Michelson
*
*/
@@ -31,13 +31,14 @@
#include "asterisk.h"
-ASTERISK_FILE_VERSION(__FILE__, "")
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
#include /* HUGE_VAL */
-#include "asterisk/test.h"
-#include "asterisk/module.h"
#include "asterisk/config.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/paths.h"
#include "asterisk/config_options.h"
#include "asterisk/netsock2.h"
#include "asterisk/acl.h"
@@ -45,6 +46,342 @@ ASTERISK_FILE_VERSION(__FILE__, "")
#include "asterisk/utils.h"
#include "asterisk/logger.h"
+#define CONFIG_FILE "test_config.conf"
+
+/*
+ * This builds the folowing config:
+ * [Capitals]
+ * Germany = Berlin
+ * China = Beijing
+ * Canada = Ottawa
+ *
+ * [Protagonists]
+ * 1984 = Winston Smith
+ * Green Eggs And Ham = Sam I Am
+ * The Kalevala = Vainamoinen
+ *
+ * This config is used for all tests below.
+ */
+const char cat1[] = "Capitals";
+const char cat1varname1[] = "Germany";
+const char cat1varvalue1[] = "Berlin";
+const char cat1varname2[] = "China";
+const char cat1varvalue2[] = "Beijing";
+const char cat1varname3[] = "Canada";
+const char cat1varvalue3[] = "Ottawa";
+
+const char cat2[] = "Protagonists";
+const char cat2varname1[] = "1984";
+const char cat2varvalue1[] = "Winston Smith";
+const char cat2varname2[] = "Green Eggs And Ham";
+const char cat2varvalue2[] = "Sam I Am";
+const char cat2varname3[] = "The Kalevala";
+const char cat2varvalue3[] = "Vainamoinen";
+
+struct pair {
+ const char *name;
+ const char *val;
+};
+
+struct association {
+ const char *category;
+ struct pair vars[3];
+} categories [] = {
+ { cat1,
+ {
+ { cat1varname1, cat1varvalue1 },
+ { cat1varname2, cat1varvalue2 },
+ { cat1varname3, cat1varvalue3 },
+ }
+ },
+ { cat2,
+ {
+ { cat2varname1, cat2varvalue1 },
+ { cat2varname2, cat2varvalue2 },
+ { cat2varname3, cat2varvalue3 },
+ }
+ },
+};
+
+/*!
+ * \brief Build ast_config struct from above definitions
+ *
+ * \retval NULL Failed to build the config
+ * \retval non-NULL An ast_config struct populated with data
+ */
+static struct ast_config *build_cfg(void)
+{
+ struct ast_config *cfg;
+ struct association *cat_iter;
+ struct pair *var_iter;
+ size_t i;
+ size_t j;
+
+ cfg = ast_config_new();
+ if (!cfg) {
+ goto fail;
+ }
+
+ for (i = 0; i < ARRAY_LEN(categories); ++i) {
+ struct ast_category *cat;
+ cat_iter = &categories[i];
+
+ cat = ast_category_new(cat_iter->category, "", 999999);
+ if (!cat) {
+ goto fail;
+ }
+ ast_category_append(cfg, cat);
+
+ for (j = 0; j < ARRAY_LEN(cat_iter->vars); ++j) {
+ struct ast_variable *var;
+ var_iter = &cat_iter->vars[j];
+
+ var = ast_variable_new(var_iter->name, var_iter->val, "");
+ if (!var) {
+ goto fail;
+ }
+ ast_variable_append(cat, var);
+ }
+ }
+
+ return cfg;
+
+fail:
+ ast_config_destroy(cfg);
+ return NULL;
+}
+
+/*!
+ * \brief Tests that the contents of an ast_config is what is expected
+ *
+ * \param cfg Config to test
+ * \retval -1 Failed to pass a test
+ * \retval 0 Config passes checks
+ */
+static int test_config_validity(struct ast_config *cfg)
+{
+ int i;
+ const char *cat_iter = NULL;
+ /* Okay, let's see if the correct content is there */
+ for (i = 0; i < ARRAY_LEN(categories); ++i) {
+ struct ast_variable *var = NULL;
+ size_t j;
+ cat_iter = ast_category_browse(cfg, cat_iter);
+ if (strcmp(cat_iter, categories[i].category)) {
+ ast_log(LOG_ERROR, "Category name mismatch, %s does not match %s\n", cat_iter, categories[i].category);
+ return -1;
+ }
+ for (j = 0; j < ARRAY_LEN(categories[i].vars); ++j) {
+ var = var ? var->next : ast_variable_browse(cfg, cat_iter);
+ if (strcmp(var->name, categories[i].vars[j].name)) {
+ ast_log(LOG_ERROR, "Variable name mismatch, %s does not match %s\n", var->name, categories[i].vars[j].name);
+ return -1;
+ }
+ if (strcmp(var->value, categories[i].vars[j].val)) {
+ ast_log(LOG_ERROR, "Variable value mismatch, %s does not match %s\n", var->value, categories[i].vars[j].val);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+AST_TEST_DEFINE(copy_config)
+{
+ enum ast_test_result_state res = AST_TEST_FAIL;
+ struct ast_config *cfg = NULL;
+ struct ast_config *copy = NULL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "copy_config";
+ info->category = "/main/config/";
+ info->summary = "Test copying configuration";
+ info->description =
+ "Ensure that variables and categories are copied correctly";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ cfg = build_cfg();
+ if (!cfg) {
+ goto out;
+ }
+
+ copy = ast_config_copy(cfg);
+ if (!copy) {
+ goto out;
+ }
+
+ if (test_config_validity(copy) != 0) {
+ goto out;
+ }
+
+ res = AST_TEST_PASS;
+
+out:
+ ast_config_destroy(cfg);
+ ast_config_destroy(copy);
+ return res;
+}
+
+/*!
+ * \brief Write the config file to disk
+ *
+ * This is necessary for testing config hooks since
+ * they are only triggered when a config is read from
+ * its intended storage medium
+ */
+static int write_config_file(void)
+{
+ int i;
+ FILE *config_file;
+ char filename[PATH_MAX];
+
+ snprintf(filename, sizeof(filename), "%s/%s",
+ ast_config_AST_CONFIG_DIR, CONFIG_FILE);
+ config_file = fopen(filename, "w");
+
+ if (!config_file) {
+ return -1;
+ }
+
+ for (i = 0; i < ARRAY_LEN(categories); ++i) {
+ int j;
+ fprintf(config_file, "[%s]\n", categories[i].category);
+ for (j = 0; j < ARRAY_LEN(categories[i].vars); ++j) {
+ fprintf(config_file, "%s = %s\n",
+ categories[i].vars[j].name,
+ categories[i].vars[j].val);
+ }
+ }
+
+ fclose(config_file);
+ return 0;
+}
+
+/*!
+ * \brief Delete config file created by write_config_file
+ */
+static void delete_config_file(void)
+{
+ char filename[PATH_MAX];
+ snprintf(filename, sizeof(filename), "%s/%s",
+ ast_config_AST_CONFIG_DIR, CONFIG_FILE);
+ unlink(filename);
+}
+
+/*
+ * Boolean to indicate if the config hook has run
+ */
+static int hook_run;
+
+/*
+ * Boolean to indicate if, when the hook runs, the
+ * data passed to it is what is expected
+ */
+static int hook_config_sane;
+
+static int hook_cb(struct ast_config *cfg)
+{
+ hook_run = 1;
+ if (test_config_validity(cfg) == 0) {
+ hook_config_sane = 1;
+ }
+ ast_config_destroy(cfg);
+ return 0;
+}
+
+AST_TEST_DEFINE(config_hook)
+{
+ enum ast_test_result_state res = AST_TEST_FAIL;
+ enum config_hook_flags hook_flags = { 0, };
+ struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
+ struct ast_config *cfg;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "config_hook";
+ info->category = "/main/config/";
+ info->summary = "Test config hooks";
+ info->description =
+ "Ensure that config hooks are called at approriate times,"
+ "not called at inappropriate times, and that all information"
+ "that should be present is present.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ write_config_file();
+
+ /*
+ * Register a config hook to run when CONFIG_FILE is loaded by this module
+ */
+ ast_config_hook_register("test_hook",
+ CONFIG_FILE,
+ AST_MODULE,
+ hook_flags,
+ hook_cb);
+
+ /*
+ * Try loading the config file. This should result in the hook
+ * being called
+ */
+ cfg = ast_config_load(CONFIG_FILE, config_flags);
+ ast_config_destroy(cfg);
+ if (!hook_run || !hook_config_sane) {
+ ast_test_status_update(test, "Config hook either did not run or was given bad data!\n");
+ goto out;
+ }
+
+ /*
+ * Now try loading the wrong config file but from the right module.
+ * Hook should not run
+ */
+ hook_run = 0;
+ cfg = ast_config_load("asterisk.conf", config_flags);
+ ast_config_destroy(cfg);
+ if (hook_run) {
+ ast_test_status_update(test, "Config hook ran even though an incorrect file was specified.\n");
+ goto out;
+ }
+
+ /*
+ * Now try loading the correct config file but from the wrong module.
+ * Hook should not run
+ */
+ hook_run = 0;
+ cfg = ast_config_load2(CONFIG_FILE, "fake_module.so", config_flags);
+ ast_config_destroy(cfg);
+ if (hook_run) {
+ ast_test_status_update(test, "Config hook ran even though an incorrect module was specified.\n");
+ goto out;
+ }
+
+ /*
+ * Now try loading the file correctly, but without any changes to the file.
+ * Hook should not run
+ */
+ hook_run = 0;
+ cfg = ast_config_load(CONFIG_FILE, config_flags);
+ /* Only destroy this cfg conditionally. Otherwise a crash happens. */
+ if (cfg != CONFIG_STATUS_FILEUNCHANGED) {
+ ast_config_destroy(cfg);
+ }
+ if (hook_run) {
+ ast_test_status_update(test, "Config hook ran even though file contents had not changed\n");
+ goto out;
+ }
+
+ res = AST_TEST_PASS;
+
+out:
+ delete_config_file();
+ return res;
+}
+
enum {
EXPECT_FAIL = 0,
EXPECT_SUCCEED,
@@ -278,7 +615,6 @@ AST_TEST_DEFINE(ast_parse_arg_test)
return ret;
}
-
struct test_item {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(name);
@@ -575,6 +911,8 @@ AST_TEST_DEFINE(config_options_test)
static int unload_module(void)
{
+ AST_TEST_UNREGISTER(copy_config);
+ AST_TEST_UNREGISTER(config_hook);
AST_TEST_UNREGISTER(ast_parse_arg_test);
AST_TEST_UNREGISTER(config_options_test);
return 0;
@@ -582,9 +920,12 @@ static int unload_module(void)
static int load_module(void)
{
+ AST_TEST_REGISTER(copy_config);
+ AST_TEST_REGISTER(config_hook);
AST_TEST_REGISTER(ast_parse_arg_test);
AST_TEST_REGISTER(config_options_test);
return AST_MODULE_LOAD_SUCCESS;
}
-AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "config test module");
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Config test module");
+
diff --git a/tests/test_voicemail_api.c b/tests/test_voicemail_api.c
new file mode 100644
index 0000000000..cc83625bf6
--- /dev/null
+++ b/tests/test_voicemail_api.c
@@ -0,0 +1,1443 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2012, Matt Jordan
+ *
+ * Matt Jordan
+ *
+ * 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 Skeleton Test
+ *
+ * \author\verbatim Matt Jordan \endverbatim
+ *
+ * Tests for the publicly exposed Voicemail API
+ * \ingroup tests
+ */
+
+/*** MODULEINFO
+ TEST_FRAMEWORK
+ core
+ ***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include
+
+#include "asterisk/utils.h"
+#include "asterisk/module.h"
+#include "asterisk/test.h"
+#include "asterisk/paths.h"
+#include "asterisk/channel.h"
+#include "asterisk/app.h"
+#include "asterisk/app_voicemail.h"
+
+/*! \internal \brief Permissions to set on the voicemail directories we create
+ * - taken from app_voicemail */
+#define VOICEMAIL_DIR_MODE 0777
+
+/*! \internal \brief Permissions to set on the voicemail files we create
+ * - taken from app_voicemail */
+#define VOICEMAIL_FILE_MODE 0666
+
+/*! \internal \brief The number of mock snapshot objects we use for tests */
+#define TOTAL_SNAPSHOTS 4
+
+/*! \internal \brief Create and populate the mock message objects and create the
+ * envelope files on the file system */
+#define VM_API_TEST_SETUP do { \
+ if (test_vm_api_test_setup()) { \
+ VM_API_TEST_CLEANUP; \
+ ast_test_status_update(test, "Failed to set up necessary mock objects for voicemail API test\n"); \
+ return AST_TEST_FAIL; \
+ } else { \
+ int i = 0; \
+ for (; i < TOTAL_SNAPSHOTS; i++) { \
+ ast_test_status_update(test, "Created message in %s/%s with ID %s\n", \
+ test_snapshots[i]->exten, test_snapshots[i]->folder_name, test_snapshots[i]->msg_id); \
+ } \
+} } while (0)
+
+/*! \internal \brief Safely cleanup after a test run. This should be called both when a
+ * test fails and when it passes */
+#define VM_API_TEST_CLEANUP test_vm_api_test_teardown()
+
+/*! \internal \brief Safely cleanup a snapshot and a test run. Note that it assumes
+ * that the mailbox snapshot object is test_mbox_snapshot */
+#define VM_API_SNAPSHOT_TEST_CLEANUP \
+ if (test_mbox_snapshot) { \
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \
+ } \
+ VM_API_TEST_CLEANUP; \
+
+/*! \internal \brief Verify the expected result from two string values obtained
+ * from a mailbox snapshot. Note that it assumes the mailbox snapshot
+ * object is test_mbox_snapshot
+ */
+#define VM_API_STRING_FIELD_VERIFY(expected, actual) do { \
+ if (strncmp((expected), (actual), sizeof((expected)))) { \
+ ast_test_status_update(test, "Test failed for parameter %s: Expected [%s], Actual [%s]\n", #actual, expected, actual); \
+ VM_API_SNAPSHOT_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Verify the expected result from two integer values. Note
+ * that it assumes the mailbox snapshot object is test_mbox_snapshot */
+#define VM_API_INT_VERIFY(expected, actual) do { \
+ if ((expected) != (actual)) { \
+ ast_test_status_update(test, "Test failed for parameter %s: Expected [%d], Actual [%d]\n", #actual, expected, actual); \
+ VM_API_SNAPSHOT_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Verify that a mailbox snapshot contains the expected message
+ * snapshot, in the correct position, with the expected values. Note
+ * that it assumes the mailbox snapshot object is test_mbox_snapshot
+ */
+#define VM_API_SNAPSHOT_MSG_VERIFY(expected, actual, expected_folder, expected_index) do { \
+ struct ast_vm_msg_snapshot *msg; \
+ int found = 0; \
+ int counter = 0; \
+ AST_LIST_TRAVERSE(&((actual)->snapshots[get_folder_by_name(expected_folder)]), msg, msg) { \
+ if (!(strcmp(msg->msg_id, (expected)->msg_id))) { \
+ ast_test_status_update(test, "Found message %s in snapshot\n", msg->msg_id); \
+ found = 1; \
+ if ((expected_index) != counter) { \
+ ast_test_status_update(test, "Expected message %s at index %d; Actual [%d]\n", \
+ (expected)->msg_id, (expected_index), counter); \
+ VM_API_SNAPSHOT_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } \
+ VM_API_STRING_FIELD_VERIFY((expected)->callerid, msg->callerid); \
+ VM_API_STRING_FIELD_VERIFY((expected)->callerchan, msg->callerchan); \
+ VM_API_STRING_FIELD_VERIFY((expected)->exten, msg->exten); \
+ VM_API_STRING_FIELD_VERIFY((expected)->origdate, msg->origdate); \
+ VM_API_STRING_FIELD_VERIFY((expected)->origtime, msg->origtime); \
+ VM_API_STRING_FIELD_VERIFY((expected)->duration, msg->duration); \
+ VM_API_STRING_FIELD_VERIFY((expected)->folder_name, msg->folder_name); \
+ VM_API_STRING_FIELD_VERIFY((expected)->flag, msg->flag); \
+ VM_API_INT_VERIFY((expected)->msg_number, msg->msg_number); \
+ break; \
+ } \
+ ++counter; \
+ } \
+ if (!found) { \
+ ast_test_status_update(test, "Test failed for message snapshot %s: not found in mailbox snapshot\n", (expected)->msg_id); \
+ VM_API_SNAPSHOT_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+} } while (0)
+
+
+/*! \internal \brief Create a message snapshot, failing the test if the snapshot could not be created.
+ * This requires having a snapshot named test_mbox_snapshot.
+ */
+#define VM_API_SNAPSHOT_CREATE(mailbox, context, folder, desc, sort, old_and_inbox) do { \
+ if (!(test_mbox_snapshot = ast_vm_mailbox_snapshot_create( \
+ (mailbox), (context), (folder), (desc), (sort), (old_and_inbox)))) { \
+ ast_test_status_update(test, "Failed to create voicemail mailbox snapshot\n"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Create a message snapshot, failing the test if the snapshot could be created.
+ * This is used to test off nominal conditions.
+ * This requires having a snapshot named test_mbox_snapshot.
+ */
+#define VM_API_SNAPSHOT_OFF_NOMINAL_TEST(mailbox, context, folder, desc, sort, old_and_inbox) do { \
+ if ((test_mbox_snapshot = ast_vm_mailbox_snapshot_create( \
+ (mailbox), (context), (folder), (desc), (sort), (old_and_inbox)))) { \
+ ast_test_status_update(test, "Created mailbox snapshot when none was expected\n"); \
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Move a voicemail message, failing the test if the message could not be moved */
+#define VM_API_MOVE_MESSAGE(mailbox, context, number_of_messages, source, message_numbers_in, dest) do { \
+ if (ast_vm_msg_move((mailbox), (context), (number_of_messages), (source), (message_numbers_in), (dest))) { \
+ ast_test_status_update(test, "Failed to move message %s@%s from %s to %s\n", \
+ (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (source) ? (source) : "(NULL)", (dest) ? (dest) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+ /*! \internal \brief Attempt to move a voicemail message, failing the test if the message could be moved */
+#define VM_API_MOVE_MESSAGE_OFF_NOMINAL(mailbox, context, number_of_messages, source, message_numbers_in, dest) do { \
+ if (!ast_vm_msg_move((mailbox), (context), (number_of_messages), (source), (message_numbers_in), (dest))) { \
+ ast_test_status_update(test, "Succeeded to move message %s@%s from %s to %s when we really shouldn't\n", \
+ (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (source) ? (source) : "(NULL)", (dest) ? (dest) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Remove a message, failing the test if the method failed or if the message is still present. */
+#define VM_API_REMOVE_MESSAGE(mailbox, context, number_of_messages, folder, message_numbers_in) do { \
+ if (ast_vm_msg_remove((mailbox), (context), (number_of_messages), (folder), (message_numbers_in))) { \
+ ast_test_status_update(test, "Failed to remove message from mailbox %s@%s, folder %s", \
+ (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (folder) ? (folder) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } \
+ VM_API_SNAPSHOT_CREATE((mailbox), (context), (folder), 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0); \
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 0); \
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot); \
+} while (0)
+
+/*! \internal \brief Remove a message, failing the test if the method succeeds */
+#define VM_API_REMOVE_MESSAGE_OFF_NOMINAL(mailbox, context, number_of_messages, folder, message_numbers_in) do { \
+ if (!ast_vm_msg_remove((mailbox), (context), (number_of_messages), (folder), (message_numbers_in))) { \
+ ast_test_status_update(test, "Succeeded in removing message from mailbox %s@%s, folder %s, when expected result was failure\n", \
+ (mailbox) ? (mailbox): "(NULL)", (context) ? (context) : "(NULL)", (folder) ? (folder) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Forward a message, failing the test if the message could not be forwarded */
+# define VM_API_FORWARD_MESSAGE(from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder, number_of_messages, message_numbers_in, delete_old) do { \
+ if (ast_vm_msg_forward((from_mailbox), (from_context), (from_folder), (to_mailbox), (to_context), (to_folder), (number_of_messages), (message_numbers_in), (delete_old))) { \
+ ast_test_status_update(test, "Failed to forward message from %s@%s [%s] to %s@%s [%s]\n", \
+ (from_mailbox) ? (from_mailbox) : "(NULL)", (from_context) ? (from_context) : "(NULL)", (from_folder) ? (from_folder) : "(NULL)", \
+ (to_mailbox) ? (to_mailbox) : "(NULL)", (to_context) ? (to_context) : "(NULL)", (to_folder) ? (to_folder) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+ /*! \internal \brief Forward a message, failing the test if the message was successfully forwarded */
+#define VM_API_FORWARD_MESSAGE_OFF_NOMINAL(from_mailbox, from_context, from_folder, to_mailbox, to_context, to_folder, number_of_messages, message_numbers_in, delete_old) do { \
+ if (!ast_vm_msg_forward((from_mailbox), (from_context), (from_folder), (to_mailbox), (to_context), (to_folder), (number_of_messages), (message_numbers_in), (delete_old))) { \
+ ast_test_status_update(test, "Succeeded in forwarding message from %s@%s [%s] to %s@%s [%s] when expected result was fail\n", \
+ (from_mailbox) ? (from_mailbox) : "(NULL)", (from_context) ? (from_context) : "(NULL)", (from_folder) ? (from_folder) : "(NULL)", \
+ (to_mailbox) ? (to_mailbox) : "(NULL)", (to_context) ? (to_context) : "(NULL)", (to_folder) ? (to_folder) : "(NULL)"); \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Playback a message on a channel or callback function. Note that the channel name must be test_channel.
+ * Fail the test if the message could not be played. */
+#define VM_API_PLAYBACK_MESSAGE(channel, mailbox, context, folder, message, callback_fn) do { \
+ if (ast_vm_msg_play((channel), (mailbox), (context), (folder), (message), (callback_fn))) { \
+ ast_test_status_update(test, "Failed nominal playback message test\n"); \
+ if (test_channel) { \
+ ast_hangup(test_channel); \
+ } \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+/*! \internal \brief Playback a message on a channel or callback function. Note that the channel name must be test_channel.
+ * Fail the test if the message is successfully played */
+#define VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(channel, mailbox, context, folder, message, callback_fn) do { \
+ if (!ast_vm_msg_play((channel), (mailbox), (context), (folder), (message), (callback_fn))) { \
+ ast_test_status_update(test, "Succeeded in playing back of message when expected result was to fail\n"); \
+ if (test_channel) { \
+ ast_hangup(test_channel); \
+ } \
+ VM_API_TEST_CLEANUP; \
+ return AST_TEST_FAIL; \
+ } } while (0)
+
+
+/*! \internal \brief Possible names of folders. Taken from app_voicemail */
+static const char * const mailbox_folders[] = {
+ "INBOX",
+ "Old",
+ "Work",
+ "Family",
+ "Friends",
+ "Cust1",
+ "Cust2",
+ "Cust3",
+ "Cust4",
+ "Cust5",
+ "Deleted",
+ "Urgent",
+};
+
+/*! \internal \brief Message snapshots representing the messages that are used by the various tests */
+static struct ast_vm_msg_snapshot *test_snapshots[TOTAL_SNAPSHOTS];
+
+/*! \internal \brief Tracks whether or not we entered into the message playback callback function */
+static int global_entered_playback_callback = 0;
+
+/*! \internal \brief Get a folder index by its name */
+static int get_folder_by_name(const char *folder)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
+ if (strcasecmp(folder, mailbox_folders[i]) == 0) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+/*! \internal \brief Get a mock snapshot object
+ * \param context The mailbox context
+ * \param exten The mailbox extension
+ * \param callerid The caller ID of the person leaving the message
+ * \returns an ast_vm_msg_snapshot object on success
+ * \returns NULL on error
+ */
+static struct ast_vm_msg_snapshot *test_vm_api_create_mock_snapshot(const char *context, const char *exten, const char *callerid)
+{
+ char msg_id_hash[AST_MAX_CONTEXT + AST_MAX_EXTENSION + sizeof(callerid) + 1];
+ char msg_id_buf[256];
+ struct ast_vm_msg_snapshot *snapshot;
+
+ snprintf(msg_id_hash, sizeof(msg_id_hash), "%s%s%s", exten, context, callerid);
+ snprintf(msg_id_buf, sizeof(msg_id_buf), "%ld-%d", (long)time(NULL), ast_str_hash(msg_id_hash));
+
+ if ((snapshot = ast_calloc(1, sizeof(*snapshot)))) {
+ ast_string_field_init(snapshot, 128);
+ ast_string_field_set(snapshot, msg_id, msg_id_buf);
+ ast_string_field_set(snapshot, exten, exten);
+ ast_string_field_set(snapshot, callerid, callerid);
+ }
+ return snapshot;
+}
+
+/*! \internal \brief Make a voicemail mailbox folder based on the values provided in a message snapshot
+ * \param snapshot The snapshot containing the information to create the folder from
+ * \returns 0 on success
+ * \returns 1 on failure
+ */
+static int test_vm_api_create_voicemail_folder(const char *folder_path)
+{
+ mode_t mode = VOICEMAIL_DIR_MODE;
+ int res;
+
+ if ((res = ast_mkdir(folder_path, mode))) {
+ ast_log(AST_LOG_ERROR, "ast_mkdir '%s' failed: %s\n", folder_path, strerror(res));
+ return 1;
+ }
+ return 0;
+}
+
+/*! \internal \brief Create the voicemail files specified by a snapshot
+ * \param context The context of the mailbox
+ * \param mailbox The actual mailbox
+ * \param snapshot The message snapshot object containing the relevant envelope data
+ * \note This will symbolic link the sound file 'beep.gsm' to act as the 'sound' portion of the voicemail.
+ * Certain actions in app_voicemail will fail if an actual sound file does not exist
+ * \returns 0 on success
+ * \returns 1 on any failure
+ */
+static int test_vm_api_create_voicemail_files(const char *context, const char *mailbox, struct ast_vm_msg_snapshot *snapshot)
+{
+ FILE *msg_file;
+ char folder_path[PATH_MAX];
+ char msg_path[PATH_MAX];
+ char snd_path[PATH_MAX];
+ char beep_path[PATH_MAX];
+
+ /* Note that we create both the text and a dummy sound file here. Without
+ * the sound file, a number of the voicemail operations 'silently' fail, as it
+ * does not believe that an actual voicemail exists
+ */
+ snprintf(folder_path, sizeof(folder_path), "%s/voicemail/%s/%s/%s",
+ ast_config_AST_SPOOL_DIR, context, mailbox, snapshot->folder_name);
+ snprintf(msg_path, sizeof(msg_path), "%s/msg%04d.txt",
+ folder_path, snapshot->msg_number);
+ snprintf(snd_path, sizeof(snd_path), "%s/msg%04d.gsm",
+ folder_path, snapshot->msg_number);
+ snprintf(beep_path, sizeof(beep_path), "%s/sounds/en/beep.gsm", ast_config_AST_VAR_DIR);
+
+ if (test_vm_api_create_voicemail_folder(folder_path)) {
+ return 1;
+ }
+
+ if (ast_lock_path(folder_path) == AST_LOCK_FAILURE) {
+ ast_log(AST_LOG_ERROR, "Unable to lock directory %s\n", folder_path);
+ return 1;
+ }
+
+ if (symlink(beep_path, snd_path)) {
+ ast_unlock_path(folder_path);
+ ast_log(AST_LOG_ERROR, "Failed to create a symbolic link from %s to %s: %s\n",
+ beep_path, snd_path, strerror(errno));
+ return 1;
+ }
+
+ if (!(msg_file = fopen(msg_path, "w"))) {
+ /* Attempt to remove the sound file */
+ unlink(snd_path);
+ ast_unlock_path(folder_path);
+ ast_log(AST_LOG_ERROR, "Failed to open %s for writing\n", msg_path);
+ return 1;
+ }
+
+ fprintf(msg_file, ";\n; Message Information file\n;\n"
+ "[message]\n"
+ "origmailbox=%s\n"
+ "context=%s\n"
+ "macrocontext=%s\n"
+ "exten=%s\n"
+ "rdnis=%s\n"
+ "priority=%d\n"
+ "callerchan=%s\n"
+ "callerid=%s\n"
+ "origdate=%s\n"
+ "origtime=%s\n"
+ "category=%s\n"
+ "msg_id=%s\n"
+ "flag=%s\n"
+ "duration=%s\n",
+ mailbox,
+ context,
+ "",
+ snapshot->exten,
+ "unknown",
+ 1,
+ snapshot->callerchan,
+ snapshot->callerid,
+ snapshot->origdate,
+ snapshot->origtime,
+ "",
+ snapshot->msg_id,
+ snapshot->flag,
+ snapshot->duration);
+ fclose(msg_file);
+
+ if (chmod(msg_path, VOICEMAIL_FILE_MODE) < 0) {
+ ast_unlock_path(folder_path);
+ ast_log(AST_LOG_ERROR, "Couldn't set permissions on voicemail text file %s: %s", msg_path, strerror(errno));
+ return 1;
+ }
+ ast_unlock_path(folder_path);
+
+ return 0;
+}
+
+/*! \internal \brief Destroy the voicemail on the file system associated with a snapshot
+ * \param snapshot The snapshot describing the voicemail
+ */
+static void test_vm_api_remove_voicemail(struct ast_vm_msg_snapshot *snapshot)
+{
+ char msg_path[PATH_MAX];
+ char snd_path[PATH_MAX];
+ char folder_path[PATH_MAX];
+
+ if (!snapshot) {
+ return;
+ }
+
+ snprintf(folder_path, sizeof(folder_path), "%s/voicemail/%s/%s/%s",
+ ast_config_AST_SPOOL_DIR, "default", snapshot->exten, snapshot->folder_name);
+
+ snprintf(msg_path, sizeof(msg_path), "%s/msg%04d.txt",
+ folder_path, snapshot->msg_number);
+ snprintf(snd_path, sizeof(snd_path), "%s/msg%04d.gsm",
+ folder_path, snapshot->msg_number);
+ unlink(msg_path);
+ unlink(snd_path);
+
+ return;
+}
+
+/*! \internal \brief Destroy the voicemails associated with a mailbox snapshot
+ * \param mailbox The actual mailbox name
+ * \param mailbox_snapshot The mailbox snapshot containing the voicemails to destroy
+ * \note Its necessary to specify not just the snapshot, but the mailbox itself. The
+ * message snapshots contained in the snapshot may have originated from a different mailbox
+ * then the one we're destroying, which means that we can't determine the files to delete
+ * without knowing the actual mailbox they exist in.
+ */
+static void test_vm_api_destroy_mailbox_voicemails(const char *mailbox, struct ast_vm_mailbox_snapshot *mailbox_snapshot)
+{
+ struct ast_vm_msg_snapshot *msg;
+ int i;
+
+ for (i = 0; i < 12; ++i) {
+ AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[i], msg, msg) {
+ ast_string_field_set(msg, exten, mailbox);
+ test_vm_api_remove_voicemail(msg);
+ }
+ }
+}
+
+/*! \internal \brief Use snapshots to remove all messages in the mailboxes */
+static void test_vm_api_remove_all_messages(void)
+{
+ struct ast_vm_mailbox_snapshot *mailbox_snapshot;
+
+ /* Take a snapshot of each mailbox and remove the contents. Note that we need to use
+ * snapshots of the mailboxes in addition to our tracked test snapshots, as there's a good chance
+ * we've created copies of the snapshots */
+ if ((mailbox_snapshot = ast_vm_mailbox_snapshot_create("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0))) {
+ test_vm_api_destroy_mailbox_voicemails("test_vm_api_1234", mailbox_snapshot);
+ mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+ } else {
+ ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot - could not remove test messages for test_vm_api_1234\n");
+ }
+ if ((mailbox_snapshot = ast_vm_mailbox_snapshot_create("test_vm_api_2345", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0))) {
+ test_vm_api_destroy_mailbox_voicemails("test_vm_api_2345", mailbox_snapshot);
+ mailbox_snapshot = ast_vm_mailbox_snapshot_destroy(mailbox_snapshot);
+ } else {
+ ast_log(AST_LOG_WARNING, "Failed to create mailbox snapshot - could not remove test messages for test_vm_api_2345\n");
+ }
+}
+
+/*! \internal \brief Set up the necessary voicemails for a unit test run
+ * \note
+ * This creates 4 voicemails, stores them on the file system, and creates snapshot objects
+ * representing them for expected/actual value comparisons in the array test_snapshots.
+ *
+ * test_snapshots[0] => in test_vm_1234@default, folder INBOX, message 0
+ * test_snapshots[1] => in test_vm_1234@default, folder Old, message 0
+ * test_snapshots[2] => in test_vm_2345@default, folder INBOX, message 0
+ * test_snapshots[3] => in test_vm_2345@default, folder Old, message 1
+ *
+ * \returns 0 on success
+ * \returns 1 on failure
+ */
+static int test_vm_api_test_setup(void)
+{
+ int i, res = 0;
+ struct ast_vm_msg_snapshot *msg_one = NULL;
+ struct ast_vm_msg_snapshot *msg_two = NULL;
+ struct ast_vm_msg_snapshot *msg_three = NULL;
+ struct ast_vm_msg_snapshot *msg_four = NULL;
+
+ /* Make the four sample voicemails */
+ if ( !((msg_one = test_vm_api_create_mock_snapshot("default", "test_vm_api_1234", "\"Phil\" <2000>")))
+ || !((msg_two = test_vm_api_create_mock_snapshot("default", "test_vm_api_1234", "\"Noel\" <8000>")))
+ || !((msg_three = test_vm_api_create_mock_snapshot("default", "test_vm_api_2345", "\"Phil\" <2000>")))
+ || !((msg_four = test_vm_api_create_mock_snapshot("default", "test_vm_api_2345", "\"Bill\" <3000>")))) {
+ ast_log(AST_LOG_ERROR, "Failed to create mock snapshots for test\n");
+ ast_free(msg_one);
+ ast_free(msg_two);
+ ast_free(msg_three);
+ ast_free(msg_four);
+ return 1;
+ }
+
+ /* Create the voicemail users */
+ if (ast_vm_test_create_user("default", "test_vm_api_1234")
+ || ast_vm_test_create_user("default", "test_vm_api_2345")) {
+ ast_log(AST_LOG_ERROR, "Failed to create test voicemail users\n");
+ ast_free(msg_one);
+ ast_free(msg_two);
+ ast_free(msg_three);
+ ast_free(msg_four);
+ /* Note that the cleanup macro will ensure that any test user that
+ * was successfully created is removed
+ */
+ return 1;
+ }
+
+ /* Now that the users exist from the perspective of the voicemail
+ * application, attempt to remove any existing voicemails
+ */
+ test_vm_api_remove_all_messages();
+
+ /* Set the basic properties on each */
+ ast_string_field_set(msg_one, callerchan, "SIP/2000-00000000");
+ ast_string_field_set(msg_one, origdate, "Mon Mar 19 04:14:21 PM UTC 2012");
+ ast_string_field_set(msg_one, origtime, "1332173661");
+ ast_string_field_set(msg_one, duration, "8");
+ ast_string_field_set(msg_one, folder_name, "Old");
+ msg_one->msg_number = 0;
+ test_snapshots[0] = msg_one;
+
+ ast_string_field_set(msg_two, callerchan, "SIP/8000-00000001");
+ ast_string_field_set(msg_two, origdate, "Mon Mar 19 06:16:13 PM UTC 2012");
+ ast_string_field_set(msg_two, origtime, "1332180973");
+ ast_string_field_set(msg_two, duration, "24");
+ ast_string_field_set(msg_two, folder_name, "INBOX");
+ msg_two->msg_number = 0;
+ test_snapshots[1] = msg_two;
+
+ ast_string_field_set(msg_three, callerchan, "IAX/2000-000000a3");
+ ast_string_field_set(msg_three, origdate, "Thu Mar 22 23:13:03 PM UTC 2012");
+ ast_string_field_set(msg_three, origtime, "1332181251");
+ ast_string_field_set(msg_three, duration, "25");
+ ast_string_field_set(msg_three, folder_name, "INBOX");
+ msg_three->msg_number = 0;
+ test_snapshots[2] = msg_three;
+
+ ast_string_field_set(msg_four, callerchan, "DAHDI/3000-00000010");
+ ast_string_field_set(msg_four, origdate, "Fri Mar 23 03:01:03 AM UTC 2012");
+ ast_string_field_set(msg_four, origtime, "1332181362");
+ ast_string_field_set(msg_four, duration, "13");
+ ast_string_field_set(msg_four, folder_name, "INBOX");
+ msg_three->msg_number = 1;
+ test_snapshots[3] = msg_four;
+
+ /* Store the messages */
+ for (i = 0; i < TOTAL_SNAPSHOTS; ++i) {
+ if (test_vm_api_create_voicemail_files("default", test_snapshots[i]->exten, test_snapshots[i])) {
+ /* On a failure, the test_vm_api_test_teardown method will remove and
+ * unlink any created files. Since we failed to create the file, clean
+ * up the object here instead */
+ ast_log(AST_LOG_ERROR, "Failed to store voicemail %s/%s\n",
+ "default", test_snapshots[i]->exten);
+ ast_free(test_snapshots[i]);
+ test_snapshots[i] = NULL;
+ res = 1;
+ }
+ }
+
+ return res;
+}
+
+static void test_vm_api_test_teardown(void)
+{
+ int i;
+
+ /* Remove our test message snapshots */
+ for (i = 0; i < TOTAL_SNAPSHOTS; ++i) {
+ test_vm_api_remove_voicemail(test_snapshots[i]);
+ ast_free(test_snapshots[i]);
+ test_snapshots[i] = NULL;
+ }
+
+ test_vm_api_remove_all_messages();
+
+ /* Remove the test users */
+ ast_vm_test_destroy_user("default", "test_vm_api_1234");
+ ast_vm_test_destroy_user("default", "test_vm_api_2345");
+}
+
+/*! \internal \brief Update the test snapshots with a new mailbox snapshot
+ * \param mailbox_snapshot The new mailbox shapshot to update the test snapshots with
+ */
+static void test_vm_api_update_test_snapshots(struct ast_vm_mailbox_snapshot *mailbox_snapshot)
+{
+ int i, j;
+ struct ast_vm_msg_snapshot *msg;
+
+ for (i = 0; i < TOTAL_SNAPSHOTS; ++i) {
+ for (j = 0; j < 12; ++j) {
+ AST_LIST_TRAVERSE(&mailbox_snapshot->snapshots[j], msg, msg) {
+ if (!strcmp(msg->msg_id, test_snapshots[i]->msg_id)) {
+ ast_string_field_set(test_snapshots[i], callerid, msg->callerid);
+ ast_string_field_set(test_snapshots[i], callerchan, msg->callerchan);
+ ast_string_field_set(test_snapshots[i], exten, msg->exten);
+ ast_string_field_set(test_snapshots[i], origdate, msg->origdate);
+ ast_string_field_set(test_snapshots[i], origtime, msg->origtime);
+ ast_string_field_set(test_snapshots[i], duration, msg->duration);
+ ast_string_field_set(test_snapshots[i], folder_name, msg->folder_name);
+ ast_string_field_set(test_snapshots[i], flag, msg->flag);
+ test_snapshots[i]->msg_number = msg->msg_number;
+ }
+ }
+ }
+ }
+}
+
+/*! \internal \brief A callback function for message playback
+ * \param chan The channel the file would be played back on
+ * \param file The file to play back
+ * \param duration The duration of the file
+ * \note This sets global_entered_playback_callback to 1 if the parameters
+ * passed to the callback are minimally valid
+ */
+static void message_playback_callback_fn(struct ast_channel *chan, const char *file, int duration)
+{
+ if ((chan) && !ast_strlen_zero(file) && duration > 0) {
+ global_entered_playback_callback = 1;
+ } else {
+ ast_log(AST_LOG_WARNING, "Entered into message playback callback function with invalid parameters\n");
+ }
+}
+
+/*! \internal \brief Dummy channel write function for mock_channel_tech */
+static int test_vm_api_mock_channel_write(struct ast_channel *chan, struct ast_frame *frame)
+{
+ return 0;
+}
+
+/*! \internal \brief Dummy channel read function for mock_channel_tech */
+static struct ast_frame *test_vm_api_mock_channel_read(struct ast_channel *chan)
+{
+ return &ast_null_frame;
+}
+
+/*! \internal \brief A dummy channel technology */
+static const struct ast_channel_tech mock_channel_tech = {
+ .write = test_vm_api_mock_channel_write,
+ .read = test_vm_api_mock_channel_read,
+};
+
+/*! \internal \brief Create a dummy channel suitable for 'playing back' gsm sound files on
+ * \returns a channel on success
+ * \returns NULL on failure
+ */
+static struct ast_channel *test_vm_api_create_mock_channel(void)
+{
+ struct ast_channel *mock_channel;
+ struct ast_format_cap *native_formats;
+
+ if (!(mock_channel = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, 0, 0, "TestChannel"))) {
+ return NULL;
+ }
+
+ ast_format_set(ast_channel_writeformat(mock_channel), AST_FORMAT_GSM, 0);
+ native_formats = ast_channel_nativeformats(mock_channel);
+ ast_format_cap_add(native_formats, ast_channel_writeformat(mock_channel));
+ ast_format_set(ast_channel_rawwriteformat(mock_channel), AST_FORMAT_GSM, 0);
+ ast_format_set(ast_channel_readformat(mock_channel), AST_FORMAT_GSM, 0);
+ ast_format_set(ast_channel_rawreadformat(mock_channel), AST_FORMAT_GSM, 0);
+ ast_channel_tech_set(mock_channel, &mock_channel_tech);
+
+ return mock_channel;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_snapshot)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_snapshot";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal mailbox snapshot tests";
+ info->description =
+ "Test retrieving mailbox snapshots";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ ast_test_status_update(test, "Test retrieving message 1 from INBOX of test_vm_1234\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(1, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0 from Old of test_vm_1234\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(1, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from Old and INBOX of test_vm_1234 ordered by time\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 1);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 1);
+
+ ast_test_status_update(test, "Test retrieving message 1, 0 from Old and INBOX of test_vm_1234 ordered by time desc\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 1, AST_VM_SNAPSHOT_SORT_BY_TIME, 1);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 1);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from Old and INBOX of test_vm_1234 ordered by id\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_ID, 1);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 1);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 1, 0 from Old and INBOX of test_vm_1234 ordered by id desc\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 1, AST_VM_SNAPSHOT_SORT_BY_ID, 1);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "INBOX", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 1);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234 ordered by id\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_ID, 0);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234 ordered by time\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", NULL, 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test retrieving message 0, 1 from all folders of test_vm_1234, default context ordered by time\n");
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", NULL, NULL, 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(2, test_mbox_snapshot->total_msg_num);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[0], test_mbox_snapshot, "Old", 0);
+ VM_API_SNAPSHOT_MSG_VERIFY(test_snapshots[1], test_mbox_snapshot, "INBOX", 0);
+ ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_snapshot)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_snapshot";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal mailbox snapshot tests";
+ info->description =
+ "Test off nominal requests for mailbox snapshots. This includes"
+ " testing the following:\n"
+ " * Access to non-exisstent mailbox\n"
+ " * Access to NULL mailbox\n"
+ " * Access to non-existent context\n"
+ " * Access to non-existent folder\n"
+ " * Access to NULL folder\n"
+ " * Invalid sort identifier\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ ast_test_status_update(test, "Test access to non-existent mailbox test_vm_api_3456\n");
+ VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_3456", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+
+ ast_test_status_update(test, "Test access to null mailbox\n");
+ VM_API_SNAPSHOT_OFF_NOMINAL_TEST(NULL, "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+
+ ast_test_status_update(test, "Test access non-existent context test_vm_api_defunct\n");
+ VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_1234", "test_vm_api_defunct", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+
+ ast_test_status_update(test, "Test non-existent folder test_vm_api_platypus\n");
+ VM_API_SNAPSHOT_OFF_NOMINAL_TEST("test_vm_api_1234", "default", "test_vm_api_platypus", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_move)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+ const char *inbox_msg_id;
+ const char *old_msg_id;
+ const char *multi_msg_ids[2];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_move";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal move voicemail tests";
+ info->description =
+ "Test nominal requests to move a voicemail to a different"
+ " folder. This includes moving messages given a context,"
+ " given a NULL context, and moving multiple messages";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+ old_msg_id = test_snapshots[0]->msg_id;
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[2]->msg_id;
+ multi_msg_ids[1] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test move of test_vm_api_1234 message from INBOX to Family\n");
+ VM_API_MOVE_MESSAGE("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move of test_vm_api_1234 message from Old to Family\n");
+ VM_API_MOVE_MESSAGE("test_vm_api_1234", NULL, 1, "Old", &old_msg_id, "Family");
+
+ /* Take a snapshot and update the test snapshots for verification */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ test_vm_api_update_test_snapshots(test_mbox_snapshot);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[0]->folder_name, "Family");
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[1]->folder_name, "Family");
+ VM_API_INT_VERIFY(test_snapshots[1]->msg_number, 0);
+ VM_API_INT_VERIFY(test_snapshots[0]->msg_number, 1);
+
+ /* Move both of the 2345 messages to Family */
+ ast_test_status_update(test, "Test move of test_vm_api_2345 messages from Inbox to Family\n");
+ VM_API_MOVE_MESSAGE("test_vm_api_2345", "default", 2, "INBOX", multi_msg_ids, "Family");
+
+ /* Take a snapshot and update the test snapshots for verification */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ test_vm_api_update_test_snapshots(test_mbox_snapshot);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[2]->folder_name, "Family");
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[3]->folder_name, "Family");
+
+ ast_test_status_update(test, "Test move of test_vm_api_2345 message from Family to INBOX\n");
+ VM_API_MOVE_MESSAGE("test_vm_api_2345", "default", 2, "Family", multi_msg_ids, "INBOX");
+
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ test_vm_api_update_test_snapshots(test_mbox_snapshot);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[2]->folder_name, "INBOX");
+ VM_API_STRING_FIELD_VERIFY(test_snapshots[3]->folder_name, "INBOX");
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_move)
+{
+ const char *inbox_msg_id;
+ const char *multi_msg_ids[4];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_move";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal mailbox message move tests";
+ info->description =
+ "Test nominal requests to move a voicemail to a different"
+ " folder. This includes testing the following:\n"
+ " * Moving to a non-existent mailbox\n"
+ " * Moving to a NULL mailbox\n"
+ " * Moving to a non-existent context\n"
+ " * Moving to/from non-existent folder\n"
+ " * Moving to/from NULL folder\n"
+ " * Invalid message identifier(s)\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[0]->msg_id;
+ multi_msg_ids[1] = test_snapshots[1]->msg_id;
+ multi_msg_ids[2] = test_snapshots[2]->msg_id;
+ multi_msg_ids[3] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test move attempt for invalid mailbox test_vm_3456\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL(NULL, "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt for invalid context test_vm_api_defunct\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "test_vm_api_defunct", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt to invalid folder\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, "SPAMALOT");
+
+ ast_test_status_update(test, "Test move attempt from invalid folder\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "MEATINACAN", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt to NULL folder\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, NULL);
+
+ ast_test_status_update(test, "Test move attempt from NULL folder\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, NULL, &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with non-existent message number\n");
+ inbox_msg_id = "6";
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with invalid message number\n");
+ inbox_msg_id = "";
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with 0 number of messages\n");
+ inbox_msg_id = test_snapshots[1]->msg_id;
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 0, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with invalid number of messages\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", -30, "INBOX", &inbox_msg_id, "Family");
+
+ ast_test_status_update(test, "Test move attempt with non-existent multiple messages, where some messages exist\n");
+ VM_API_MOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 4, "INBOX", multi_msg_ids, "Family");
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_remove)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+ const char *inbox_msg_id;
+ const char *old_msg_id;
+ const char *multi_msg_ids[2];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_remove";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal mailbox remove message tests";
+ info->description =
+ "Tests removing messages from voicemail folders. Includes"
+ " both removing messages one at a time, and in a set";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ old_msg_id = test_snapshots[0]->msg_id;
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[2]->msg_id;
+ multi_msg_ids[1] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test removing a single message from INBOX\n");
+ VM_API_REMOVE_MESSAGE("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message from Old\n");
+ VM_API_REMOVE_MESSAGE("test_vm_api_1234", "default", 1, "Old", &old_msg_id);
+
+ ast_test_status_update(test, "Test removing multiple messages from INBOX\n");
+ VM_API_REMOVE_MESSAGE("test_vm_api_2345", "default", 2, "INBOX", multi_msg_ids);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_remove)
+{
+ const char *inbox_msg_id;
+ const char *multi_msg_ids[2];
+ const char *empty_msg_ids[] = { };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_remove";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal mailbox message removal tests";
+ info->description =
+ "Test off nominal requests for removing messages from "
+ "a mailbox. This includes:\n"
+ " * Removing messages with an invalid mailbox\n"
+ " * Removing messages from a NULL mailbox\n"
+ " * Removing messages from an invalid context\n"
+ " * Removing messages from an invalid folder\n"
+ " * Removing messages from a NULL folder\n"
+ " * Removing messages with bad identifiers\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ inbox_msg_id = test_snapshots[1]->msg_id;
+ multi_msg_ids[0] = test_snapshots[2]->msg_id;
+ multi_msg_ids[1] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test removing a single message with an invalid mailbox\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with a NULL mailbox\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL(NULL, "default", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with an invalid context\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "defunct", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with an invalid folder\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "SPAMINACAN", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with a NULL folder\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, NULL, &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing a single message with an invalid message number\n");
+ inbox_msg_id = "POOPOO";
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 1, "INBOX", &inbox_msg_id);
+
+ ast_test_status_update(test, "Test removing multiple messages with a single invalid message number\n");
+ multi_msg_ids[1] = "POOPOO";
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", 2, "INBOX", multi_msg_ids);
+
+ ast_test_status_update(test, "Test removing no messages with no message numbers\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", 0, "INBOX", empty_msg_ids);
+
+ ast_test_status_update(test, "Test removing multiple messages with an invalid size specifier\n");
+ VM_API_REMOVE_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", -30, "INBOX", multi_msg_ids);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_forward)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+ const char *inbox_msg_id;
+ const char *multi_msg_ids[2];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_forward";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal message forward tests";
+ info->description =
+ "Tests the nominal cases of forwarding messages"
+ " between mailboxes";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[2]->msg_id;
+ multi_msg_ids[1] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ /* Make sure we didn't delete the message */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 3 messages in test_vm_api_2345 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 3);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX with default context to test_vm_api_2345 INBOX\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_1234", NULL, "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ /* Make sure we didn't delete the message */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 4 messages in test_vm_api_2345 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 4);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX with default context\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", NULL, "INBOX", 1, &inbox_msg_id, 0);
+
+ /* Make sure we didn't delete the message */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 1);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 5 messages in test_vm_api_2345 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 5);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding message 0 from test_vm_api_1234 INBOX to test_vm_api_2345 INBOX, deleting original\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", NULL, "INBOX", 1, &inbox_msg_id, 1);
+
+ /* Make sure we deleted the message */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 0);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 6 messages in test_vm_api_2345 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 6);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding 2 messages from test_vm_api_2345 INBOX to test_vm_api_1234 INBOX");
+ VM_API_FORWARD_MESSAGE("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "INBOX", 2, multi_msg_ids, 0);
+
+ /* Make sure we didn't delete the messages */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 6);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 2 messages in test_vm_api_1234 INBOX */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ ast_test_status_update(test, "Test forwarding 2 messages from test_vm_api_2345 INBOX to test_vm_api_1234 Family, deleting original\n");
+ VM_API_FORWARD_MESSAGE("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "Family", 2, multi_msg_ids, 1);
+ /* Make sure we deleted the messages */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "INBOX", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 4);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ /* We should now have a total of 2 messages in test_vm_api_1234 Family */
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Family", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_forward)
+{
+ const char *inbox_msg_id;
+ const char *multi_msg_ids[4];
+
+ const char *empty_msg_ids[] = { };
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_forward";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal message forwarding tests";
+ info->description =
+ "Test off nominal forwarding of messages. This includes:\n"
+ " * Invalid/NULL from mailbox\n"
+ " * Invalid from context\n"
+ " * Invalid/NULL from folder\n"
+ " * Invalid/NULL to mailbox\n"
+ " * Invalid to context\n"
+ " * Invalid/NULL to folder\n"
+ " * Invalid message numbers\n"
+ " * Invalid number of messages\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ inbox_msg_id = test_snapshots[1]->msg_id;
+
+ multi_msg_ids[0] = test_snapshots[0]->msg_id;
+ multi_msg_ids[1] = test_snapshots[1]->msg_id;
+ multi_msg_ids[2] = test_snapshots[2]->msg_id;
+ multi_msg_ids[3] = test_snapshots[3]->msg_id;
+
+ ast_test_status_update(test, "Test forwarding from an invalid mailbox\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_3456", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding from a NULL mailbox\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL(NULL, "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding from an invalid context\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "defunct", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding from an invalid folder\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "POTTEDMEAT", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding from a NULL folder\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", NULL, "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to an invalid mailbox\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_3456", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to a NULL mailbox\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", NULL, "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to an invalid context\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "defunct", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to an invalid folder\n");
+
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "POTTEDMEAT", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding to a NULL folder\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", NULL, 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding when no messages are select\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 0, empty_msg_ids, 0);
+
+ ast_test_status_update(test, "Test forwarding a message that doesn't exist\n");
+ inbox_msg_id = "POOPOO";
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", 1, &inbox_msg_id, 0);
+
+ ast_test_status_update(test, "Test forwarding multiple messages, where some messages don't exist\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_2345", "default", "INBOX", "test_vm_api_1234", "default", "INBOX", 4, multi_msg_ids, 0);
+
+ ast_test_status_update(test, "Test forwarding a message with an invalid size specifier\n");
+ VM_API_FORWARD_MESSAGE_OFF_NOMINAL("test_vm_api_1234", "default", "INBOX", "test_vm_api_2345", "default", "INBOX", -30, &inbox_msg_id, 0);
+
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_nominal_msg_playback)
+{
+ struct ast_vm_mailbox_snapshot *test_mbox_snapshot = NULL;
+ struct ast_channel *test_channel;
+ const char *message_id_1234;
+ const char *message_id_2345[2];
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "nominal_msg_playback";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Nominal message playback";
+ info->description =
+ "Tests playing back a message on a provided"
+ " channel or callback function\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+
+ message_id_1234 = test_snapshots[1]->msg_id;
+ message_id_2345[0] = test_snapshots[2]->msg_id;
+ message_id_2345[1] = test_snapshots[3]->msg_id;
+
+ if (!(test_channel = test_vm_api_create_mock_channel())) {
+ ast_log(AST_LOG_ERROR, "Failed to create mock channel for testing\n");
+ VM_API_TEST_CLEANUP;
+ return AST_TEST_FAIL;
+ }
+
+ ast_test_status_update(test, "Playing back message from test_vm_api_1234 to mock channel\n");
+ VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_1234", "default", "INBOX", message_id_1234, NULL);
+
+ ast_test_status_update(test, "Playing back message from test_vm_api_2345 to callback function\n");
+ VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_2345", "default", "INBOX", message_id_2345[0], &message_playback_callback_fn);
+ VM_API_INT_VERIFY(global_entered_playback_callback, 1);
+ global_entered_playback_callback = 0;
+
+ ast_test_status_update(test, "Playing back message from test_vm_api_2345 to callback function with default context\n");
+ VM_API_PLAYBACK_MESSAGE(test_channel, "test_vm_api_2345", NULL, "INBOX", message_id_2345[1], &message_playback_callback_fn);
+ VM_API_INT_VERIFY(global_entered_playback_callback, 1);
+ global_entered_playback_callback = 0;
+
+ VM_API_SNAPSHOT_CREATE("test_vm_api_1234", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ VM_API_SNAPSHOT_CREATE("test_vm_api_2345", "default", "Old", 0, AST_VM_SNAPSHOT_SORT_BY_TIME, 0);
+ VM_API_INT_VERIFY(test_mbox_snapshot->total_msg_num, 2);
+ test_mbox_snapshot = ast_vm_mailbox_snapshot_destroy(test_mbox_snapshot);
+
+ if (test_channel) {
+ ast_hangup(test_channel);
+ }
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(voicemail_api_off_nominal_msg_playback)
+{
+ struct ast_channel *test_channel;
+ const char *msg_id;
+ const char *invalid_msg_id = "POOPOO";
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "off_nominal_msg_playback";
+ info->category = "/main/voicemail_api/";
+ info->summary = "Off nominal message playback";
+ info->description =
+ "Tests off nominal conditions in playing back a "
+ "message. This includes:\n"
+ " * Invalid/NULL mailbox\n"
+ " * Invalid context\n"
+ " * Invalid/NULL folder\n"
+ " * Invalid message identifiers\n";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ VM_API_TEST_SETUP;
+ msg_id = test_snapshots[0]->msg_id;
+
+ if (!(test_channel = test_vm_api_create_mock_channel())) {
+ ast_log(AST_LOG_ERROR, "Failed to create mock channel for testing\n");
+ VM_API_TEST_CLEANUP;
+ return AST_TEST_FAIL;
+ }
+
+ ast_test_status_update(test, "Playing back message from invalid mailbox\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_3456", "default", "INBOX", msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message from NULL mailbox\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, NULL, "default", "INBOX", msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message from invalid context\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "defunct", "INBOX", msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message from invalid folder\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "BACON", msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message from NULL folder\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", NULL, msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message with invalid message specifier\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "INBOX", invalid_msg_id, NULL);
+
+ ast_test_status_update(test, "Playing back message with NULL message specifier\n");
+ VM_API_PLAYBACK_MESSAGE_OFF_NOMINAL(test_channel, "test_vm_api_1234", "default", "INBOX", NULL, NULL);
+ if (test_channel) {
+ ast_hangup(test_channel);
+ }
+ VM_API_TEST_CLEANUP;
+
+ return AST_TEST_PASS;
+}
+
+static int unload_module(void)
+{
+ /* Snapshot tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_snapshot);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_snapshot);
+
+ /* Move Tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_move);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_move);
+
+ /* Remove Tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_remove);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_remove);
+
+ /* Forward Tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_forward);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_forward);
+
+ /* Message Playback Tests */
+ AST_TEST_UNREGISTER(voicemail_api_nominal_msg_playback);
+ AST_TEST_UNREGISTER(voicemail_api_off_nominal_msg_playback);
+ return 0;
+}
+
+static int load_module(void)
+{
+ /* Snapshot tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_snapshot);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_snapshot);
+
+ /* Move Tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_move);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_move);
+
+ /* Remove Tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_remove);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_remove);
+
+ /* Forward Tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_forward);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_forward);
+
+ /* Message Playback Tests */
+ AST_TEST_REGISTER(voicemail_api_nominal_msg_playback);
+ AST_TEST_REGISTER(voicemail_api_off_nominal_msg_playback);
+
+ return AST_MODULE_LOAD_SUCCESS;
+}
+
+AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Core Voicemail API Tests");