mirror of https://github.com/sipwise/sems.git
git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@772 8eb893ce-cfd4-0310-b710-fb5ebe64c474sayer/1.4-spce2.6
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue