checked-in Stefan's new mailbox system (msg_storage,voicebox, modified voicemail).

git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@772 8eb893ce-cfd4-0310-b710-fb5ebe64c474
sayer/1.4-spce2.6
Raphael Coeffic 17 years ago
parent 7552ac3eb4
commit b88151a267

@ -0,0 +1,7 @@
plug_in_name = msg_storage
module_ldflags =
module_cflags =
COREPATH ?=../../core
include $(COREPATH)/plug-in/Makefile.app_module

@ -0,0 +1,221 @@
#include "AmPlugIn.h"
#include "log.h"
#include "MsgStorageAPI.h"
#include "MsgStorage.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <utime.h>
#include <errno.h>
#include <dirent.h>
#include <unistd.h>
#define MSG_DIR "/var/lib/voicebox/" // TODO: configurabel...
MsgStorage* MsgStorage::_instance = 0;
EXPORT_PLUGIN_CLASS_FACTORY(MsgStorage,"msg_storage");
MsgStorage::MsgStorage(const string& name)
: AmDynInvokeFactory(name) {
_instance = this;
}
MsgStorage::~MsgStorage() { }
int MsgStorage::onLoad() {
DBG("MsgStorage loaded.\n");
return 0;
}
void MsgStorage::invoke(const string& method,
const AmArg& args, AmArg& ret) {
if(method == "msg_new"){
MessageDataFile* f =
dynamic_cast<MessageDataFile*>(args.get(3).asObject());
if (NULL == f) {
throw(string("message data is not a file ptr."));
}
ret.push(msg_new(args.get(0).asCStr(),
args.get(1).asCStr(),
args.get(2).asCStr(),
f->fp));
} else if(method == "msg_get"){
msg_get(args.get(0).asCStr(),
args.get(1).asCStr(),
args.get(2).asCStr(),
ret);
} else if(method == "msg_markread"){
ret.push(msg_markread(args.get(0).asCStr(),
args.get(1).asCStr(),
args.get(2).asCStr()));
} else if(method == "msg_delete"){
ret.push(msg_delete(args.get(0).asCStr(),
args.get(1).asCStr(),
args.get(2).asCStr()));
} else if(method == "userdir_open"){
userdir_open(args.get(0).asCStr(),
args.get(1).asCStr(),
ret);
} else if(method == "userdir_close"){
ret.push(userdir_close(args.get(0).asCStr(),
args.get(1).asCStr()));
} else if(method == "userdir_getcount"){
userdir_getcount(args.get(0).asCStr(),
args.get(1).asCStr(),
ret);
} else if(method == "_list"){
ret.push("msg_new");
ret.push("msg_get");
ret.push("msg_markread");
ret.push("msg_delete");
ret.push("userdir_open");
ret.push("userdir_close");
ret.push("userdir_getcount");
}
else
throw AmDynInvoke::NotImplemented(method);
}
int MsgStorage::msg_new(string domain, string user,
string msg_name, FILE* data) {
string path = MSG_DIR "/" + domain + "/" + user + "/";
int status = mkdir(path.c_str(),
S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (status && (errno != EEXIST)) {
ERROR("creating '%s': %s\n",
path.c_str(),strerror(errno));
return MSG_EUSRNOTFOUND;
}
FILE* fp = fopen((path + msg_name).c_str(), "wb");
if (!fp) {
ERROR("creating '%s': %s\n",
(path + msg_name).c_str(),strerror(errno));
return MSG_ESTORAGE;
}
if (data)
filecopy(data, fp);
fclose(fp);
return MSG_OK;
}
void MsgStorage::msg_get(string domain, string user,
string msg_name, AmArg& ret) {
string fname = MSG_DIR "/" + domain + "/" + user + "/"+ msg_name;
FILE* fp = fopen(fname.c_str(), "r");
if (!fp)
ret.push(MSG_EMSGNOTFOUND);
else
ret.push(MSG_OK);
AmArg af;
af.setBorrowedPointer(new MessageDataFile(fp));
ret.push(af);
}
int MsgStorage::msg_markread(string domain, string user, string msg_name) {
string path = MSG_DIR "/" + domain + "/" + user + "/" + msg_name;
struct stat e_stat;
if (stat(path.c_str(), &e_stat)) {
ERROR("cannot stat '%s': %s\n",
path.c_str(),strerror(errno));
return MSG_EMSGNOTFOUND;
}
struct utimbuf buf;
buf.actime = e_stat.st_mtime+1;
buf.modtime = e_stat.st_mtime;
if (utime(path.c_str(), &buf)) {
ERROR("cannot utime '%s': %s\n",
path.c_str(),strerror(errno));
return MSG_EREADERROR;
}
return MSG_OK;
}
int MsgStorage::msg_delete(string domain, string user, string msg_name) {
string path = MSG_DIR "/" + domain + "/" + user + "/" + msg_name;
if (unlink(path.c_str())) {
ERROR("cannot unlink '%s': %s\n",
path.c_str(),strerror(errno));
return MSG_EMSGNOTFOUND;
}
return MSG_OK;
}
void MsgStorage::userdir_open(string domain, string user, AmArg& ret) {
string path = MSG_DIR "/" + domain + "/" + user + "/";
DBG("trying to list '%s'\n", path.c_str());
DIR* dir = opendir(path.c_str());
if (!dir) {
ret.push(MSG_EUSRNOTFOUND);
ret.push(AmArg()); // empty lis
return;
}
int err=0;
struct dirent* entry;
AmArg msglist;
msglist.assertArray(0); // make it an array
while( ((entry = readdir(dir)) != NULL) && (err == 0) ){
string msgname(entry->d_name);
if(!msgname.length() ||
msgname[0] == '.'){
continue;
}
struct stat e_stat;
if (stat((path+msgname).c_str(), &e_stat)) {
ERROR("cannot stat '%s': %s\n",
(path+msgname).c_str(),strerror(errno));
continue;
}
AmArg msg;
msg.push(msgname.c_str());
// TODO: change the system here, st_atime/mtime/...
// is not really safe for saving read status!
if (e_stat.st_atime != e_stat.st_mtime) {
msg.push(0);
} else {
msg.push(1);
}
msg.push((int)e_stat.st_size);
msglist.push(msg);
}
closedir(dir);
// uh, this is really inefficient...
ret.push(MSG_OK);
ret.push(msglist);
}
int MsgStorage::userdir_close(string domain, string user) { return 0; }
void MsgStorage::userdir_getcount(string domain, string user, AmArg& ret) { }
// copies ifp to ofp, blockwise
void MsgStorage::filecopy(FILE* ifp, FILE* ofp) {
size_t nread;
char buf[1024];
rewind(ifp);
while (!feof(ifp)) {
nread = fread(buf, 1, 1024, ifp);
if (fwrite(buf, 1, nread, ofp) != nread)
break;
}
}

@ -0,0 +1,32 @@
#ifndef _MSG_STORAGE_H
#define _MSG_STORAGE_H
#include "AmApi.h"
class MsgStorage : public AmDynInvokeFactory,
public AmDynInvoke
{
static MsgStorage* _instance;
int msg_new(string domain, string user, string msg_name, FILE* data);
void msg_get(string domain, string user, string msg_name, AmArg& ret);
int msg_markread(string domain, string user, string msg_name);
int msg_delete(string domain, string user, string msg_name);
void userdir_open(string domain, string user, AmArg& ret);
int userdir_close(string domain, string user);
void userdir_getcount(string domain, string user, AmArg& ret);
inline void filecopy(FILE* ifp, FILE* ofp);
public:
MsgStorage(const string& name);
~MsgStorage();
AmDynInvoke* getInstance(){ return _instance; }
int onLoad();
void invoke(const string& method, const AmArg& args, AmArg& ret);
};
#endif

@ -0,0 +1,25 @@
#ifndef _MSG_STORAGE_API_H
#define _MSG_STORAGE_API_H
#include <stdio.h>
#define MSG_OK 0
#define MSG_EMSGEXISTS 1
#define MSG_EUSRNOTFOUND 2
#define MSG_EMSGNOTFOUND 3
#define MSG_EALREADYCLOSED 4
#define MSG_EREADERROR 5
#define MSG_ENOSPC 6
#define MSG_ESTORAGE 7
#include "AmArg.h"
class MessageDataFile
: public ArgObject {
public:
FILE* fp;
MessageDataFile(FILE* fp)
: fp(fp) { }
};
#endif

@ -0,0 +1,9 @@
plug_in_name = voicebox
module_ldflags =
module_cflags =
extra_install = $(plug_in_name)_audio
COREPATH ?=../../core
include $(COREPATH)/plug-in/Makefile.app_module

@ -0,0 +1,17 @@
#ifndef _PROMPT_OPTIONS_H
#define _PROMPT_OPTIONS_H
struct PromptOptions {
bool has_digits;
bool digits_right;
PromptOptions(bool has_digits,
bool digits_right)
: has_digits(has_digits),
digits_right(digits_right) { }
PromptOptions()
: has_digits(false),
digits_right(false) { }
};
#endif

@ -0,0 +1,303 @@
/*
* $Id: WebConference.cpp 288 2007-03-28 16:32:02Z sayer $
*
* Copyright (C) 2007 iptego GmbH
*
* This file is part of sems, a free SIP media server.
*
* sems is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* For a license to use the sems software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* sems is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Voicebox.h"
#include "AmUtils.h"
#include "log.h"
#include "AmPlugIn.h"
#include "AmSessionContainer.h"
#include "../msg_storage/MsgStorageAPI.h"
#include "VoiceboxDialog.h"
#include <stdlib.h>
#include <string>
#include <vector>
using std::string;
using std::vector;
#define APP_NAME "voicebox"
EXPORT_SESSION_FACTORY(VoiceboxFactory,APP_NAME);
VoiceboxFactory::VoiceboxFactory(const string& _app_name)
: AmSessionFactory(_app_name)
{
}
AmDynInvokeFactory* VoiceboxFactory::MessageStorage=0;
// key config
unsigned int VoiceboxFactory::repeat_key = 1;
unsigned int VoiceboxFactory::save_key = 2;
unsigned int VoiceboxFactory::delete_key = 3;
unsigned int VoiceboxFactory::startover_key = 4;
AmPromptCollection* VoiceboxFactory::getPrompts(const string& domain,
const string& language) {
map<string, map<string, AmPromptCollection*> >::iterator d_it =
prompts.find(domain);
if (d_it != prompts.end()) {
map<string, AmPromptCollection*>::iterator l_it = d_it->second.find(language);
if (l_it != d_it->second.end())
return l_it->second;
}
return NULL;
}
// todo: combine options and promptcollection into struct
PromptOptions VoiceboxFactory::getPromptOptions(const string& domain,
const string& language) {
map<string, map<string, PromptOptions> >::iterator d_it =
prompt_options.find(domain);
if (d_it != prompt_options.end()) {
map<string, PromptOptions>::iterator l_it = d_it->second.find(language);
if (l_it != d_it->second.end())
return l_it->second;
}
return PromptOptions(false, false);
}
AmPromptCollection* VoiceboxFactory::loadPrompts(string prompt_base_path,
string domain, string language,
bool load_digits) {
AmPromptCollection* pc = new AmPromptCollection();
string prompt_path = prompt_base_path + "/" + domain + "/" + language + "/";
if (pc->setPrompt("pin_prompt", prompt_path + "pin_prompt.wav", APP_NAME) < 0) {
DBG("no pin prompt set for domain <%s>, language <%s>\n",
domain.c_str(), language.c_str());
}
#define ADD_DEF_PROMPT(str) \
if (pc->setPrompt(str, prompt_path + str + ".wav", APP_NAME) < 0) { \
delete pc; \
return NULL; \
}
// Parts for the welcome text
ADD_DEF_PROMPT("you_have");
ADD_DEF_PROMPT("new_msgs");
ADD_DEF_PROMPT("saved_msgs");
ADD_DEF_PROMPT("no_msg");
ADD_DEF_PROMPT("in_your_voicebox");
ADD_DEF_PROMPT("and");
// Menu played after each message
ADD_DEF_PROMPT("msg_menu");
// Menu played after last message
ADD_DEF_PROMPT("msg_end_menu");
// Status acknowledgement
ADD_DEF_PROMPT("msg_deleted");
ADD_DEF_PROMPT("msg_saved");
ADD_DEF_PROMPT("first_new_msg");
ADD_DEF_PROMPT("next_new_msg");
ADD_DEF_PROMPT("first_saved_msg");
ADD_DEF_PROMPT("next_saved_msg");
ADD_DEF_PROMPT("no_more_msg");
// # End of conversation
ADD_DEF_PROMPT("bye");
if (load_digits) {
ADD_DEF_PROMPT("new_msg");
ADD_DEF_PROMPT("saved_msg");
// digits from 1 to 19
for (unsigned int i=1;i<20;i++) {
string str = int2str(i);
if (pc->setPrompt(str, prompt_path + str + ".wav", APP_NAME) < 0) {
delete pc;
return NULL;
}
}
// 20, 30, ...90
for (unsigned int i=20;i<100;i+=10) {
string str = int2str(i);
if (pc->setPrompt(str, prompt_path + str + ".wav", APP_NAME) < 0) {
delete pc;
return NULL;
}
}
// x1 .. x9
for (unsigned int i=1;i<10;i++) {
string str = "x"+int2str(i);
if (pc->setPrompt(str, prompt_path + str + ".wav", APP_NAME) < 0) {
delete pc;
return NULL;
}
}
}
#undef ADD_DEF_PROMPT
return pc;
}
int VoiceboxFactory::onLoad()
{
AmConfigReader cfg;
if(cfg.loadFile(AmConfig::ModConfigPath + string(APP_NAME)+ ".conf"))
return -1;
// get application specific global parameters
configureModule(cfg);
vector<string> domains = explode(cfg.getParameter("domains"), ";");
domains.push_back(""); // add default (empty) domain
vector<string> languages = explode(cfg.getParameter("languages"), ";");
languages.push_back("");// add default (empty) language
string prompt_base_path = cfg.getParameter("prompt_base_path");
if (prompt_base_path.empty()) {
ERROR("prompt_base_path not set in configuration");
return -1;
}
for (vector<string>::iterator dom = domains.begin();
dom != domains.end(); dom++) {
for (vector<string>::iterator lang = languages.begin();
lang != languages.end(); lang++) {
string language = *lang;
size_t lang_opt_pos = language.find('(');
string lang_name = language.substr(0, lang_opt_pos);
string lang_opt;
bool lang_digits = false;
bool lang_digitpos_right = true;
if (lang_opt_pos != string::npos)
lang_opt = language.substr(lang_opt_pos,
language.find(')',lang_opt_pos+1));
if (lang_opt.find("digits=right") != string::npos) {
lang_digits = true;
lang_digitpos_right = true;
}
if (lang_opt.find("digits=left") != string::npos) {
lang_digits = true;
lang_digitpos_right = true;
}
AmPromptCollection* pc = loadPrompts(prompt_base_path, *dom,
lang_name, lang_digits);
if (NULL != pc) {
prompts[*dom][lang_name]=pc;
prompt_options[*dom][lang_name]=
PromptOptions(lang_digits, lang_digitpos_right);
DBG("Enabled language <%s> for domain <%s>\n",
lang->empty()?"default":lang_name.c_str(),
dom->empty()?"default":dom->c_str()
);
}
}
}
if (prompts.empty()) {
ERROR("No menu voice messages found at '%s'.\n",
prompt_base_path.c_str());
return -1;
}
string s_repeat_key = cfg.getParameter("repeat_key", "1");
if (str2i(s_repeat_key, repeat_key)) {
ERROR("repeat_key value '%s' unparseable.\n",
s_repeat_key.c_str());
return -1;
}
string s_save_key = cfg.getParameter("save_key", "2");
if (str2i(s_save_key, save_key)) {
ERROR("save_key value '%s' unparseable.\n",
s_save_key.c_str());
return -1;
}
string s_delete_key = cfg.getParameter("delete_key", "3");
if (str2i(s_delete_key, delete_key)) {
ERROR("delete_key value '%s' unparseable.\n",
s_delete_key.c_str());
return -1;
}
string s_startover_key = cfg.getParameter("startover_key", "4");
if (str2i(s_startover_key, startover_key)) {
ERROR("startover_key value '%s' unparseable.\n",
s_startover_key.c_str());
return -1;
}
MessageStorage = NULL;
MessageStorage = AmPlugIn::instance()->getFactory4Di("msg_storage");
if(NULL == MessageStorage){
ERROR("could not load msg_storage. Load a msg_storage implementation module.\n");
return -1;
}
return 0;
}
// incoming calls
AmSession* VoiceboxFactory::onInvite(const AmSipRequest& req)
{
string user;
string pin;
string domain;
string language;
string iptel_app_param = getHeader(req.hdrs, PARAM_HDR);
if (!iptel_app_param.length()) {
AmSession::Exception(500, APP_NAME ": parameters not found");
}
user = get_header_keyvalue(iptel_app_param,"User");
pin = get_header_keyvalue(iptel_app_param,"PIN");
domain = get_header_keyvalue(iptel_app_param,"Domain");
language = get_header_keyvalue(iptel_app_param,"Language");
// checks
if (user.empty())
throw AmSession::Exception(500, APP_NAME ": user missing");
AmPromptCollection* pc = getPrompts(domain, language);
if (NULL == pc)
throw AmSession::Exception(500, APP_NAME ": no menu for domain/language");
PromptOptions po = getPromptOptions(domain, language);
return new VoiceboxDialog(user, domain, pin, pc, po);
}

@ -0,0 +1,77 @@
/*
* $Id: PinAuthConference.h 288 2007-03-28 16:32:02Z sayer $
*
* Copyright (C) 2007 iptego GmbH
*
* This file is part of sems, a free SIP media server.
*
* sems is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* For a license to use the sems software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* sems is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef _VOICEBOX_H_
#define _VOICEBOX_H_
#include "AmApi.h"
#include "AmSession.h"
#include "AmAudio.h"
#include "AmPlaylist.h"
#include "AmPromptCollection.h"
#include "PromptOptions.h"
#include <map>
#include <string>
#include <list>
using std::map;
using std::string;
using std::list;
class VoiceboxFactory
: public AmSessionFactory
{
map<string, map<string, AmPromptCollection*> > prompts;
map<string, map<string, PromptOptions> > prompt_options;
AmPromptCollection* getPrompts(const string& domain, const string& language);
PromptOptions getPromptOptions(const string& domain, const string& language);
AmPromptCollection* loadPrompts(string prompt_base_path,
string domain, string language,
bool load_digits);
static AmDynInvokeFactory* MessageStorage;
static unsigned int repeat_key;
static unsigned int save_key;
static unsigned int delete_key;
static unsigned int startover_key;
public:
VoiceboxFactory(const string& _app_name);
AmSession* onInvite(const AmSipRequest&);
// AmSession* onInvite(const AmSipRequest& req,
// AmArg& session_params);
int onLoad();
friend class VoiceboxDialog;
};
#endif
// Local Variables:
// mode:C++
// End:

@ -0,0 +1,576 @@
/*
* $Id: WebConference.cpp 288 2007-03-28 16:32:02Z sayer $
*
* Copyright (C) 2007 iptego GmbH
*
* This file is part of sems, a free SIP media server.
*
* sems is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* For a license to use the sems software under conditions
* other than those described here, or to purchase support for this
* software, please contact iptel.org by e-mail at the following addresses:
* info@iptel.org
*
* sems is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "AmUtils.h"
#include "VoiceboxDialog.h"
#include "Voicebox.h"
#include "../msg_storage/MsgStorageAPI.h" // error codes
#define enqueueFront(msg) \
prompts->addToPlaylist(msg, (long)this, play_list, true)
#define enqueueBack(msg) \
prompts->addToPlaylist(msg, (long)this, play_list, false)
// event ids of playlist separator events
#define PLAYLIST_SEPARATOR_MSG_BEGIN 1 // play back of message starts
const char* MsgStrError(int e) {
switch (e) {
case MSG_OK: return "MSG_OK"; break;
case MSG_EMSGEXISTS: return "MSG_EMSGEXISTS"; break;
case MSG_EUSRNOTFOUND: return "MSG_EUSRNOTFOUND"; break;
case MSG_EMSGNOTFOUND: return "MSG_EMSGNOTFOUND"; break;
case MSG_EALREADYCLOSED: return "MSG_EALREADYCLOSED"; break;
case MSG_EREADERROR: return "MSG_EREADERROR"; break;
case MSG_ENOSPC: return "MSG_ENOSPC"; break;
case MSG_ESTORAGE: return "MSG_ESTORAGE"; break;
default: return "Unknown Error";
}
}
VoiceboxDialog::VoiceboxDialog(const string& user,
const string& domain,
const string& pin,
AmPromptCollection* prompts,
PromptOptions prompt_options)
: user(user), domain(domain), pin(pin),
prompts(prompts), prompt_options(prompt_options),
play_list(this),
userdir_open(false), in_saved_msgs(false),
do_save_cur_msg(false)
{
setDtmfDetectionEnabled(true);
msg_storage = VoiceboxFactory::MessageStorage->getInstance();
if(!msg_storage){
ERROR("could not get a message storage reference\n");
throw AmSession::Exception(500,"could not get a message storage reference");
}
}
VoiceboxDialog::~VoiceboxDialog()
{
// empty playlist items
play_list.close(false);
prompts->cleanup((long)this);
}
void VoiceboxDialog::onSessionStart(const AmSipRequest& req) {
if (pin.empty()) {
state = Prompting;
doMailboxStart();
} else {
state = EnteringPin;
enqueueFront("pin_prompt");
}
// set the playlist as input and output
setInOut(&play_list,&play_list);
}
void VoiceboxDialog::onBye(const AmSipRequest& req)
{
closeMailbox();
setStopped();
}
void VoiceboxDialog::process(AmEvent* ev)
{
// audio events
AmAudioEvent* audio_ev = dynamic_cast<AmAudioEvent*>(ev);
if (audio_ev &&
audio_ev->event_id == AmAudioEvent::noAudio) {
DBG("########## noAudio event #########\n");
if (Bye == state) {
closeMailbox();
dlg.bye();
setStopped();
}
return;
}
AmPlaylistSeparatorEvent* pl_ev = dynamic_cast<AmPlaylistSeparatorEvent*>(ev);
if (pl_ev) {
DBG("########## Playlist separator ####\n");
if (Prompting == state) {
if (pl_ev->event_id == PLAYLIST_SEPARATOR_MSG_BEGIN){
// mark message as saved
saveCurMessage();
// now we can accept action on the message
DBG("Changed state to MsgAction.\n");
state = MsgAction;
}
}
return;
}
AmSession::process(ev);
}
void VoiceboxDialog::onDtmf(int event, int duration)
{
DBG("VoiceboxDialog::onDtmf: event %d duration %d\n",
event, duration);
if (EnteringPin == state) {
play_list.close(false);
// check pin
if (event<10) {
entered_pin += int2str(event);
DBG("added '%s': PIN is now '%s'.\n",
int2str(event).c_str(), entered_pin.c_str());
}
if (event==10 || event==11) { // # and * keys
if (entered_pin.compare(pin)) { // wrong pin
entered_pin.clear();
play_list.close(false);
prompts->addToPlaylist("pin_prompt", (long)this, play_list, true);
}
}
if (!entered_pin.compare(pin)) {
state = Prompting;
doMailboxStart();
}
}
if (MsgAction == state) {
if ((unsigned int)event == VoiceboxFactory::repeat_key) {
play_list.close(false);
repeatCurMessage();
} else if ((unsigned int)event == VoiceboxFactory::save_key) {
state = Prompting;
play_list.close(false);
enqueueBack("msg_saved");
saveCurMessage();
edited_msgs.push_back(*cur_msg);
advanceMessage();
checkFinalMessage();
if (!isAtEnd()) {
enqueueCurMessage();
}
} else if ((unsigned int)event == VoiceboxFactory::delete_key) {
state = Prompting;
play_list.close(false);
enqueueBack("msg_deleted");
deleteCurMessage();
advanceMessage();
checkFinalMessage();
if (!isAtEnd()) {
enqueueCurMessage();
}
} else if ((unsigned int)event == VoiceboxFactory::startover_key) {
if (isAtLastMsg()) {
edited_msgs.push_back(*cur_msg);
state = Prompting;
mergeMsglists();
gotoFirstSavedMessage();
enqueueCurMessage();
}
}
}
if (PromptTurnover == state) {
if (((unsigned int)event == VoiceboxFactory::startover_key)
&& (isAtEnd())) {
state = Prompting;
mergeMsglists();
gotoFirstSavedMessage();
enqueueCurMessage();
}
}
}
void VoiceboxDialog::openMailbox() {
cur_msg = new_msgs.begin();
AmArg di_args,ret;
di_args.push(domain.c_str()); // domain
di_args.push(user.c_str()); // user
msg_storage->invoke("userdir_open",di_args,ret);
if (!ret.size()
|| !isArgInt(ret.get(0))) {
ERROR("userdir_open for user '%s' domain '%s'"
" returned no (valid) result.\n",
user.c_str(), domain.c_str()
);
return;
}
userdir_open = true;
int ecode = ret.get(0).asInt();
if (MSG_EUSRNOTFOUND == ecode) {
DBG("empty mailbox for user '%s' domain '%s'.",
user.c_str(), domain.c_str()
);
closeMailbox();
return;
}
if (MSG_OK != ecode) {
ERROR("userdir_open for user '%s' domain '%s': %s",
user.c_str(), domain.c_str(),
MsgStrError(ret.get(0).asInt()));
closeMailbox();
return;
}
if ((ret.size() < 2) ||
(!isArgArray(ret.get(1)))) {
ERROR("userdir_open for user '%s' domain '%s'"
" returned too few parameters.\n",
user.c_str(), domain.c_str()
);
closeMailbox();
return;
}
for (size_t i=0;i<ret.get(1).size();i++) {
AmArg& elem = ret.get(1).get(i);
if (!isArgArray(elem)
|| elem.size() != 3) {
ERROR("wrong element in userdir list.\n");
continue;
}
string msg_name = elem.get(0).asCStr();
int msg_unread = elem.get(1).asInt();
int size = elem.get(2).asInt();
if (size) { // TODO: treat empty messages as well!
if (msg_unread) {
new_msgs.push_back(Message(msg_name, size));
} else {
saved_msgs.push_back(Message(msg_name, size));
}
}
}
new_msgs.sort();
new_msgs.reverse();
saved_msgs.sort();
saved_msgs.reverse();
DBG("Got %zd new and %zd saved messages for user '%s' domain '%s'\n",
new_msgs.size(), saved_msgs.size(),
user.c_str(), domain.c_str());
if (new_msgs.size()) {
cur_msg = new_msgs.begin();
in_saved_msgs = false;
} else {
if (saved_msgs.size())
cur_msg = saved_msgs.begin();
in_saved_msgs = true;
}
}
void VoiceboxDialog::closeMailbox() {
if (!userdir_open)
return;
AmArg di_args,ret;
di_args.push(domain.c_str()); // domain
di_args.push(user.c_str()); // user
msg_storage->invoke("userdir_close",di_args,ret);
if (ret.size() &&
isArgInt(ret.get(0)) &&
ret.get(0).asInt() != MSG_OK
) {
ERROR("userdir_close for user '%s' domain '%s': %s\n",
user.c_str(), domain.c_str(),
MsgStrError(ret.get(0).asInt()));
}
userdir_open = false;
}
FILE* VoiceboxDialog::getCurrentMessage() {
string msgname = cur_msg->name;
DBG("trying to get message '%s' for user '%s' domain '%s'\n",
msgname.c_str(), user.c_str(), domain.c_str());
AmArg di_args,ret;
di_args.push(domain.c_str()); // domain
di_args.push(user.c_str()); // user
di_args.push(msgname.c_str()); // msg name
msg_storage->invoke("msg_get",di_args,ret);
if (!ret.size()
|| !isArgInt(ret.get(0))) {
ERROR("msg_get for user '%s' domain '%s' msg '%s'"
" returned no (valid) result.\n",
user.c_str(), domain.c_str(),
msgname.c_str()
);
return NULL;
}
int ecode = ret.get(0).asInt();
if (MSG_OK != ecode) {
ERROR("msg_get for user '%s' domain '%s' message '%s': %s",
user.c_str(), domain.c_str(),
msgname.c_str(),
MsgStrError(ret.get(0).asInt()));
return NULL;
}
if ((ret.size() < 2) ||
(!isArgAObject(ret.get(1)))) {
ERROR("msg_get for user '%s' domain '%s' message '%s': invalid return value\n",
user.c_str(), domain.c_str(),
msgname.c_str());
return NULL;
}
MessageDataFile* f =
dynamic_cast<MessageDataFile*>(ret.get(1).asObject());
if (NULL == f)
return NULL;
FILE* fp = f->fp;
delete f;
return fp;
}
void VoiceboxDialog::doMailboxStart() {
openMailbox();
doListOverview();
if (new_msgs.empty() && saved_msgs.empty()) {
state = Bye;
} else {
enqueueCurMessage();
}
}
void VoiceboxDialog::doListOverview() {
if (new_msgs.empty() && saved_msgs.empty()) {
enqueueBack("no_msg");
return;
}
enqueueFront("you_have");
if (!new_msgs.empty()) {
if (prompt_options.has_digits &&
(new_msgs.size() == 1)) {
// one new message
enqueueBack("new_msg");
} else {
// five
if (prompt_options.has_digits)
enqueueCount(new_msgs.size());
// new messages
enqueueBack("new_msgs");
}
if (!saved_msgs.empty())
enqueueBack("and");
}
if (!saved_msgs.empty()) {
if (prompt_options.has_digits &&
(saved_msgs.size() == 1)) {
// one saved message
enqueueBack("saved_msg");
} else {
// fifteen
if (prompt_options.has_digits)
enqueueCount(saved_msgs.size());
// saved messages
enqueueBack("saved_msgs");
}
}
}
bool VoiceboxDialog::enqueueCurMessage() {
if (((in_saved_msgs) && (cur_msg == saved_msgs.end()))
||((!in_saved_msgs) && (cur_msg == new_msgs.end()))) {
ERROR("check implementation!\n");
return false;
}
FILE* fp=getCurrentMessage();
if (NULL == fp)
return false;
if (!in_saved_msgs) {
if (cur_msg == new_msgs.begin())
enqueueBack("first_new_msg");
else
enqueueBack("next_new_msg");
} else {
if (cur_msg == saved_msgs.begin())
enqueueBack("first_saved_msg");
else
enqueueBack("next_saved_msg");
}
// notifies the dialog that playback of message starts
enqueueSeparator(PLAYLIST_SEPARATOR_MSG_BEGIN);
// enqueue msg
message.fpopen(cur_msg->name, AmAudioFile::Read, fp);
play_list.addToPlaylist(new AmPlaylistItem(&message, NULL));
if (!isAtLastMsg())
enqueueBack("msg_menu");
else
enqueueBack("msg_end_menu");
//can do save action on cur message?
do_save_cur_msg = !in_saved_msgs;
return true;
}
void VoiceboxDialog::repeatCurMessage() {
play_list.close(false);
message.rewind();
play_list.addToPlaylist(new AmPlaylistItem(&message, NULL));
enqueueBack("msg_menu");
}
void VoiceboxDialog::advanceMessage() {
if (!in_saved_msgs) {
if (cur_msg != new_msgs.end())
cur_msg++;
if (cur_msg == new_msgs.end()) {
cur_msg = saved_msgs.begin();
in_saved_msgs = true;
}
} else {
if (cur_msg != saved_msgs.end())
cur_msg++;
}
}
void VoiceboxDialog::gotoFirstSavedMessage() {
cur_msg = saved_msgs.begin();
in_saved_msgs = true;
}
void VoiceboxDialog::curMsgOP(const char* op) {
if (!isAtEnd()) {
string msgname = cur_msg->name;
AmArg di_args,ret;
di_args.push(domain.c_str()); // domain
di_args.push(user.c_str()); // user
di_args.push(msgname.c_str()); // msg name
msg_storage->invoke(op,di_args,ret);
if ((ret.size() < 1)
|| !isArgInt(ret.get(0))) {
ERROR("%s returned wrong result type\n", op);
return;
}
int errcode = ret.get(0).asInt();
if (errcode != MSG_OK) {
ERROR("%s error: %s\n",
op, MsgStrError(errcode));
}
}
}
void VoiceboxDialog::saveCurMessage() {
if (do_save_cur_msg)
curMsgOP("msg_markread");
do_save_cur_msg = false;
}
void VoiceboxDialog::deleteCurMessage() {
curMsgOP("msg_delete");
}
bool VoiceboxDialog::isAtLastMsg() {
if (in_saved_msgs) {
if (saved_msgs.empty())
return true;
return cur_msg->name == saved_msgs.back().name;
} else {
if (!saved_msgs.empty() || (new_msgs.empty()))
return false;
return cur_msg->name == new_msgs.back().name;
}
}
bool VoiceboxDialog::isAtEnd() {
bool res =
(in_saved_msgs && (cur_msg == saved_msgs.end()))
||(!in_saved_msgs && (cur_msg == new_msgs.end()));
return res;
}
void VoiceboxDialog::checkFinalMessage() {
if (isAtEnd()) {
if (!edited_msgs.empty()) {
enqueueBack("no_more_msg");
state = PromptTurnover;
} else {
state = Bye;
enqueueBack("no_msg");
}
}
}
void VoiceboxDialog::enqueueCount(unsigned int cnt) {
if (cnt > 99) {
ERROR("only support up to 99 messages count.\n");
return;
}
if ((cnt <= 20) || (! (cnt%10))) {
enqueueBack(int2str(cnt));
return;
}
div_t num = div(cnt, 10);
if (prompt_options.digits_right) {
// language has single digits after 10s
enqueueBack(int2str(num.quot * 10));
enqueueBack("x"+int2str(num.rem));
} else {
// language has single digits before 10s
enqueueBack("x"+int2str(num.rem));
enqueueBack(int2str(num.quot * 10));
}
}
// only one separator may be in playlist!
void VoiceboxDialog::enqueueSeparator(int id) {
playlist_separator.reset(new AmPlaylistSeparator(this, id));
play_list.addToPlaylist(new AmPlaylistItem(playlist_separator.get(), NULL));
}
// copy edited_msgs to saved_msgs
// so that the user can go throuh them again
void VoiceboxDialog::mergeMsglists() {
saved_msgs.clear();
saved_msgs = edited_msgs;
edited_msgs.clear();
}

@ -0,0 +1,115 @@
#ifndef _VOICEBOX_DIALOG_H_
#define _VOICEBOX_DIALOG_H_
#include "AmApi.h"
#include "AmSession.h"
#include "AmAudio.h"
#include "AmPlaylist.h"
#include "AmPromptCollection.h"
#include "PromptOptions.h"
#include <map>
#include <string>
#include <list>
using std::map;
using std::string;
using std::list;
struct Message {
string name;
int size;
int operator<(const Message& b) const
{ return name < b.name; }
Message() { }
Message(string n, int s)
: name(n), size(s) { }
};
class VoiceboxDialog
: public AmSession
{
public:
enum VoiceboxCallState {
None, // starting
EnteringPin, // checking mailbox pin
Prompting, // playing prompt (not in message yet)
MsgAction, // accepting action on message
PromptTurnover, // prompting to turn over to first message
Bye // term dialog
};
private:
AmPlaylist play_list;
// we need only one separator in queue
auto_ptr<AmPlaylistSeparator> playlist_separator;
AmPromptCollection* prompts;
PromptOptions prompt_options;
VoiceboxCallState state;
string entered_pin;
string user;
string domain;
string pin;
void openMailbox();
void closeMailbox();
FILE* getCurrentMessage();
// logic ops
void doMailboxStart();
void doListOverview();
void checkFinalMessage();
void mergeMsglists();
// msg ops
inline bool enqueueCurMessage();
inline void repeatCurMessage();
inline void advanceMessage();
inline void deleteCurMessage();
inline void saveCurMessage();
inline void curMsgOP(const char* op);
inline void enqueueCount(unsigned int cnt);
inline void enqueueSeparator(int id);
inline bool isAtEnd();
inline bool isAtLastMsg();
inline void gotoFirstSavedMessage();
list<Message> new_msgs;
list<Message> saved_msgs;
// list of the messages that come be in the msg list the next round
list<Message> edited_msgs;
bool userdir_open; // have we opened the user dir?
bool do_save_cur_msg; // saving of current message possible?
list<Message>::iterator cur_msg;
bool in_saved_msgs;
AmAudioFile message; // message file being played
AmDynInvoke* msg_storage;
public:
VoiceboxDialog(const string& user,
const string& domain,
const string& pin,
AmPromptCollection* prompts,
PromptOptions prompt_options);
~VoiceboxDialog();
void onSessionStart(const AmSipRequest& req);
void onDtmf(int event, int duration);
void onBye(const AmSipRequest& req);
void process(AmEvent* ev);
};
#endif
// Local Variables:
// mode:C++ // Stroustrup?
// End:

@ -0,0 +1,16 @@
prompt_base_path=../apps/voicebox/wav
#
# specific prompts for these domains will be supported
#
#
domains=iptego-voice.de
#
# specific prompts for these languages will be supported
#
#
languages=english

@ -0,0 +1,71 @@
# ----------- global configuration parameters ------------------------
debug=3 #5 # debug level (cmd line: -dddddddddd)
fork=yes
log_stderror=yes # (cmd line: -E)
check_via=no # (cmd. line: -v)
dns=no # (cmd. line: -r)
rev_dns=no # (cmd. line: -R)
port=5060
children=4
#listen=eth0
loadmodule "/usr/lib/msp-ser/modules/sl.so"
loadmodule "/usr/lib/msp-ser/modules/tm.so"
loadmodule "/usr/lib/msp-ser/modules/rr.so"
loadmodule "/usr/lib/msp-ser/modules/maxfwd.so"
loadmodule "/usr/lib/msp-ser/modules/textops.so"
# ----------------- setting module-specific parameters ---------------
# add value to ;lr param to make some broken UAs happy
modparam("rr", "enable_full_lr", 1)
# ------------------------- request routing logic -------------------
# main routing logic
route{
# initial sanity checks -- messages with
# max_forwards==0, or excessively long requests
if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483","Too Many Hops");
break;
};
if (!(method=="REGISTER")) record_route();
if ((method=="ACK") || (loose_route()) || (!uri==myself)) {
t_relay();
break;
};
if (method=="REGISTER") {
sl_send_reply("200", "okey");
# save("location");
break;
};
if ((method == "INVITE" ) || (method=="CANCEL") || (method=="REFER")) {
if (uri=~"sip:1.*") {
append_hf("P-App-Name: voicebox\r\n");
append_hf("P-App-Param: User=stefan;Domain=iptego-voice.de;Language=english\r\n");
rewritehostport("62.220.31.201:5070");
t_relay_to_udp("62.220.31.201","5070");
break;
}
append_hf("P-App-Name: voicemail\r\n");
append_hf("P-App-Param: User=stefan;Domain=iptego-voice.de;Language=english;Sender=someone;Mode=box\r\n");
rewritehostport("62.220.31.201:5070");
t_relay_to_udp("62.220.31.201","5070");
break;
}
sl_send_reply("501", "method not understood here");
break;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -33,8 +33,8 @@
#include "AmUtils.h"
#include "AmPlugIn.h"
#include "AmPlaylist.h"
#include "AmMail.h"
#include "../msg_storage/MsgStorageAPI.h"
#include "sems.h"
#include "log.h"
@ -56,13 +56,15 @@
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#define MOD_NAME "voicemail"
#define DEFAULT_AUDIO_EXT "wav"
#define DEFAULT_MAIL_TMPL_PATH string("/usr/local/etc/sems")
#define DEFAULT_MAIL_TMPL string("default")
#define DEFAULT_MAIL_TMPL_EXT string("template")
#define DEFAULT_MIN_RECORD_TIME 0
#define RECORD_TIMER 99
EXPORT_SESSION_FACTORY(AnswerMachineFactory,MOD_NAME);
@ -71,13 +73,13 @@ AnswerMachineFactory::AnswerMachineFactory(const string& _app_name)
: AmSessionFactory(_app_name)
{}
string AnswerMachineFactory::EmailAddress;
string AnswerMachineFactory::RecFileExt;
string AnswerMachineFactory::AnnouncePath;
string AnswerMachineFactory::DefaultAnnounce;
int AnswerMachineFactory::MaxRecordTime;
int AnswerMachineFactory::MinRecordTime = 0;
AmDynInvokeFactory* AnswerMachineFactory::UserTimer=0;
AmDynInvokeFactory* AnswerMachineFactory::MessageStorage=0;
bool AnswerMachineFactory::SaveEmptyMsg = true;
string AnswerMachineFactory::SmtpServerAddress = SMTP_ADDRESS_IP;
@ -85,9 +87,9 @@ unsigned int AnswerMachineFactory::SmtpServerPort = SMTP_PORT;
#ifdef USE_MYSQL
mysqlpp::Connection AnswerMachineFactory::Connection(mysqlpp::use_exceptions);
int get_audio_file(string message, string domain, string user,
string language, string *audio_file)
string language, string *audio_file)
{
string query_string;
@ -98,49 +100,49 @@ int get_audio_file(string message, string domain, string user,
} else {
if (language.empty()) {
if (domain.empty()) {
*audio_file = string("/tmp/") + MOD_NAME + "_" + message +
".wav";
query_string = "select audio from " + string(DEFAULT_AUDIO_TABLE) + " where application='" + MOD_NAME + "' and message='" + message + "' and language=''";
*audio_file = string("/tmp/") + MOD_NAME + "_" + message +
".wav";
query_string = "select audio from " + string(DEFAULT_AUDIO_TABLE) + " where application='" + MOD_NAME + "' and message='" + message + "' and language=''";
} else {
*audio_file = string("/tmp/") + domain + "_" + MOD_NAME +
"_" + message + ".wav";
query_string = "select audio from " + string(DOMAIN_AUDIO_TABLE) + " where application='" + MOD_NAME + "' and message='" + message + "' and domain='" + domain + "' and language=''";
*audio_file = string("/tmp/") + domain + "_" + MOD_NAME +
"_" + message + ".wav";
query_string = "select audio from " + string(DOMAIN_AUDIO_TABLE) + " where application='" + MOD_NAME + "' and message='" + message + "' and domain='" + domain + "' and language=''";
}
} else {
if (domain.empty()) {
*audio_file = string("/tmp/") + MOD_NAME + "_" + message +
"_" + language + ".wav";
query_string = "select audio from " + string(DEFAULT_AUDIO_TABLE) + " where application='" + MOD_NAME + "' and message='" + message + "' and language='" + language + "'";
*audio_file = string("/tmp/") + MOD_NAME + "_" + message +
"_" + language + ".wav";
query_string = "select audio from " + string(DEFAULT_AUDIO_TABLE) + " where application='" + MOD_NAME + "' and message='" + message + "' and language='" + language + "'";
} else {
*audio_file = string("/tmp/") + domain + "_" + MOD_NAME + "_" +
message + "_" + language + ".wav";
query_string = "select audio from " + string(DOMAIN_AUDIO_TABLE) + " where application='" + MOD_NAME + "' and message='" + message + "' and domain='" + domain + "' and language='" + language + "'";
*audio_file = string("/tmp/") + domain + "_" + MOD_NAME + "_" +
message + "_" + language + ".wav";
query_string = "select audio from " + string(DOMAIN_AUDIO_TABLE) + " where application='" + MOD_NAME + "' and message='" + message + "' and domain='" + domain + "' and language='" + language + "'";
}
}
}
try {
mysqlpp::Query query = AnswerMachineFactory::Connection.query();
DBG("Query string <%s>\n", query_string.c_str());
query << query_string;
mysqlpp::Result res = query.store();
mysqlpp::Row row;
if (res) {
if ((res.num_rows() > 0) && (row = res.at(0))) {
FILE *file;
unsigned long length = row.raw_string(0).size();
file = fopen((*audio_file).c_str(), "wb");
fwrite(row.at(0).data(), 1, length, file);
fclose(file);
return 1;
FILE *file;
unsigned long length = row.raw_string(0).size();
file = fopen((*audio_file).c_str(), "wb");
fwrite(row.at(0).data(), 1, length, file);
fclose(file);
return 1;
} else {
*audio_file = "";
return 1;
*audio_file = "";
return 1;
}
} else {
ERROR("Database query error\n");
@ -163,7 +165,7 @@ int AnswerMachineFactory::loadEmailTemplatesFromMySQL()
try {
mysqlpp::Query query = AnswerMachineFactory::Connection.query();
string query_string, table;
query_string = "select replace(template, '\r', '') as template, language from " + string(DEFAULT_TEMPLATE_TABLE) + " where application='" + MOD_NAME + "' and message='" + EMAIL_TMPL + "'";
@ -183,25 +185,25 @@ int AnswerMachineFactory::loadEmailTemplatesFromMySQL()
string tmp_file, tmpl_name;
row = res.at(i);
if (string(row["language"]) == "") {
tmp_file = "/tmp/voicemail_email.template";
tmpl_name = DEFAULT_MAIL_TMPL;
tmp_file = "/tmp/voicemail_email.template";
tmpl_name = DEFAULT_MAIL_TMPL;
} else {
tmp_file = string("/tmp/voicemail_email_") +
string(row["language"]) + ".template";
tmpl_name = DEFAULT_MAIL_TMPL + "_" +
string(row["language"]);
tmp_file = string("/tmp/voicemail_email_") +
string(row["language"]) + ".template";
tmpl_name = DEFAULT_MAIL_TMPL + "_" +
string(row["language"]);
}
file = fopen(tmp_file.c_str(), "wb");
file = fopen(tmp_file.c_str(), "wb");
fwrite(row["template"], 1, length, file);
fclose(file);
DBG("loading %s as %s ...\n", tmp_file.c_str(), tmpl_name.c_str());
EmailTemplate tmp_tmpl;
if (tmp_tmpl.load(tmp_file)) {
ERROR("Voicemail: could not load default"
" email template: '%s'\n", tmp_file.c_str());
return -1;
ERROR("Voicemail: could not load default"
" email template: '%s'\n", tmp_file.c_str());
return -1;
} else {
email_tmpl[tmpl_name] = tmp_tmpl;
email_tmpl[tmpl_name] = tmp_tmpl;
}
}
if (email_tmpl.count(DEFAULT_MAIL_TMPL) == 0) {
@ -225,27 +227,27 @@ int AnswerMachineFactory::loadEmailTemplatesFromMySQL()
string tmp_file, tmpl_name;
row = res.at(i);
if (string(row["language"]) == "") {
tmp_file = "/tmp/" + string(row["domain"]) +
"_voicemail_email.template";
tmpl_name = string(row["domain"]);
tmp_file = "/tmp/" + string(row["domain"]) +
"_voicemail_email.template";
tmpl_name = string(row["domain"]);
} else {
tmp_file = string("/tmp/") + string(row["domain"]) +
"_voicemail_email_" + string(row["language"]) +
".template";
tmpl_name = string(row["domain"]) + "_" +
string(row["language"]);
tmp_file = string("/tmp/") + string(row["domain"]) +
"_voicemail_email_" + string(row["language"]) +
".template";
tmpl_name = string(row["domain"]) + "_" +
string(row["language"]);
}
file = fopen(tmp_file.c_str(), "wb");
file = fopen(tmp_file.c_str(), "wb");
fwrite(row["template"], 1, length, file);
fclose(file);
DBG("loading %s as %s ...\n",tmp_file.c_str(), tmpl_name.c_str());
EmailTemplate tmp_tmpl;
if (tmp_tmpl.load(tmp_file) < 0) {
ERROR("Voicemail: could not load default"
" email template: '%s'\n", tmp_file.c_str());
return -1;
ERROR("Voicemail: could not load default"
" email template: '%s'\n", tmp_file.c_str());
return -1;
} else {
email_tmpl[tmpl_name] = tmp_tmpl;
email_tmpl[tmpl_name] = tmp_tmpl;
}
}
}
@ -259,11 +261,12 @@ int AnswerMachineFactory::loadEmailTemplatesFromMySQL()
return 0;
}
#else
#else
int AnswerMachineFactory::loadEmailTemplates(const string& path)
{
std::string email_tmpl_file = add2path(path, 1,
string email_tmpl_file = add2path(path, 1,
(DEFAULT_MAIL_TMPL + "."
+ DEFAULT_MAIL_TMPL_EXT).c_str());
@ -320,6 +323,7 @@ int AnswerMachineFactory::onLoad()
AmConfigReader cfg;
if(cfg.loadFile(add2path(AmConfig::ModConfigPath,1, MOD_NAME ".conf")))
return -1;
// get application specific global parameters
configureModule(cfg);
@ -329,13 +333,13 @@ int AnswerMachineFactory::onLoad()
// smtp_port
if(cfg.hasParameter("smtp_port")){
if(sscanf(cfg.getParameter("smtp_port").c_str(),
"%u",&SmtpServerPort) != 1) {
"%u",&SmtpServerPort) != 1) {
ERROR("invalid smtp_port specified\n");
return -1;
}
}
DBG("SMTP server set to %s:%u\n",
DBG("SMTP server set to %s:%u\n",
SmtpServerAddress.c_str(), SmtpServerPort);
#ifdef USE_MYSQL
@ -369,14 +373,14 @@ int AnswerMachineFactory::onLoad()
try {
Connection.connect(mysql_db.c_str(), mysql_server.c_str(),
mysql_user.c_str(), mysql_passwd.c_str());
mysql_user.c_str(), mysql_passwd.c_str());
if (!Connection) {
ERROR("Database connection failed: %s\n", Connection.error());
return -1;
}
Connection.set_option(mysqlpp::Connection::opt_reconnect, true);
}
catch (const mysqlpp::Exception& er) {
// Catch-all for any MySQL++ exceptions
ERROR("MySQL++ error: %s\n", er.what());
@ -388,7 +392,7 @@ int AnswerMachineFactory::onLoad()
return -1;
}
#else
#else
/* Get email templates from file system */
@ -397,13 +401,12 @@ int AnswerMachineFactory::onLoad()
return -1;
}
AnnouncePath = cfg.getParameter("announce_path",ANNOUNCE_PATH);
DefaultAnnounce = cfg.getParameter("default_announce",DEFAULT_ANNOUNCE);
AnnouncePath = cfg.getParameter("announce_path");
DefaultAnnounce = cfg.getParameter("default_announce");
#endif
MaxRecordTime = cfg.getParameterInt("max_record_time",DEFAULT_RECORD_TIME);
MinRecordTime = cfg.getParameterInt("min_record_time",DEFAULT_MIN_RECORD_TIME);
RecFileExt = cfg.getParameter("rec_file_ext",DEFAULT_AUDIO_EXT);
UserTimer = AmPlugIn::instance()->getFactory4Di("user_timer");
@ -413,10 +416,21 @@ int AnswerMachineFactory::onLoad()
return -1;
}
EmailAddress = cfg.getParameter("email_address");
MessageStorage = NULL;
MessageStorage = AmPlugIn::instance()->getFactory4Di("msg_storage");
if(NULL == MessageStorage){
INFO("could not load msg_storage. Voice Box mode will not be available.\n");
}
DBG("Starting SMTP daemon\n");
AmMailDeamon::instance()->start();
string s_save_empty_msg = cfg.getParameter("box_save_empty_msg");
if (!s_save_empty_msg.empty()) {
SaveEmptyMsg = !(s_save_empty_msg == "no");
}
DBG("Voicebox will%s save empty messages.\n",
SaveEmptyMsg?"":" not");
return 0;
}
@ -425,67 +439,88 @@ AmSession* AnswerMachineFactory::onInvite(const AmSipRequest& req)
{
string language;
string email;
string domain;
string user;
string sender;
int vm_mode = MODE_VOICEMAIL;
string iptel_app_param = getHeader(req.hdrs, PARAM_HDR);
if (!EmailAddress.length()) {
if (iptel_app_param.length()) {
language = get_header_keyvalue(iptel_app_param,"Language");
email = get_header_keyvalue(iptel_app_param,"Email-Address");
} else {
email = getHeader(req.hdrs,"P-Email-Address");
language = getHeader(req.hdrs,"P-Language"); // local user's language
if (email.length()) {
INFO("Use of P-Email-Address/P-Language is deprecated. \n");
INFO("Use '%s: Email-Address=<addr>;"
"Language=<lang>' instead.\n",PARAM_HDR);
}
}
} else {
email = EmailAddress;
if (!iptel_app_param.length()) {
AmSession::Exception(500, "voicemail: parameters not found");
}
DBG("email address for user '%s': <%s> \n",
req.user.c_str(),email.c_str());
DBG("language: <%s> \n", language.c_str());
language = get_header_keyvalue(iptel_app_param,"Language");
email = get_header_keyvalue(iptel_app_param,"Email-Address");
string mode = get_header_keyvalue(iptel_app_param,"Mode");
if (!mode.empty()) {
if (mode == "box")
vm_mode = MODE_BOX;
else if (mode == "both")
vm_mode = MODE_BOTH;
}
user = get_header_keyvalue(iptel_app_param,"User");
sender = get_header_keyvalue(iptel_app_param,"Sender");
domain = get_header_keyvalue(iptel_app_param,"Domain");
// checks
if (user.empty())
throw AmSession::Exception(500, "voicemail: user missing");
if (sender.empty())
throw AmSession::Exception(500, "voicemail: sender missing");
if (((vm_mode == MODE_BOX) || (vm_mode == MODE_BOTH))
&& (NULL == MessageStorage)) {
throw AmSession::Exception(500, "voicemail: no message storage available");
}
DBG("voicemail invocation parameters: \n");
DBG(" Mode: <%s> \n", mode.c_str());
DBG(" User: <%s> \n", user.c_str());
DBG(" Sender: <%s> \n", sender.c_str());
DBG(" Domain: <%s> \n", domain.c_str());
DBG(" Language: <%s> \n", language.c_str());
#ifdef USE_MYSQL
string announce_file;
if (get_audio_file(GREETING_MSG, req.domain, req.user, "",
&announce_file) && !announce_file.empty())
if (get_audio_file(GREETING_MSG, domain, req.user, "",
&announce_file) && !announce_file.empty())
goto announce_found;
if (!language.empty()) {
if (get_audio_file(GREETING_MSG, req.domain, "", language,
&announce_file) && !announce_file.empty())
if (get_audio_file(GREETING_MSG, domain, "", language,
&announce_file) && !announce_file.empty())
goto announce_found;
} else {
if (get_audio_file(GREETING_MSG, req.domain, "", "",
&announce_file) && !announce_file.empty())
if (get_audio_file(GREETING_MSG, domain, "", "",
&announce_file) && !announce_file.empty())
goto announce_found;
}
if (!language.empty())
if (get_audio_file(GREETING_MSG, "", "", language,
&announce_file) && !announce_file.empty())
&announce_file) && !announce_file.empty())
goto announce_found;
get_audio_file(GREETING_MSG, "", "", "", &announce_file);
#else
string announce_file = add2path(AnnouncePath,2, req.domain.c_str(), (req.user + ".wav").c_str());
string announce_file = add2path(AnnouncePath,2, domain.c_str(), (user + ".wav").c_str());
if (file_exists(announce_file)) goto announce_found;
if (!language.empty()) {
announce_file = add2path(AnnouncePath,3, req.domain.c_str(), language.c_str(), DefaultAnnounce.c_str());
announce_file = add2path(AnnouncePath,3, domain.c_str(), language.c_str(), DefaultAnnounce.c_str());
if (file_exists(announce_file)) goto announce_found;
}
announce_file = add2path(AnnouncePath,2, req.domain.c_str(), DefaultAnnounce.c_str());
announce_file = add2path(AnnouncePath,2, domain.c_str(), DefaultAnnounce.c_str());
if (file_exists(announce_file)) goto announce_found;
if (!language.empty()) {
@ -502,15 +537,21 @@ AmSession* AnswerMachineFactory::onInvite(const AmSipRequest& req)
announce_found:
if(announce_file.empty())
throw AmSession::Exception(500,"voicemail: no greeting file found");
// VBOX mode does not need email template
if (vm_mode == MODE_BOX)
return new AnswerMachineDialog(user, sender, domain,
email, announce_file,
vm_mode, NULL);
if(email.empty())
throw AmSession::Exception(404,"missing email address");
std::map<string,EmailTemplate>::iterator tmpl_it;
map<string,EmailTemplate>::iterator tmpl_it;
if (!language.empty()) {
tmpl_it = email_tmpl.find(req.domain + "_" + language);
tmpl_it = email_tmpl.find(domain + "_" + language);
if(tmpl_it == email_tmpl.end()) {
tmpl_it = email_tmpl.find(req.domain);
tmpl_it = email_tmpl.find(domain);
if(tmpl_it == email_tmpl.end()) {
tmpl_it = email_tmpl.find(DEFAULT_MAIL_TMPL + "_"
+ language);
@ -519,34 +560,50 @@ AmSession* AnswerMachineFactory::onInvite(const AmSipRequest& req)
}
}
} else {
tmpl_it = email_tmpl.find(req.domain);
tmpl_it = email_tmpl.find(domain);
if(tmpl_it == email_tmpl.end())
tmpl_it = email_tmpl.find(DEFAULT_MAIL_TMPL);
}
if(tmpl_it == email_tmpl.end()){
ERROR("Voicemail: unable to find an email template.\n");
return 0;
}
return new AnswerMachineDialog(email,announce_file,
&tmpl_it->second);
return new AnswerMachineDialog(user, sender, domain,
email, announce_file,
vm_mode, &tmpl_it->second);
}
AnswerMachineDialog::AnswerMachineDialog(const string& email,
AnswerMachineDialog::AnswerMachineDialog(const string& user,
const string& sender,
const string& domain,
const string& email,
const string& announce_file,
int vm_mode,
const EmailTemplate* tmpl)
: announce_file(announce_file),
tmpl(tmpl), playlist(this),
status(0)
status(0), vm_mode(vm_mode)
{
email_dict["user"] = user;
email_dict["sender"] = sender;
email_dict["domain"] = domain;
email_dict["email"] = email;
user_timer = AnswerMachineFactory::UserTimer->getInstance();
if(!user_timer){
ERROR("could not get a user timer reference\n");
throw AmSession::Exception(500,"could not get a user timer reference");
}
if (vm_mode == MODE_BOTH || vm_mode == MODE_BOX) {
msg_storage = AnswerMachineFactory::MessageStorage->getInstance();
if(!msg_storage){
ERROR("could not get a message storage reference\n");
throw AmSession::Exception(500,"could not get a message storage reference");
}
}
}
AnswerMachineDialog::~AnswerMachineDialog()
@ -569,11 +626,11 @@ void AnswerMachineDialog::process(AmEvent* event)
playlist.addToPlaylist(new AmPlaylistItem(NULL,&a_msg));
{AmArg di_args,ret;
di_args.push(RECORD_TIMER);
di_args.push(AnswerMachineFactory::MaxRecordTime);
di_args.push(getLocalTag().c_str());
di_args.push(RECORD_TIMER);
di_args.push(AnswerMachineFactory::MaxRecordTime);
di_args.push(getLocalTag().c_str());
user_timer->invoke("setTimer",di_args,ret);}
user_timer->invoke("setTimer",di_args,ret);}
status = 1;
break;
@ -585,7 +642,7 @@ void AnswerMachineDialog::process(AmEvent* event)
case 2:
dlg.bye();
sendMailNotification();
saveMessage();
setStopped();
break;
@ -649,38 +706,77 @@ void AnswerMachineDialog::onSessionStart(const AmSipRequest& req)
setInOut(&playlist,&playlist);
request2dict(req);
char now[15];
sprintf(now, "%d", (int) time(NULL));
email_dict["ts"] = now;
}
void AnswerMachineDialog::onBye(const AmSipRequest& req)
{
sendMailNotification();
setInOut(NULL, NULL);
saveMessage();
setStopped();
}
void AnswerMachineDialog::sendMailNotification()
void AnswerMachineDialog::saveMessage()
{
int rec_length = a_msg.getLength();
DBG("recorded file length: %i ms\n",rec_length);
if(rec_length <= AnswerMachineFactory::MinRecordTime){
DBG("recorded file too small. Not sending voicemail.\n");
char buf[1024];
unsigned int rec_size = a_msg.getDataSize();
DBG("recorded data size: %i\n",rec_size);
if(!rec_size){
unlink(msg_filename.c_str());
}
else {
// record in box empty messages as well
if (AnswerMachineFactory::SaveEmptyMsg &&
((vm_mode == MODE_BOX) ||
(vm_mode == MODE_BOTH))) {
saveBox(NULL);
}
} else {
try {
// avoid tmp file to be closed
// ~AmMail will do that...
a_msg.setCloseOnDestroy(false);
a_msg.on_close();
AmMail* mail = new AmMail(tmpl->getEmail(email_dict));
mail->attachements.push_back(Attachement(a_msg.getfp(),
"message."
+ AnswerMachineFactory::RecFileExt,
a_msg.getMimeType()));
//mail->clean_up = clean_up_mail;
AmMailDeamon::instance()->sendQueued(mail);
// copy to tmpfile for box msg
if ((vm_mode == MODE_BOTH) ||
(vm_mode == MODE_BOX)) {
DBG("will save to box...\n");
FILE* m_fp = a_msg.getfp();
if (vm_mode == MODE_BOTH) {
// copy file to new tmpfile
m_fp = tmpfile();
if(!m_fp){
ERROR("could not create temporary file: %s\n",
strerror(errno));
} else {
FILE* fp = a_msg.getfp();
rewind(fp);
size_t nread;
while (!feof(fp)) {
nread = fread(buf, 1, 1024, fp);
if (fwrite(buf, 1, nread, m_fp) != nread)
break;
}
}
}
saveBox(m_fp);
}
if ((vm_mode == MODE_BOTH) ||
(vm_mode == MODE_VOICEMAIL)) {
// send mail
AmMail* mail = new AmMail(tmpl->getEmail(email_dict));
mail->attachements.push_back(Attachement(a_msg.getfp(),
"message."
+ AnswerMachineFactory::RecFileExt,
a_msg.getMimeType()));
AmMailDeamon::instance()->sendQueued(mail);
}
}
catch(const string& err){
ERROR("while creating email: %s\n",err.c_str());
@ -688,31 +784,21 @@ void AnswerMachineDialog::sendMailNotification()
}
}
void AnswerMachineDialog::clean_up_mail(AmMail* mail)
{
// for( Attachements::const_iterator att_it = mail->attachements.begin();
// att_it != mail->attachements.end(); ++att_it )
// unlink(att_it->fullname.c_str());
void AnswerMachineDialog::saveBox(FILE* fp) {
string msg_name = email_dict["ts"] + MSG_SEPARATOR +
email_dict["sender"] + ".wav";
DBG("message name is '%s'\n", msg_name.c_str());
AmArg di_args,ret;
di_args.push(email_dict["domain"].c_str()); // domain
di_args.push(email_dict["user"].c_str()); // user
di_args.push(msg_name.c_str()); // message name
AmArg df;
MessageDataFile df_arg(fp);
df.setBorrowedPointer(&df_arg);
di_args.push(df);
msg_storage->invoke("msg_new",di_args,ret);
// TODO: evaluate ret return value
fclose(fp);
}
void AnswerMachineDialog::request2dict(const AmSipRequest& req)
{
email_dict["user"] = req.user;
email_dict["domain"] = req.domain;
email_dict["from"] = req.from;
email_dict["to"] = req.to;
string::size_type pos1 = req.from.rfind("<sip:");
string::size_type pos2 = req.from.find("@",pos1);
if(pos1 != string::npos && pos2 != string::npos) {
email_dict["from_user"] = req.from.substr(pos1+5,pos2-pos1-5);
pos1 = pos2;
pos2 = req.from.find(">",pos1);
if(pos1 != string::npos && pos2 != string::npos)
email_dict["from_domain"] = req.from.substr(pos1+1,pos2-pos1-1);
}
}

@ -33,13 +33,16 @@
#endif
#include "AmSession.h"
#include "AmAudioFile.h"
#include "AmConfigReader.h"
#include "EmailTemplate.h"
#include "AmMail.h"
#include "AmPlaylist.h"
#include "AmAudioFile.h"
#include <string>
#include <map>
using std::string;
using std::map;
// defaults for config options
#define SMTP_ADDRESS_IP "localhost"
@ -47,79 +50,88 @@ using std::string;
class AmMail;
/** \brief Factory for voicemail sessions */
#define MODE_VOICEMAIL 0
#define MODE_BOX 1
#define MODE_BOTH 2
#define MSG_SEPARATOR "+" // separates values in message name
class AnswerMachineFactory: public AmSessionFactory
{
std::map<string, EmailTemplate> email_tmpl;
map<string, EmailTemplate> email_tmpl;
int getEmailAddress();
int getEmailAddress();
#ifdef USE_MYSQL
int loadEmailTemplatesFromMySQL();
int loadEmailTemplatesFromMySQL();
#else
int loadEmailTemplates(const string& path);
int loadEmailTemplates(const string& path);
#endif
public:
static string EmailAddress;
static string RecFileExt;
static string AnnouncePath;
static string DefaultAnnounce;
static int MaxRecordTime;
static int MinRecordTime;
static AmDynInvokeFactory* UserTimer;
/** After server start, IP of the SMTP server. */
static string SmtpServerAddress;
/** SMTP server port. */
static unsigned int SmtpServerPort;
static string RecFileExt;
static string AnnouncePath;
static string DefaultAnnounce;
static int MaxRecordTime;
static AmDynInvokeFactory* UserTimer;
static AmDynInvokeFactory* MessageStorage;
static bool SaveEmptyMsg;
/** After server start, IP of the SMTP server. */
static string SmtpServerAddress;
/** SMTP server port. */
static unsigned int SmtpServerPort;
#ifdef USE_MYSQL
static mysqlpp::Connection Connection;
static mysqlpp::Connection Connection;
#endif
AnswerMachineFactory(const string& _app_name);
AnswerMachineFactory(const string& _app_name);
int onLoad();
AmSession* onInvite(const AmSipRequest& req);
int onLoad();
AmSession* onInvite(const AmSipRequest& req);
};
/** \brief implementation of voicemail session logic */
class AnswerMachineDialog : public AmSession
{
AmAudioFile a_greeting,a_beep;
AmAudioFile a_msg;
AmPlaylist playlist;
AmAudioFile a_greeting,a_beep;
AmAudioFile a_msg;
AmPlaylist playlist;
string announce_file;
string msg_filename;
string announce_file;
string msg_filename;
const EmailTemplate* tmpl;
EmailTmplDict email_dict;
const EmailTemplate* tmpl;
EmailTmplDict email_dict;
AmDynInvoke* user_timer;
AmDynInvoke* user_timer;
AmDynInvoke* msg_storage;
int status;
int status;
int vm_mode; // MODE_*
void request2dict(const AmSipRequest& req);
void sendMailNotification();
//void sendMailNotification();
void saveMessage();
void saveBox(FILE* fp);
public:
AnswerMachineDialog(const string& email,
public:
AnswerMachineDialog(const string& user,
const string& sender,
const string& domain,
const string& email,
const string& announce_file,
int vm_mode,
const EmailTemplate* tmpl);
~AnswerMachineDialog();
void process(AmEvent* event);
~AnswerMachineDialog();
void onSessionStart(const AmSipRequest& req);
void onBye(const AmSipRequest& req);
void onDtmf(int event, int duration_msec) {}
void process(AmEvent* event);
static void clean_up_mail(AmMail* mail);
void onSessionStart(const AmSipRequest& req);
void onBye(const AmSipRequest& req);
void onDtmf(int event, int duration_msec) {}
friend class AnswerMachineFactory;
friend class AnswerMachineFactory;
};
#endif

Loading…
Cancel
Save