Voicemail Lesson 4

We can now leave multiple messages for the voicemail box owner. The application can differientiate between the owner and anyone else and play a different menu accordingly. The owner can obtain access to all previous recorded messages, whereas anyone else may only record a message. So, what next?

To make the voicemail system more personal, we will add an option to record a voicemail prompt for the owner, to be played as a welcome message. Additionally, our voicemail box owner may want to selectively delete messages.

Let's get started!


    • Sample Files:
      • Samples\voicemail_4.py

      We've expanded our welcome message logic by checking for the existence of a pre-recorded file. If it exists, we play the file, rather than using TTS for a welcome message.

      Menu expansion

      The top_level_menu() code has been expanded to include a third option for the owner of the voicemail box. The owner may record a welcome message, used by the welcome logic on first answering the call.

      Optional delete

      The play_all_messages() subroutine has been extended. After playing the message, the user is given the option of either keeping the message, or deleting it. We can delete a file using the file_man.delete_file() function.

    • __uas_identify__ = "application"
      __uas_version__ = "1.0b1"
      
      from prosody.uas import Hangup, Error, ICanRun
      
      """
      
          An inbound application that answers the call and says a welcome message.
          The welcome message defaults to "Welcome to the voicemail server for
          <name>" but can be changed. <name> is obtained from the
          application_parameters and must be registered on the inbound services
          page of the CWP.
      
          The application uses <name> to determine whether the caller owns the
          voicemail box. It does this by comparing <name> with the <call_from>
          property of the inbound call.
      
          If the caller owns the voicemail box, the top level options given are:
      
              1. Play all existing messages with the option to delete each one
                 individually
              2. Delete all existing messages
              3. Record a new welcome message
      
          If the caller does not own the voicemail box, he can leave a new
          message. The name of the WAV file to save to will be
          "samples/voicemail4/<name>/msg<counter>.wav". The <counter> is a
          variable, initialised to 1, which increments each time a message is
          successfully saved.
      
          This application is part of the online Voice Mail tutorial.
      
          For this sample it is important that you also read the online documentation about working with media files,
          which describes some of the effects of Eventual Consistency.
      
      """
      
      class VoiceMailBox:
          def __init__(self, box_name, channel, file_manager, logger, welcome_message):
              self._channel = channel
              self._file_manager = file_manager
              self._logger = logger
              self._file_format = "samples/voicemail4/" + box_name + "/msg{0}.wav"
              self._recorded_file_counter = 1
              self.welcome_message_filename = "samples/voicemail4/" + box_name + '/' + welcome_message
      
      
          def _move_files_down(self, counter):
              next_file = self._file_format.format(counter + 1)
              while self._file_manager.exists(next_file):
                  this_file = self._file_format.format(counter)
                  if self._file_manager.rename(next_file, this_file) is True:
                      counter += 1
                      next_file = self._file_format.format(counter + 1)
                  else:
                      break
      
      
          def record_welcome_message(self):
      
              # wait on i_can_run to prevent an accidental infinite loop
              i_can_run = ICanRun(self._channel)
              while i_can_run:
                  msg = "Please record a welcome message after the tone. Press any digit to stop the recording."
                  self.record_new_message(msg=msg, filename=self.welcome_message_filename)
      
                  key_selected = get_menu_select(self._channel, self,
                                                "Please press 1 to keep this message or 2 to record an alternative message.",
                                                20, "12")
                  if key_selected == '1':
                      self.say_tts("Message saved.")
                      break
      
      
          def reset_message_counter(self):
              # check the message box and set the counter
              while(self._file_manager.exists(self._file_format.format(self._recorded_file_counter))):
                  self._logger.info("{0} exists".format(self._recorded_file_counter))
                  self._recorded_file_counter += 1
      
      
          def say_tts(self, message, bargein=False, beep=False):
              cause = self._channel.FilePlayer.say(message, barge_in=bargein)
              if cause == self._channel.FilePlayer.Cause.BARGEIN:
                  return
              if cause == self._channel.FilePlayer.Cause.NORMAL:
                  if beep is True:
                      cause = self._channel.DTMFPlayer.play("#");
                      if cause != self._channel.DTMFPlayer.Cause.NORMAL:
                          raise Error("Play tone failed: caused is {0}".format(cause))
              else:
                  raise Error("Say '{0}' failed: cause is {1}".format(message, cause))
      
      
          def play_welcome(self):
              if self._file_manager.exists(self.welcome_message_filename):
                  self.play_file(self.welcome_message_filename)
                  return True
              return False
      
      
          def play_file(self, filename):
              cause = self._channel.FilePlayer.play(filename)
              if cause != self._channel.FilePlayer.Cause.NORMAL:
                  raise Error("Play '{0}' failed: cause is {1}".format(filename, cause))
      
      
          def record_new_message(self, msg="Please record a message after the tone. Press any digit to stop the recording.", filename=None, replace=False):
      
              if filename is None:
                  if replace is True and self._recorded_file_counter > 1:
                      self._recorded_file_counter -= 1
                  filename = self._file_format.format(self._recorded_file_counter)
                  self._recorded_file_counter += 1
      
              self.say_tts(msg, beep=True)
      
              cause = self._channel.FileRecorder.record(filename, milliseconds_max_silence=3000,
                                                        seconds_timeout=60, barge_in=True)
              if cause != self._channel.FileRecorder.Cause.SILENCE and cause != self._channel.FileRecorder.Cause.BARGEIN:
                  raise Error("Record message failed: cause is {0}".format(cause))
              # check whether the file has been saved correctly, does it exist?
              if not self._file_manager.exists(filename):
                  self.say_tts("Your message could not be saved.")
              else:
                  self.say_tts("The following message has been recorded")
                  self.play_file(filename)
      
      
          def play_all_messages(self):
              file_counter = 1
              say_counter = 1
              filename = self._file_format.format(file_counter)
      
              while self._file_manager.exists(filename):
                  self.say_tts("Message {0}".format(say_counter))
                  self.play_file(filename)
      
                  key_selected = get_menu_select(self._channel, self,
                                                "Please press 1 to keep this message. \
                                                Press 2 to delete this message. \
                                                Press 3 to quit.",
                                                20, "123")
      
                  if key_selected == '2':
                      if self._file_manager.delete_file(filename) is True:
                          self.say_tts("Message deleted")
                          self._move_files_down(file_counter)
                          file_counter -= 1
                  if key_selected == '3':
                      break
      
                  file_counter += 1
                  say_counter += 1
                  filename = self._file_format.format(file_counter)
      
              self._recorded_file_counter = file_counter
              if file_counter == 1:
                  self.say_tts("You have no messages to play.")
              else:
                  self.say_tts("End of messages.")
      
      
          def delete_all_messages(self):
              counter = 1
              filename = self._file_format.format(counter)
      
              while self._file_manager.exists(filename):
                  self._file_manager.delete_file(filename)
                  counter += 1
                  filename = self._file_format.format(counter)
      
              if counter == 1:
                  self.say_tts("You have no messages to delete.")
              else:
                  self.say_tts("All messages deleted.")
      
              self._recorded_file_counter = 1
      
      
      def main(channel, application_instance_id, file_man, my_log, application_parameters):
          return_code = 0
          my_log.info("Started {0} :".format(application_parameters))
      
          try:
              # The voicemail box name must have been configured at service registration
              if len(application_parameters) == 0:
                  raise Error("Argument list is empty")
      
              mailbox = VoiceMailBox(application_parameters, channel, file_man, my_log, "welcome.wav")
              mailbox.reset_message_counter()
      
              my_log.info("Ring and answer")
      
              channel.ring(2)
              channel.answer()
              caller = get_caller(channel)
      
              my_log.info("Call from {0} to {1}'s Voicemail answered".format(caller, application_parameters))
      
              if mailbox.play_welcome() is False:
                  mailbox.say_tts("Welcome to the voicemail server for {0}".format(" ".join(list(application_parameters))))
      
              if caller == application_parameters:
                  top_level_menu(channel, mailbox)
              else:
                  replace = False
                  # wait on i_can_run to prevent an accidental infinite loop
                  i_can_run = ICanRun(channel)
                  while i_can_run:
                      mailbox.record_new_message(replace=replace)
                      key_selected = get_menu_select(channel, mailbox,
                                                     "Please press 1 to keep this message or 2 to record an alternative message",
                                                     20, "12")
      
                      if key_selected == '1':
                          mailbox.say_tts("Message saved.")
                          break
                      replace = True
      
              mailbox.say_tts("Goodbye.")
      
              channel.hang_up();
      
          except Hangup as exc:
              my_log.info("Completed with Hangup")
              return_code = 100
      
          except Error as exc:
              my_log.error("Completed with Error exception! {0}".format(exc))
              return_code = -101
      
          except Exception as exc:
              my_log.exception("Completed with exception! {0}".format(exc))
              return_code = -102
      
          finally:
              if channel.state() != channel.State.IDLE:
                  channel.hang_up()
          my_log.info("Voicemail 4 example completed")
          return return_code
      
      
      def get_caller(channel):
          caller = channel.Details.call_from
          # Extract the username from the SIP address
          if caller.startswith("sip:"):
              caller = caller[4:]
          return caller.split('@', 1)[0]
      
      
      def top_level_menu(channel, mailbox):
      
          key_selected = get_menu_select(channel, mailbox,
                                        "Press 1 to listen to all messages. \
                                         Press 2 to delete all messages. \
                                         Press 3 to record a welcome message. \
                                         Press 4 to quit.",
                                         20, "1234")
      
          if key_selected == '1':
              mailbox.play_all_messages()
          elif key_selected == '2':
              mailbox.delete_all_messages()
          elif key_selected == '3':
              mailbox.record_welcome_message()
      
      
      def get_menu_select(channel, mailbox, prompt, seconds_repeat_prompt, valid_keys):
          channel.DTMFDetector.clear_digits()
      
          # wait on i_can_run to prevent an accidental infinite loop
          i_can_run = ICanRun(channel)
          while i_can_run:
              mailbox.say_tts(prompt, bargein=True, beep=True)
      
              digits = channel.DTMFDetector.get_digits(end=valid_keys, seconds_predigits_timeout=seconds_repeat_prompt)
              cause = channel.DTMFDetector.cause()
              if cause == channel.DTMFDetector.Cause.END:
                  return digits[-1]
              else:
                  mailbox.say_tts("Invalid selection.")
                                  
    • Sample Files:
      • Samples\C#\Voicemail4\Voicemail4.cs

      In this step the PlayWelcomeMessage() method has been expanded. If the required pre-recorded file exists it plays it. Otherwise, a welcome message is played using TTS. Additionally the message names are now stored in a database (REQUIRES DATABASE SETUP: see Database Tutorial for instructions).

      Menu Expansion

      The TopLevelMenu() method now has a third option: the ability to leave a message prompt. RecordNewMessage has been altered so that we can specify a destination record filename.

      Optional Delete

      In PlayAllMessage(), after playing the message, we use some TTS and then the GetMenuSelection() method to prompt the user to keep or delete the message just played. Depending on the selection, the message is deleted using the FileManager.DeleteFile() method.

      Database

      The names of the message files have been abstracted and stored in a local database. As the cloud file system stores the messages files in a reliable and distributed manner, there can be some delays before files are available to read. Using the database ensures the list of files the sample manipulates is accurate and up to date.

    • using System;
      using System.Threading;
      using System.Collections.Generic;
      using AMSClassLibrary;
      using UASAppAPI;
      using System.Data.SqlClient;
      
      // An inbound application that answers the call, and says a welcome message
      // that defaults to "Welcome to the voicemail server for <name>." but can
      // be changed. <name> is obtained from the applicationArguments.
      // It is compared with the <callFrom> property of the inbound call.
      //
      // If <callFrom> equals <name>
      //	 Say "Press 1 to listen to all messages.
      //        Press 2 to delete all messages.
      //        Press 3 to record a welcome message".
      //	 1. Play all existing messages - with option of deleting each one individually.
      //	 2. Delete all existing messages
      //	 3. Record a welcome message
      // else
      //	 Do recording as in voicemail 2.
      //
      // All options return to the above menu.
      // The app only completes when the call is hung up.
      // The name of the .wav file to save to will be "samples/voicemail4/name/msg_<guid>.wav"
      // where guid is a unique identifier.
      //
      // This application uses an SQLEXPRESS database to manage the recorded message files.
      // Please see the Database Tutorial documentation for instructions on how to set up
      // the required database 'test'.
      //
      // Requires:
      // [applicationParameters = voicemail box name]
      namespace Voicemail4
      {
          public class Voicemail4 : UASInboundApplication
          {
              // Possible return codes
              enum ReturnCode
              {
                  // Success Codes:
                  Success = 0,
                  RecordedAMessage = 1,
                  CallHungUp = 2,
      
                  // Fail Codes:
                  // -1 to -99 reserved
                  ExceptionThrown = -100,
                  ArgumentEmpty = -101,
                  RecordingFailedToStart = -102
              }
      
              // This is the entry point for the application
              public override int Run(UASCallChannel channel,
                                      string applicationParameters)
              {
                  this.Trace.TraceInfo("Started with application Parameters: {0}", applicationParameters);
                  ReturnCode reply = ReturnCode.Success;
      
                  // The voicemail box name must have been configured in the
                  // applicationParameters at service registration
                  _voicemailBoxName = applicationParameters;
                  if (_voicemailBoxName.Length == 0)
                  {
                      return (int)ReturnCode.ArgumentEmpty;
                  }
      
                  try
                  {
                      // Set up the location for the recorded message files
                      _messageFolder = "samples/voicemail4/" + _voicemailBoxName + "/";
      
                      // Setup the location and format for the recorded filenames and welcome message
                      _welcomeMessageFileName = "welcome.wav";
                      _messageFileNameFormat = "msg_{0}.wav";
      
                      // Answer the call
                      CallState state = channel.Answer();
                      if (state == CallState.Answered)
                      {
                          // Create Database Connection Object
                          _voicemailBoxDatabase = new VoicemailBoxDatabase(applicationParameters);
      
                          // Open the database
                          _voicemailBoxDatabase.Open();
      
                          string caller = GetCaller(channel);
                          this.Trace.TraceInfo("Call from {0} to {1}'s Voicemail answered",
                                                      caller, _voicemailBoxName);
      
                          PlayWelcomeMessage(channel);
      
                          // If the caller is the owner of the voicemailbox let them manage their voicemailbox
                          // Else record a message
                          if (caller == _voicemailBoxName)
                          {
                              TopLevelMenu(channel);
                          }
                          else
                          {
                              RecordNewMessage(channel);
      
                              channel.FilePlayer.Say("Goodbye.");
                          }
                      }
                  }
                  catch (Exception e)
                  {
                      this.Trace.TraceError("Exception thrown {0}", e.Message);
                      reply = ReturnCode.ExceptionThrown;
                  }
                  finally
                  {
                      channel.HangUp();
                      _voicemailBoxDatabase.Close();
                  }
      
                  this.Trace.TraceInfo("Completed");
                  return (int)reply;
              }
      
              // Play the recorded welcome message or a standard welcome message if it
              // doesn't exist.
              private ReturnCode PlayWelcomeMessage(UASCallChannel channel)
              {
                  if (this.FileManager.FileExists(_welcomeMessageFileName))
                  {
                      channel.FilePlayer.Play(_messageFolder + _welcomeMessageFileName);
                  }
                  else
                  {
                      string welcomeMessage = "Welcome to the voicemail server for " + _voicemailBoxName;
                      channel.FilePlayer.Say(welcomeMessage);
                  }
      
                  if (channel.FilePlayer.Cause == FilePlayerCause.HangUp)
                  {
                      return ReturnCode.CallHungUp;
                  }
                  return ReturnCode.Success;
              }
      
              // Obtain the caller id
              private string GetCaller(UASCallChannel channel)
              {
                  // Extract the user part of a SIP address
                  string caller = channel.CallDetails.CallFrom;
                  if (caller.Contains("sip:"))
                  {
                      caller = caller.Substring(4);
                      int pos = caller.IndexOf('@');
                      if (pos >= 0)
                      {
                          caller = caller.Remove(pos);
                      }
                  }
                  return caller;
              }
      
              // Get a new message filename
              private string GetNextRecordFileName()
              {
                  return String.Format(_messageFileNameFormat, Guid.NewGuid().ToString());
              }
      
              // Run the top level menu for the voicemailbox owner.
              private ReturnCode TopLevelMenu(UASCallChannel channel)
              {
                  ReturnCode reply = ReturnCode.Success;
                  string prompt = "Press 1 to listen to all messages. " +
                                  "Press 2 to delete all messages. " +
                                  "Press 3 to record a welcome message. " +
                                  "Press hash to exit.";
                  char keySelected;
      
                  do
                  {
                      reply = GetMenuSelection(channel, prompt, 20, "123#", out keySelected);
                      if (reply == ReturnCode.Success)
                      {
                          switch (keySelected)
                          {
                              case '1':
                                  reply = PlayAllMessages(channel);
                                  break;
                              case '2':
                                  DeleteAllMessages(channel);
                                  break;
                              case '3':
                                  reply = RecordNewMessage(channel, true);
                                  break;
                              case '#':
                                  return reply;
                          }
                      }
                  } while (reply == ReturnCode.Success);
      
                  return reply;
              }
      
              private ReturnCode RecordNewMessage(UASCallChannel channel)
              {
                  return RecordNewMessage(channel, false);
              }
      
              // Record a new message, allowing the caller to save or retry the recording
              private ReturnCode RecordNewMessage(UASCallChannel channel, bool recordingWelcomeMessage)
              {
                  ReturnCode reply = ReturnCode.Success;
                  string prompt;
                  string fileName;
      
                  if (recordingWelcomeMessage)
                  {
                      fileName = _welcomeMessageFileName;
                      prompt = "Please record a welcome message after the tone. " +
                               "Press any digit to stop the recording.";
                  }
                  else
                  {
                      fileName = GetNextRecordFileName();
                      prompt = "Please record a message after the tone. " +
                               "Press any digit to stop the recording.";
                  }
                  string fullFileName = _messageFolder + fileName;
      
                  // Loop round until we have recorded a message acceptable to the caller
                  // or caller has hung up
                  bool satisfiedWithMessage = false;
                  do
                  {
                      // Play the prompt
                      channel.FilePlayer.Say(prompt);
      
                      // Delay before starting the recording and playing the tone (for effect)
                      Thread.Sleep(1000);
      
                      // Clear any received digits
                      channel.DtmfDetector.ClearDigits();
      
                      // Start recording, enabling barge in
                      this.Trace.TraceInfo("Recording to file: [{0}]", fullFileName);
                      if (!channel.FileRecorder.Start(fullFileName, true))
                      {
                          this.Trace.TraceError("Recording failed to start cause = {0}",
                                                channel.FileRecorder.Cause);
                          reply = ReturnCode.RecordingFailedToStart;
                          break;
                      }
      
                      // Play a tone to signal that recording has started
                      channel.DtmfPlayer.Play("0");
      
                      // Wait for a period for recording to complete or
                      // the call to have been hung up
                      FileRecorderCause recCause = channel.FileRecorder.WaitUntilFinished(60);
                      if (recCause == FileRecorderCause.HangUp)
                      {
                          this.Trace.TraceInfo("Call has been hung up");
                          reply = ReturnCode.CallHungUp;
                          break;
                      }
                      if (recCause == FileRecorderCause.Timeout)
                      {
                          this.Trace.TraceInfo("Recording has stopped after 60 seconds");
                          channel.FileRecorder.Stop();
                      }
      
                      // Replay the recorded message
                      channel.FilePlayer.Say("The following message has been recorded");
                      if (channel.FilePlayer.Play(fullFileName, true) == FilePlayerCause.HangUp)
                      {
                          this.Trace.TraceInfo("Call has been hung up");
                          reply = ReturnCode.CallHungUp;
                          break;
                      }
      
                      // Check if the user is satisfied with the recorded message
                      string acknowledgePrompt = "Please press 1 to keep this message or 2 to record an " +
                                                 "alternative message";
                      char keySelected;
      
                      reply = GetMenuSelection(channel, acknowledgePrompt, 20, "12", out keySelected);
                      if (reply == ReturnCode.Success)
                      {
                          if (keySelected == '1')
                          {
                              satisfiedWithMessage = true;
                              if (!recordingWelcomeMessage)
                              {
                                  _voicemailBoxDatabase.SaveMessage(fileName);
                              }
                              channel.FilePlayer.Say("Message saved.");
                          }
                      }
                  } while (!satisfiedWithMessage && (reply == ReturnCode.Success));
      
                  return reply;
              }
      
              // Play all the recorded messages, allowing the caller to delete any as required
              private ReturnCode PlayAllMessages(UASCallChannel channel)
              {
                  ReturnCode reply = ReturnCode.Success;
      
                  List<string> fileNames = _voicemailBoxDatabase.GetMessageFileNames();
      
                  foreach (string fileName in fileNames)
                  {
                      string fullFileName = _messageFolder + fileName;
      
                      if (!this.FileManager.FileExists(fullFileName))
                      {
                          channel.FilePlayer.Say("The next message is currently inaccessable");
                          Thread.Sleep(1000);
                          continue;
                      }
      
                      channel.FilePlayer.Say("Next message");
      
                      channel.FilePlayer.Play(fullFileName);
                      if (channel.FilePlayer.Cause == FilePlayerCause.HangUp)
                      {
                          reply = ReturnCode.CallHungUp;
                          break;
                      }
      
                      // Allow the caller to delete individual messages
                      string prompt = "Press 1 to keep this message. Press 2 to delete this message";
                      char keySelected;
                      reply = GetMenuSelection(channel, prompt, 20, "12", out keySelected);
                      if (reply == ReturnCode.Success)
                      {
                          if (keySelected == '2')
                          {
                              // delete the entry in the db
                              _voicemailBoxDatabase.DeleteMessage(fileName);
      
                              // and delete the message file on the cloud
                              this.FileManager.DeleteFile(fullFileName);
      
                              channel.FilePlayer.Say("Message deleted.");
                          }
                      }
                  }
      
                  if (reply == ReturnCode.Success)
                  {
                      channel.FilePlayer.Say("No more messages.");
                  }
      
                  return reply;
              }
      
              // Delete all recorded messages, both in the database and the files themselves
              private void DeleteAllMessages(UASCallChannel channel)
              {
                  // Get the names of all existing files
                  List<string> fileNames = _voicemailBoxDatabase.GetMessageFileNames();
      
                  // Delete them from the db
                  _voicemailBoxDatabase.DeleteAllMessages();
      
                  // Delete all the message files on the cloud
                  if (fileNames.Count > 0)
                  {
                      foreach (string fileName in fileNames)
                      {
                          this.FileManager.DeleteFile(_messageFolder + fileName);
                      }
                      channel.FilePlayer.Say("All messages have been deleted.");
                  }
                  else
                  {
                      channel.FilePlayer.Say("There are no messages to delete.");
                  }
              }
      
              // Find out what the caller wants to do.
              // Returns: Success if a valid key was selected, CallHungUp if call hung up.
              private ReturnCode GetMenuSelection(UASCallChannel channel, string prompt, int secondsRepeatPrompt, string validKeys, out char keySelected)
              {
                  keySelected = '\0';
      
                  // Prepare to receive digits
                  channel.DtmfDetector.ClearDigits();
      
                  do
                  {
                      FilePlayerCause cause = channel.FilePlayer.Say(prompt, true);
                      if (cause == FilePlayerCause.HangUp)
                      {
                          return ReturnCode.CallHungUp;
                      }
      
                      string digits;
                      channel.DtmfDetector.GetDigits(1, out digits, secondsRepeatPrompt);
                      if (channel.DtmfDetector.Cause == DtmfDetectorCause.Count)
                      {
                          if (validKeys.Contains(digits))
                          {
                              this.Trace.TraceInfo("Got keypress {0}", digits[0]);
                              keySelected = digits[0];
                              return ReturnCode.Success;
                          }
                          else
                          {
                              channel.FilePlayer.Say("Selection not valid.");
                          }
                      }
                  } while (channel.DtmfDetector.Cause != DtmfDetectorCause.HangUp);
      
                  // returning on hangup
                  return ReturnCode.CallHungUp;
              }
      
              private string _messageFolder;
              private string _welcomeMessageFileName;
              private string _messageFileNameFormat;
              private VoicemailBoxDatabase _voicemailBoxDatabase;
              private string _voicemailBoxName;
          }
      
          /// <summary>
          /// A class that represents a database of voicemail messages.
          /// </summary>
          class VoicemailBoxDatabase
          {
              public VoicemailBoxDatabase(string tableName)
              {
                  // add a non-numeric prefix to the table name
                  _tableName = "vmb" + tableName;
      
                  _mySqlConnection = new SqlConnection("server=.\\SQLEXPRESS;" +
                                                       "Trusted_Connection=yes;" +
                                                       "database=test;" +
                                                       "connection timeout=10");
              }
      
              public void Open()
              {
                  _mySqlConnection.Open();
      
                  // Create the table if it doesn't already exist.
                  CreateTable();
              }
      
              public void Close()
              {
                  _mySqlConnection.Close();
              }
      
              public void SaveMessage(string fileName)
              {
                  // Save an entry in the database
                  string command = "insert into " + _tableName;
                  command += " values (getdate(), '" + fileName + "')";
                  ExecuteSqlNonQueryCommand(command);
              }
      
              public void DeleteMessage(string fileName)
              {
                  // Delete the entry in the database
                  string command = "delete from " + _tableName;
                  command += " where filename='" + fileName + "'";
                  ExecuteSqlNonQueryCommand(command);
              }
      
              public void DeleteAllMessages()
              {
                  // Delete all the entries in the database
                  string command = "delete from " + _tableName;
                  ExecuteSqlNonQueryCommand(command);
              }
      
              public List<string> GetMessageFileNames()
              {
                  List<string> fileNames = new List<string>();
                  string command = "select * from " + _tableName;
                  SqlCommand myCommand = new SqlCommand(command, _mySqlConnection);
                  SqlDataReader reader = myCommand.ExecuteReader();
                  if (reader.HasRows)
                  {
                      // Read the row data
                      while (reader.Read())
                      {
                          // Get rid of any extraneous characters
                          string fileName = reader["filename"].ToString().TrimEnd(_endChars);
                          fileNames.Add(fileName);
                      }
                  }
                  reader.Close();
      
                  return fileNames;
              }
      
              // Create the table if necessary
              private void CreateTable()
              {
                  string command =
                      "if object_id('" + _tableName + "', 'U') is null begin " +
                          "create table " + _tableName + " (timestamp datetime, filename char(64)) " +
                      "end";
      
                  ExecuteSqlNonQueryCommand(command);
              }
      
              private void ExecuteSqlNonQueryCommand(string command)
              {
                  SqlCommand myCommand = new SqlCommand(command, _mySqlConnection);
                  myCommand.ExecuteNonQuery();
              }
      
              private string _tableName;
              private SqlConnection _mySqlConnection;
              private readonly char[] _endChars = { ' ', '\n', '\r' };
          }
      }
                                  
    • Sample Files:
      • Samples\VB\Voicemail4\Voicemail4.vb

      In this step the PlayWelcomeMessage() method has been expanded. If the required pre-recorded file exists it plays it. Otherwise, a welcome message is played using TTS.

      Menu Expansion

      The TopLevelMenu() method now has a third option: the ability to leave a message prompt. RecordNewMessage has been altered so that we can specify a destination record filename.

      Optional Delete

      In PlayAllMessage(), after playing the message, we use some TTS and then the GetMenuSelection() method to prompt the user to keep or delete the message just played. Depending on the selection, the message is deleted using the FileManager.DeleteFile() method.

      Database

      The names of the message files have been abstracted and stored in a local database. As the cloud file system stores the messages files in a reliable and distributed manner, there can be some delays before files are available to read. Using the database ensures the list of files the sample manipulates is accurate and up to date.

    • Imports System.Collections.Generic
      Imports System.Data.SqlClient
      Imports AMSClassLibrary
      Imports UASAppAPI
      
      ' An inbound application that answers the call, and says a welcome message
      ' that defaults to "Welcome to the voicemail server for <name>." but can
      ' be changed. <name> is obtained from the applicationArguments.
      ' It is compared with the <callFrom> property of the inbound call.
      '
      ' If <callFrom> equals <name>
      '	 Say "Press 1 to listen to all messages.
      '         Press 2 to delete all messages.
      '         Press 3 to record a welcome message".
      '	 1. Play all existing messages - with option of deleting each one individually.
      '	 2. Delete all existing messages
      '	 3. Record a welcome message
      ' else
      '	 Do recording as in voicemail 2.
      '
      ' All options return to the above menu.
      ' The app only completes when the call is hung up.
      ' The name of the .wav file to save to will be "samples/voicemail4/name/msg<guid>.wav".
      ' where guid is a unique identifier.
      '
      ' This application uses an SQLEXPRESS database to manage the recorded message files.
      '
      ' Requires:
      ' [applicationParameters = voicemail box name]
      Namespace Voicemail4
      
          ' The application class.
          ' This must have the same name as the assembly and must inherit from either
          ' UASInboundApplication or UASOutboundApplication.
          ' It must override the Run method.
          Public Class Voicemail4
              Inherits UASInboundApplication
      
              ' Possible return codes
              Enum ReturnCode
                  ' Success Codes:
                  Success = 0
                  RecordedAMessage = 1
                  CallHungUp = 2
      
                  ' Fail Codes:
                  ' -1 to -99 reserved
                  ExceptionThrown = -100
                  ArgumentEmpty = -101
                  RecordingFailedToStart = -102
              End Enum
      
              ' This is the entry point for the application
              Overrides Function Run(ByVal channel As UASCallChannel, _
                                     ByVal applicationParameters As String) _
                                     As Integer
      
                  Me.Trace.TraceInfo("Started")
                  Dim reply As ReturnCode = ReturnCode.Success
      
                  ' The voicemail box name must have been configured in the
                  ' applicationParameters at service registration
                  voicemailBoxName = applicationParameters
                  If voicemailBoxName.Length = 0 Then
                      Return ReturnCode.ArgumentEmpty
                  End If
      
                  Try
                      ' Set up the location for the recorded message files
                      messageFolder = "samples/voicemail4/" + voicemailBoxName + "/"
      
                      ' Setup the location and format for the recorded filenames and welcome message
                      welcomeMessageFilename = "welcome.wav"
                      messageFileNameFormat = "msg_{0}.wav"
      
                      ' Answer the call
                      Dim state As CallState
                      state = channel.Answer()
                      If state = CallState.Answered Then
      
                          ' Create Database Connection Object
                          voicemailBoxDatabase = New VoicemailBoxDatabase(applicationParameters)
      
                          ' Open the database
                          voicemailBoxDatabase.Open()
      
                          Dim caller = GetCaller(channel)
                          Me.Trace.TraceInfo("Call from {0} to {1}'s Voicemail answered", _
                                                      caller, voicemailBoxName)
      
                          PlayWelcomeMessage(channel)
      
                          ' If the caller is the owner of the voicemailbox let them manage their voicemailbox
                          ' Else record a message
                          If caller = voicemailBoxName Then
                              TopLevelMenu(channel)
                          Else
                              RecordNewMessage(channel)
      
                              channel.FilePlayer.Say("Goodbye.")
                          End If
                      End If
      
                  Catch ex As Exception
                      Me.Trace.TraceError("Exception thrown {0}", ex.Message)
                      reply = ReturnCode.ExceptionThrown
                  Finally
                      channel.HangUp()
                  End Try
      
                  Me.Trace.TraceInfo("Completed")
                  Return reply
      
              End Function
      
              ' Play the recorded welcome message or a standard welcome message if it
              ' doesn't exist.
              Private Function PlayWelcomeMessage(ByVal channel As UASCallChannel) As ReturnCode
      
                  If Me.FileManager.FileExists(welcomeMessageFilename) Then
                      channel.FilePlayer.Play(messageFolder + welcomeMessageFilename)
                  Else
                      Dim welcomeMessage = "Welcome to the voicemail server for " + voicemailBoxName
                      channel.FilePlayer.Say(welcomeMessage)
                  End If
      
                  If channel.FilePlayer.Cause = FilePlayerCause.HangUp Then
                      Return ReturnCode.CallHungUp
                  End If
                  Return ReturnCode.Success
              End Function
      
              Private Function GetCaller(ByVal channel As UASCallChannel) As String
                  ' Extract the user part of a SIP address
                  Dim caller = channel.CallDetails.CallFrom
                  If caller.Contains("sip:") Then
                      caller = caller.Substring(4)
                      Dim pos = caller.IndexOf("@")
                      If (pos >= 0) Then
                          caller = caller.Remove(pos)
                      End If
                  End If
                  Return caller
              End Function
      
              ' Get a new message filename
              Private Function GetNextRecordFileName() As String
                  Return String.Format(messageFileNameFormat, Guid.NewGuid().ToString())
              End Function
      
              ' Run the top level menu for the voicemailbox owner.
              Private Function TopLevelMenu(ByVal channel As UASCallChannel) As ReturnCode
                  Dim reply = ReturnCode.Success
                  Dim prompt = "Press 1 to listen to all messages. " + _
                               "Press 2 to delete all messages. " + _
                               "Press 3 to record a welcome message. " + _
                               "Press hash to exit."
                  Dim keySelected As Char
      
                  Do
                      reply = GetMenuSelection(channel, prompt, 20, "123#", keySelected)
                      If reply = ReturnCode.Success Then
                          Select Case keySelected
                              Case "1"
                                  reply = PlayAllMessages(channel)
                              Case "2"
                                  DeleteAllMessages(channel)
                              Case "3"
                                  reply = RecordNewMessage(channel, True)
                              Case "#"
                                  Return reply
                          End Select
                      End If
                  Loop While reply = ReturnCode.Success
      
                  Return reply
              End Function
      
              'Dim filename = String.Format(recordFilenameFormat, recordedFileCounter.ToString())
              '                recordedFileCounter += 1
              'Dim prompt = "Please record a message after the tone. " + _
              '             "Press any digit to stop the recording."
              Private Function RecordNewMessage(ByVal channel As UASCallChannel) As ReturnCode
                  Return RecordNewMessage(channel, False)
              End Function
      
              Private Function RecordNewMessage(ByVal channel As UASCallChannel, ByVal recordingWelcomeMessage As Boolean) As ReturnCode
                  Dim reply = ReturnCode.Success
                  Dim prompt As String
                  Dim fileName As String
      
                  If recordingWelcomeMessage Then
                      fileName = welcomeMessageFilename
                      prompt = "Please record a welcome message after the tone. " + _
                               "Press any digit to stop the recording."
                  Else
                      fileName = GetNextRecordFileName()
                      prompt = "Please record a message after the tone. " + _
                               "Press any digit to stop the recording."
                  End If
      
                  Dim fullFileName = messageFolder + fileName
      
                  ' Loop round until have recorded a message acceptable to the caller
                  ' or caller has hung up
                  Dim satisfiedWithMessage = False
                  Do
                      ' Play the prompt
                      channel.FilePlayer.Say(prompt)
      
                      ' Delay before starting the recording and playing the tone (for effect)
                      Thread.Sleep(1000)
      
                      ' Clear any received digits
                      channel.DtmfDetector.ClearDigits()
      
                      ' Start recording, enabling barge in
                      Me.Trace.TraceInfo("Recording message to file: [{0}]", fullFileName)
                      If Not channel.FileRecorder.Start(fullFileName, True) Then
                          Me.Trace.TraceError("Recording failed to start cause = {0}", _
                                                channel.FileRecorder.Cause)
                          reply = ReturnCode.RecordingFailedToStart
                          Exit Do
                      End If
      
                      ' Play a tone to signal that recording has started
                      channel.DtmfPlayer.Play("0")
      
                      ' Wait for a period for recording to complete or
                      ' the call to have been hung up
                      Dim recCause = channel.FileRecorder.WaitUntilFinished(60)
                      If recCause = FileRecorderCause.HangUp Then
                          Me.Trace.TraceInfo("Call has been hung up")
                          reply = ReturnCode.CallHungUp
                          Exit Do
                      End If
                      If recCause = FileRecorderCause.Timeout Then
                          Me.Trace.TraceInfo("Recording has stopped after 60 seconds")
                          channel.FileRecorder.Stop()
                      End If
      
                      ' Replay the recorded message
                      channel.FilePlayer.Say("The following message has been recorded")
                      If channel.FilePlayer.Play(fullFileName, True) = FilePlayerCause.HangUp Then
                          Me.Trace.TraceInfo("Call has been hung up")
                          reply = ReturnCode.CallHungUp
                          Exit Do
                      End If
      
                      ' Check if the user is satisfied with the recorded message
                      Dim acknowledgePrompt = "Please press 1 to keep this message or 2 to record an " + _
                                              "alternative message"
                      Dim keySelected As Char
      
                      reply = GetMenuSelection(channel, acknowledgePrompt, 20, "12", keySelected)
                      If reply = ReturnCode.Success Then
                          If keySelected = "1" Then
                              satisfiedWithMessage = True
                              If Not recordingWelcomeMessage Then
                                  voicemailBoxDatabase.SaveMessage(fileName)
                              End If
                              channel.FilePlayer.Say("Message saved.")
                          End If
                      End If
                  Loop While Not satisfiedWithMessage And (reply = ReturnCode.Success)
      
                  Return reply
              End Function
      
              Private Function PlayAllMessages(ByVal channel As UASCallChannel) As ReturnCode
                  Dim reply = ReturnCode.Success
                  Dim counter = 1
      
                  Dim fileNames = voicemailBoxDatabase.GetMessageFileNames()
      
                  For Each fileName In fileNames
      
                      Dim fullFileName = messageFolder + fileName
      
                      If Not Me.FileManager.FileExists(fullFileName) Then
                          channel.FilePlayer.Say("The next message is currently inaccessable")
                          Thread.Sleep(1000)
                          Continue For
                      End If
      
                      channel.FilePlayer.Say("Next Message")
      
                      channel.FilePlayer.Play(fullFileName)
                      If channel.FilePlayer.Cause = FilePlayerCause.HangUp Then
                          reply = ReturnCode.CallHungUp
                          Exit For
                      End If
      
                      ' Allow the caller to delete individual messages
                      Dim prompt = "Press 1 to keep this message. Press 2 to delete this message"
                      Dim keySelected As Char
                      reply = GetMenuSelection(channel, prompt, 20, "12", keySelected)
                      If reply = ReturnCode.Success Then
                          If keySelected = "2" Then
                              ' delete the entry in the db
                              voicemailBoxDatabase.DeleteMessage(fileName)
      
                              Me.FileManager.DeleteFile(fullFileName)
      
                              channel.FilePlayer.Say("Message deleted.")
                          End If
                      End If
                  Next
      
                  If reply = ReturnCode.Success Then
                      channel.FilePlayer.Say("No more messages.")
                  End If
      
                  Return reply
              End Function
      
              ' Delete all recorded messages, both in the database and the files themselves
              Private Sub DeleteAllMessages(ByVal channel As UASCallChannel)
      
                  ' Get the names of all existing files
                  Dim fileNames = voicemailBoxDatabase.GetMessageFileNames()
      
                  ' Delete them from the db
                  voicemailBoxDatabase.DeleteAllMessages()
      
                  ' Delete all the message files on the cloud
                  If (fileNames.Count > 0) Then
                      For Each fileName In fileNames
                          Me.FileManager.DeleteFile(messageFolder + fileName)
                      Next
                      channel.FilePlayer.Say("All messages have been deleted.")
                  Else
                      channel.FilePlayer.Say("There are no messages to delete.")
                  End If
              End Sub
      
              ' Returns: Success if a valid key was selected, CallHungUp if call hung up.
              Private Function GetMenuSelection(ByVal channel As UASCallChannel, ByVal prompt As String, ByVal secondsRepeatPrompt As Integer, ByVal validKeys As String, ByRef keySelected As Char) As ReturnCode
                  keySelected = "\0"
      
                  ' Prepare to receive digits
                  channel.DtmfDetector.ClearDigits()
      
                  Do
                      Dim cause = channel.FilePlayer.Say(prompt, True)
                      If cause = FilePlayerCause.HangUp Then
                          Return ReturnCode.CallHungUp
                      End If
      
                      Dim digits As String = ""
                      channel.DtmfDetector.GetDigits(1, digits, secondsRepeatPrompt)
                      If channel.DtmfDetector.Cause = DtmfDetectorCause.Count Then
                          If (validKeys.Contains(digits)) Then
                              Me.Trace.TraceInfo("Got keypress {0}", digits(0))
                              keySelected = digits(0)
                              Return ReturnCode.Success
                          Else
                              channel.FilePlayer.Say("Invalid selection.")
                          End If
                      End If
                  Loop While channel.DtmfDetector.Cause <> DtmfDetectorCause.HangUp
      
                  ' returning on hangup
                  Return ReturnCode.CallHungUp
              End Function
      
              Private voicemailBoxDatabase As VoicemailBoxDatabase
              Private voicemailBoxName As String
              Private messageFolder As String
              Private messageFileNameFormat As String
              Private welcomeMessageFilename As String
              Private recordFilenameFormat As String
              Private recordedFileCounter As String
      
          End Class
      
          ' A class that represents a database of voicemail messages.
          Public Class VoicemailBoxDatabase
      
              Public Sub New(ByVal tableName As String)
                  ' add a non-numeric prefix to the table name
                  Me.tableName = "vmb" + tableName
      
                  mySqlConnection = New SqlConnection("server=.\SQLEXPRESS;" + _
                                                      "Trusted_Connection=yes;" + _
                                                      "database=test;" + _
                                                      "connection timeout=10")
              End Sub
      
              Public Sub Open()
                  mySqlConnection.Open()
      
                  ' Create the table if it doesn't already exist.
                  CreateTable()
              End Sub
      
              Public Sub Close()
                  mySqlConnection.Close()
              End Sub
      
              Public Sub SaveMessage(ByVal fileName As String)
                  ' Save an entry in the database
                  Dim command = "insert into " + tableName
                  command += " values (getdate(), '" + fileName + "')"
                  ExecuteSqlNonQueryCommand(command)
              End Sub
      
              Public Sub DeleteMessage(ByVal fileName As String)
                  ' Delete the entry in the database
                  Dim command = "delete from " + tableName
                  command += " where filename='" + fileName + "'"
                  ExecuteSqlNonQueryCommand(command)
              End Sub
      
              Public Sub DeleteAllMessages()
                  ' Delete all the entries in the database
                  Dim command = "delete from " + tableName
                  ExecuteSqlNonQueryCommand(command)
              End Sub
      
              Public Function GetMessageFileNames() As List(Of String)
                  Dim fileNames = New List(Of String)
                  Dim command = "select * from " + tableName
                  Dim myCommand = New SqlCommand(command, mySqlConnection)
                  Dim reader = myCommand.ExecuteReader()
                  If (reader.HasRows) Then
                      ' Read the row data
                      While (reader.Read())
                          ' Get rid of any extraneous characters
                          Dim fileName = reader("filename").ToString().TrimEnd(" "c, vbLf, vbCr)
                          fileNames.Add(fileName)
                      End While
                  End If
                  reader.Close()
      
                  Return fileNames
              End Function
      
              ' Create the table if necessary
              Private Sub CreateTable()
                  Dim command = _
                      "if object_id('" + tableName + "', 'U') is null begin " + _
                          "create table " + tableName + " (timestamp datetime, filename char(64)) " + _
                      "end"
      
                  ExecuteSqlNonQueryCommand(Command)
              End Sub
      
              Private Sub ExecuteSqlNonQueryCommand(ByVal command As String)
                  Dim myCommand = New SqlCommand(command, mySqlConnection)
                  myCommand.ExecuteNonQuery()
              End Sub
      
              Private tableName As String
              Private mySqlConnection As SqlConnection
          End Class
      End Namespace
                                  

Take it away...

If you have understood everything in these tutorials, you should be some way to understanding how easy it is to create a multi-purpose voicemail system. Take our code and experiment!