[asterisk-commits] mmichelson: branch 10-digiumphones r361216 - /branches/10-digiumphones/apps/

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Wed Apr 4 15:18:20 CDT 2012


Author: mmichelson
Date: Wed Apr  4 15:18:15 2012
New Revision: 361216

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=361216
Log:
apps changes necessary for Digium phone support.


Modified:
    branches/10-digiumphones/apps/app_mixmonitor.c
    branches/10-digiumphones/apps/app_queue.c
    branches/10-digiumphones/apps/app_voicemail.c
    branches/10-digiumphones/apps/app_voicemail.exports.in

Modified: branches/10-digiumphones/apps/app_mixmonitor.c
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/apps/app_mixmonitor.c?view=diff&rev=361216&r1=361215&r2=361216
==============================================================================
--- branches/10-digiumphones/apps/app_mixmonitor.c (original)
+++ branches/10-digiumphones/apps/app_mixmonitor.c Wed Apr  4 15:18:15 2012
@@ -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 @@
 #include "asterisk/channel.h"
 #include "asterisk/autochan.h"
 #include "asterisk/manager.h"
+#include "asterisk/callerid.h"
 #include "asterisk/mod_format.h"
 
 /*** DOCUMENTATION
@@ -106,6 +108,12 @@
 						<para>Use the specified file to record the <emphasis>transmit</emphasis> audio feed.
 						Like with the basic filename argument, if an absolute path isn't given, it will create
 						the file in the configured monitoring directory.</para>
+					</option>
+					<option name="m">
+						<argument name="mailbox" required="true" />
+						<para>Create a copy of the recording as a voicemail in each indicated <emphasis>mailbox</emphasis>
+						separated by commas eg. m(1111 at default,2222 at default,...)</para>
+						<note><para>The recording will be deleted once all the copies are made.</para></note>
 					</option>
 				</optionlist>
 			</parameter>
@@ -176,6 +184,16 @@
 
 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];
+	AST_LIST_ENTRY(vm_recipient) list;
+};
+
 struct mixmonitor {
 	struct ast_audiohook audiohook;
 	char *filename;
@@ -186,6 +204,20 @@
 	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 {
@@ -197,6 +229,7 @@
 	MUXFLAG_READ = (1 << 6),
 	MUXFLAG_WRITE = (1 << 7),
 	MUXFLAG_COMBINED = (1 << 8),
+	MUXFLAG_VMRECIPIENTS = (1 << 6),
 };
 
 enum mixmonitor_args {
@@ -205,6 +238,7 @@
 	OPT_ARG_VOLUME,
 	OPT_ARG_WRITENAME,
 	OPT_ARG_READNAME,
+	OPT_ARG_VMRECIPIENTS,
 	OPT_ARG_ARRAY_SIZE,	/* Always last element of the enum */
 };
 
@@ -216,6 +250,7 @@
 	AST_APP_OPTION_ARG('W', MUXFLAG_VOLUME, OPT_ARG_VOLUME),
 	AST_APP_OPTION_ARG('r', MUXFLAG_READ, OPT_ARG_READNAME),
 	AST_APP_OPTION_ARG('t', MUXFLAG_WRITE, OPT_ARG_WRITENAME),
+	AST_APP_OPTION_ARG('m', MUXFLAG_VMRECIPIENTS, OPT_ARG_VMRECIPIENTS),
 });
 
 struct mixmonitor_ds {
@@ -314,6 +349,64 @@
 		ast_softhangup(peer, AST_SOFTHANGUP_UNBRIDGE);	
 
 	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 at context,mailbox2 at context2, mailbox3 at context3" */
+	char *cur_mailbox = ast_strdupa(vm_recipients);
+	char *cur_context;
+	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_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));
+
+			/* 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
@@ -330,6 +423,13 @@
 			ast_free(mixmonitor->name);
 			ast_free(mixmonitor->post_process);
 		}
+
+		/* Free everything in the recipient list */
+		clear_mixmonitor_recipient_list(mixmonitor);
+
+		/* clean stringfields */
+		ast_string_field_free_memory(mixmonitor);
+
 		ast_free(mixmonitor);
 	}
 }
@@ -363,9 +463,59 @@
 	}
 }
 
+/*!
+ * \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, char *ext)
+{
+	struct vm_recipient *recipient = NULL;
+	struct ast_vm_recording_data recording_data;
+	char filename[PATH_MAX];
+
+	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, mixmonitor->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 and mailbox 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_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);
+	}
+
+	/* Delete the source file */
+	snprintf(filename, sizeof(filename), "%s.%s", mixmonitor->filename, ext);
+	if (remove(filename)) {
+		ast_log(LOG_ERROR, "Failed to delete recording source file %s\n", filename);
+	}
+
+	/* Free the string fields for recording_data before exiting the function. */
+	ast_string_field_free_memory(&recording_data);
+}
+
 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;
@@ -479,6 +629,27 @@
 	}
 
 	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;
 }
@@ -518,7 +689,7 @@
 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,
-				  const char *filename_read) 
+				  const char *filename_read, const char *recipients) 
 {
 	pthread_t thread;
 	struct mixmonitor *mixmonitor;
@@ -540,6 +711,12 @@
 
 	/* Pre-allocate mixmonitor structure and spy */
 	if (!(mixmonitor = ast_calloc(1, sizeof(*mixmonitor)))) {
+		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;
 	}
 
@@ -578,6 +755,32 @@
 
 	if (!ast_strlen_zero(filename_read)) {
 		mixmonitor->filename_read = ast_strdup(filename_read);
+	}
+
+	if (!ast_strlen_zero(recipients)) {
+		char callerid[256];
+
+		ast_channel_lock(chan);
+
+		/* We use the connected line of the invoking channel for caller ID. */
+		ast_debug(3, "Connected Line CID = %d - %s : %d - %s\n", chan->connected.id.name.valid,
+			chan->connected.id.name.str, chan->connected.id.number.valid,
+			chan->connected.id.number.str);
+		ast_callerid_merge(callerid, sizeof(callerid),
+			S_COR(chan->connected.id.name.valid, chan->connected.id.name.str, NULL),
+			S_COR(chan->connected.id.number.valid, chan->connected.id.number.str, NULL),
+			"Unknown");
+
+		ast_string_field_set(mixmonitor, call_context, chan->context);
+		ast_string_field_set(mixmonitor, call_macrocontext, chan->macrocontext);
+		ast_string_field_set(mixmonitor, call_extension, chan->exten);
+		ast_string_field_set(mixmonitor, call_callerchan, chan->name);
+		ast_string_field_set(mixmonitor, call_callerid, callerid);
+		mixmonitor->call_priority = chan->priority;
+
+		ast_channel_unlock(chan);
+
+		add_vm_recipients_from_string(mixmonitor, recipients);
 	}
 
 	ast_set_flag(&mixmonitor->audiohook, AST_AUDIOHOOK_TRIGGER_SYNC);
@@ -630,6 +833,7 @@
 	char filename_buffer[1024] = "";
 
 	struct ast_flags flags = { 0 };
+	char *recipients = NULL;
 	char *parse;
 	AST_DECLARE_APP_ARGS(args,
 		AST_APP_ARG(filename);
@@ -688,6 +892,15 @@
 		if (ast_test_flag(&flags, MUXFLAG_READ)) {
 			filename_read = ast_strdupa(filename_parse(opts[OPT_ARG_READNAME], filename_buffer, sizeof(filename_buffer)));
 		}
+
+		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 there are no file writing arguments/options for the mix monitor, send a warning message and return -1 */
@@ -703,7 +916,7 @@
 	}
 
 	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);
+	launch_monitor_thread(chan, args.filename, flags.flags, readvol, writevol, args.post_process, filename_write, filename_read, recipients);
 
 	return 0;
 }

Modified: branches/10-digiumphones/apps/app_queue.c
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/apps/app_queue.c?view=diff&rev=361216&r1=361215&r2=361216
==============================================================================
--- branches/10-digiumphones/apps/app_queue.c (original)
+++ branches/10-digiumphones/apps/app_queue.c Wed Apr  4 15:18:15 2012
@@ -1577,7 +1577,13 @@
 	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"))) {

Modified: branches/10-digiumphones/apps/app_voicemail.c
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/apps/app_voicemail.c?view=diff&rev=361216&r1=361215&r2=361216
==============================================================================
--- branches/10-digiumphones/apps/app_voicemail.c (original)
+++ branches/10-digiumphones/apps/app_voicemail.c Wed Apr  4 15:18:15 2012
@@ -113,12 +113,14 @@
 #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"
@@ -330,6 +332,30 @@
 			</enumlist>
 		</description>
 	</application>
+	<application name="VoiceMailPlayMsg" language="en_US">
+		<synopsis>
+			Play a single voice mail msg from a mailbox by msg id.
+		</synopsis>
+		<syntax>
+			<parameter name="mailbox" required="true" argsep="@">
+				<argument name="mailbox" />
+				<argument name="context" />
+			</parameter>
+			<parameter name="msg_id" required="true">
+				<para>The msg id of the msg to play back. </para>
+			</parameter>
+		</syntax>
+		<description>
+			<para>This application sets the following channel variable upon completion:</para>
+			<variablelist>
+				<variable name="VOICEMAIL_PLAYBACKSTATUS">
+					<para>The status of the playback attempt as a text string.</para>
+					<value name="SUCCESS"/>
+					<value name="FAILED"/>
+				</variable>
+			</variablelist>
+		</description>
+	</application>
 	<application name="VMSayName" language="en_US">
 		<synopsis>
 			Play the name of a voicemail user
@@ -491,7 +517,6 @@
 #define ERROR_LOCK_PATH  -100
 #define OPERATOR_EXIT     300
 
-
 enum vm_box {
 	NEW_FOLDER,
 	OLD_FOLDER,
@@ -538,6 +563,25 @@
 	AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
 	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
@@ -785,6 +829,8 @@
 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);
@@ -923,6 +969,7 @@
 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);
 
 struct ao2_container *inprocess_container;
@@ -1700,25 +1747,6 @@
 	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
@@ -2178,6 +2206,9 @@
 	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;
 	}
 
@@ -2185,8 +2216,10 @@
 	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;
 	}
 
@@ -2298,8 +2331,8 @@
 	}
 
 	make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
-		S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
-		S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
+		chan ? S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL) : NULL,
+		chan ? S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL) : NULL,
 		fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
 	/* read mail file to memory */
 	len = ftell(p);
@@ -4658,8 +4691,8 @@
 #endif
 		/* flag added for Urgent */
 		fprintf(p, "X-Asterisk-VM-Flag: %s" ENDL, flag);
-		fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
-		fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
+		fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan ? chan->priority : 0);
+		fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan ? chan->name : "");
 		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);
@@ -5336,7 +5369,7 @@
 	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, chan->language)) {	
+		if (EXISTS(fromdir, msgnum, frompath, chan ? chan->language : "")) {
 			COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
 		} else {
 #endif
@@ -5354,10 +5387,12 @@
 		res = -1;
 	}
 	ast_unlock_path(todir);
-	notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt,
-		S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
-		S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
-		flag);
+	if (chan) {
+		notify_new_message(chan, recip, NULL, recipmsgnum, duration, fmt,
+			S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
+			S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
+			flag);
+	}
 	
 	return res;
 }
@@ -5559,6 +5594,295 @@
 	signed char record_gain;
 	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 <dir>/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 */
+
+	/* 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);
+			duration = (int) (framelength / ast_format_rate(ast_getformatbyname(recdata->recording_ext)));
+		}
+	}
+
+	/* 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[256];
+		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, "INBOX");
+
+#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, "");
+	}
+
+	free_user(recipient);
+	unlink(tmptxtfile);
+	return 0;
+}
 
 /*!
  * \brief Prompts the user and records a voicemail to a mailbox.
@@ -5934,6 +6258,14 @@
 		/* Store information */
 		txt = fdopen(txtdes, "w+");
 		if (txt) {
+			char msg_id[256] = "";
+			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", chan->exten, chan->context, 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(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
@@ -5954,7 +6286,8 @@
 				"callerid=%s\n"
 				"origdate=%s\n"
 				"origtime=%ld\n"
-				"category=%s\n",
+				"category=%s\n"
+				"msg_id=%s\n",
 				ext,
 				chan->context,
 				chan->macrocontext, 
@@ -5965,7 +6298,8 @@
 				chan->name,
 				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);
@@ -6170,7 +6504,7 @@
 	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)
 {
 #ifdef IMAP_STORAGE
 	/* we must use mbox(x) folder names, and copy the message there */
@@ -6245,6 +6579,10 @@
 		COPY(dir, msg, ddir, x, username, context, sfn, dfn);
 	}
 	ast_unlock_path(ddir);
+
+	if (newmsg) {
+		*newmsg = x;
+	}
 #endif
 	return 0;
 }
@@ -7967,7 +8305,7 @@
 			}
 		} 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);
 			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");
@@ -7977,7 +8315,7 @@
 			}
 		} 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);
 			if (res == ERROR_LOCK_PATH) {
 				/* If save failed do not delete the message */
 				vms->deleted[x] = 0;
@@ -9846,6 +10184,165 @@
 	if (vmu && !skipuser) {
 		memcpy(res_vmu, vmu, sizeof(struct ast_vm_user));
 	}
+	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)
+{
+	struct ast_config *msg_cfg;
+	struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
+	char filename[256];
+	const char *other_msg_id;
+	int found = 0;
+
+	for (vms->curmsg = 0; vms->curmsg <= vms->lastmsg && !found; vms->curmsg++) {
+		/* 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;
+		}
+
+		other_msg_id = ast_variable_retrieve(msg_cfg, "message", "msg_id");
+
+		if (!ast_strlen_zero(other_msg_id) && !strcmp(other_msg_id, msg_id)) {
+			/* 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);
+			found = 1;
+
+#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;
+			}
+		}
+
+		/* cleanup configs and msg */
+		ast_config_destroy(msg_cfg);
+		DISPOSE(vms->curdir, vms->curmsg);
+	}
+
+	return found ? 0 : -1;
+}
+
+/*!
+ * \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 (chan->_state != 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;
 }
 
@@ -10451,7 +10948,7 @@
 				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);
 				if (cmd == ERROR_LOCK_PATH) {
 					res = cmd;
 					goto out;
@@ -13016,6 +13513,7 @@
 	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_manager_unregister("VoicemailUsersList");
@@ -13067,6 +13565,7 @@
 	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_manager_register_xml("VoicemailUsersList", EVENT_FLAG_CALL | EVENT_FLAG_REPORTING, manager_list_voicemail_users);
@@ -13084,7 +13583,7 @@
 	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);

[... 706 lines stripped ...]



More information about the asterisk-commits mailing list