diff --git a/apps/msg_storage/Makefile b/apps/msg_storage/Makefile new file mode 100644 index 00000000..7971e172 --- /dev/null +++ b/apps/msg_storage/Makefile @@ -0,0 +1,7 @@ +plug_in_name = msg_storage + +module_ldflags = +module_cflags = + +COREPATH ?=../../core +include $(COREPATH)/plug-in/Makefile.app_module diff --git a/apps/msg_storage/MsgStorage.cpp b/apps/msg_storage/MsgStorage.cpp new file mode 100644 index 00000000..022ade81 --- /dev/null +++ b/apps/msg_storage/MsgStorage.cpp @@ -0,0 +1,221 @@ + +#include "AmPlugIn.h" +#include "log.h" + +#include "MsgStorageAPI.h" +#include "MsgStorage.h" + +#include +#include +#include +#include +#include +#include + +#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(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; + } +} + + + + + diff --git a/apps/msg_storage/MsgStorage.h b/apps/msg_storage/MsgStorage.h new file mode 100644 index 00000000..8e773d3d --- /dev/null +++ b/apps/msg_storage/MsgStorage.h @@ -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 diff --git a/apps/msg_storage/MsgStorageAPI.h b/apps/msg_storage/MsgStorageAPI.h new file mode 100644 index 00000000..e62f3159 --- /dev/null +++ b/apps/msg_storage/MsgStorageAPI.h @@ -0,0 +1,25 @@ +#ifndef _MSG_STORAGE_API_H +#define _MSG_STORAGE_API_H + +#include + +#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 diff --git a/apps/voicebox/Makefile b/apps/voicebox/Makefile new file mode 100644 index 00000000..26542d2f --- /dev/null +++ b/apps/voicebox/Makefile @@ -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 diff --git a/apps/voicebox/PromptOptions.h b/apps/voicebox/PromptOptions.h new file mode 100644 index 00000000..e935c4a2 --- /dev/null +++ b/apps/voicebox/PromptOptions.h @@ -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 diff --git a/apps/voicebox/Voicebox.cpp b/apps/voicebox/Voicebox.cpp new file mode 100644 index 00000000..a3b4ca59 --- /dev/null +++ b/apps/voicebox/Voicebox.cpp @@ -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 + +#include +#include +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 >::iterator d_it = + prompts.find(domain); + if (d_it != prompts.end()) { + map::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 >::iterator d_it = + prompt_options.find(domain); + if (d_it != prompt_options.end()) { + map::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 domains = explode(cfg.getParameter("domains"), ";"); + domains.push_back(""); // add default (empty) domain + vector 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::iterator dom = domains.begin(); + dom != domains.end(); dom++) { + for (vector::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); +} + diff --git a/apps/voicebox/Voicebox.h b/apps/voicebox/Voicebox.h new file mode 100644 index 00000000..3ba64544 --- /dev/null +++ b/apps/voicebox/Voicebox.h @@ -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 +#include +#include +using std::map; +using std::string; +using std::list; + +class VoiceboxFactory + : public AmSessionFactory +{ + map > prompts; + map > 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: + diff --git a/apps/voicebox/VoiceboxDialog.cpp b/apps/voicebox/VoiceboxDialog.cpp new file mode 100644 index 00000000..8096a493 --- /dev/null +++ b/apps/voicebox/VoiceboxDialog.cpp @@ -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(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(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;iinvoke("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(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(); +} diff --git a/apps/voicebox/VoiceboxDialog.h b/apps/voicebox/VoiceboxDialog.h new file mode 100644 index 00000000..c72adb7c --- /dev/null +++ b/apps/voicebox/VoiceboxDialog.h @@ -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 +#include +#include +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 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 new_msgs; + list saved_msgs; + + // list of the messages that come be in the msg list the next round + list edited_msgs; + + bool userdir_open; // have we opened the user dir? + bool do_save_cur_msg; // saving of current message possible? + + list::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: + diff --git a/apps/voicebox/etc/voicebox.conf b/apps/voicebox/etc/voicebox.conf new file mode 100644 index 00000000..2730242f --- /dev/null +++ b/apps/voicebox/etc/voicebox.conf @@ -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 + diff --git a/apps/voicebox/test/ser_main.cfg b/apps/voicebox/test/ser_main.cfg new file mode 100644 index 00000000..db4d99dc --- /dev/null +++ b/apps/voicebox/test/ser_main.cfg @@ -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; +} diff --git a/apps/voicebox/wav/and.wav b/apps/voicebox/wav/and.wav new file mode 100644 index 00000000..bd0e2eb6 Binary files /dev/null and b/apps/voicebox/wav/and.wav differ diff --git a/apps/voicebox/wav/bye.wav b/apps/voicebox/wav/bye.wav new file mode 100644 index 00000000..ed5758e5 Binary files /dev/null and b/apps/voicebox/wav/bye.wav differ diff --git a/apps/voicebox/wav/first_new_msg.wav b/apps/voicebox/wav/first_new_msg.wav new file mode 100644 index 00000000..ca73b5f4 Binary files /dev/null and b/apps/voicebox/wav/first_new_msg.wav differ diff --git a/apps/voicebox/wav/first_saved_msg.wav b/apps/voicebox/wav/first_saved_msg.wav new file mode 100644 index 00000000..9ec2b8c6 Binary files /dev/null and b/apps/voicebox/wav/first_saved_msg.wav differ diff --git a/apps/voicebox/wav/in_your_voicebox.wav b/apps/voicebox/wav/in_your_voicebox.wav new file mode 100644 index 00000000..d4bc5753 Binary files /dev/null and b/apps/voicebox/wav/in_your_voicebox.wav differ diff --git a/apps/voicebox/wav/msg_deleted.wav b/apps/voicebox/wav/msg_deleted.wav new file mode 100644 index 00000000..b0c57adb Binary files /dev/null and b/apps/voicebox/wav/msg_deleted.wav differ diff --git a/apps/voicebox/wav/msg_menu.wav b/apps/voicebox/wav/msg_menu.wav new file mode 100644 index 00000000..d64244cc Binary files /dev/null and b/apps/voicebox/wav/msg_menu.wav differ diff --git a/apps/voicebox/wav/msg_saved.wav b/apps/voicebox/wav/msg_saved.wav new file mode 100644 index 00000000..c1bac5fa Binary files /dev/null and b/apps/voicebox/wav/msg_saved.wav differ diff --git a/apps/voicebox/wav/new_msgs.wav b/apps/voicebox/wav/new_msgs.wav new file mode 100644 index 00000000..6d60d6af Binary files /dev/null and b/apps/voicebox/wav/new_msgs.wav differ diff --git a/apps/voicebox/wav/next_new_msg.wav b/apps/voicebox/wav/next_new_msg.wav new file mode 100644 index 00000000..c45e3be3 Binary files /dev/null and b/apps/voicebox/wav/next_new_msg.wav differ diff --git a/apps/voicebox/wav/next_saved_msg.wav b/apps/voicebox/wav/next_saved_msg.wav new file mode 100644 index 00000000..15fc13a7 Binary files /dev/null and b/apps/voicebox/wav/next_saved_msg.wav differ diff --git a/apps/voicebox/wav/no_more_msg.wav b/apps/voicebox/wav/no_more_msg.wav new file mode 100644 index 00000000..4274200c Binary files /dev/null and b/apps/voicebox/wav/no_more_msg.wav differ diff --git a/apps/voicebox/wav/no_msg.wav b/apps/voicebox/wav/no_msg.wav new file mode 100644 index 00000000..b949f453 Binary files /dev/null and b/apps/voicebox/wav/no_msg.wav differ diff --git a/apps/voicebox/wav/pin_prompt.wav b/apps/voicebox/wav/pin_prompt.wav new file mode 100644 index 00000000..fe1c6a67 Binary files /dev/null and b/apps/voicebox/wav/pin_prompt.wav differ diff --git a/apps/voicebox/wav/saved_msgs.wav b/apps/voicebox/wav/saved_msgs.wav new file mode 100644 index 00000000..c4f84b17 Binary files /dev/null and b/apps/voicebox/wav/saved_msgs.wav differ diff --git a/apps/voicebox/wav/you_have.wav b/apps/voicebox/wav/you_have.wav new file mode 100644 index 00000000..16f0bdec Binary files /dev/null and b/apps/voicebox/wav/you_have.wav differ diff --git a/apps/voicemail/AnswerMachine.cpp b/apps/voicemail/AnswerMachine.cpp index 682a6c9d..e41e6e90 100644 --- a/apps/voicemail/AnswerMachine.cpp +++ b/apps/voicemail/AnswerMachine.cpp @@ -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 #include +#include + #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=;" - "Language=' 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::iterator tmpl_it; + map::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("",pos1); - if(pos1 != string::npos && pos2 != string::npos) - email_dict["from_domain"] = req.from.substr(pos1+1,pos2-pos1-1); - } -} diff --git a/apps/voicemail/AnswerMachine.h b/apps/voicemail/AnswerMachine.h index 5ef4e7ae..c59d168b 100644 --- a/apps/voicemail/AnswerMachine.h +++ b/apps/voicemail/AnswerMachine.h @@ -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 +#include 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 email_tmpl; + map 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