mirror of https://github.com/sipwise/sems.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
512 lines
14 KiB
512 lines
14 KiB
/*
|
|
* Copyright (C) 2008 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 "AnnRecorder.h"
|
|
#include "AmConfig.h"
|
|
#include "AmUtils.h"
|
|
#include "AmPlugIn.h"
|
|
#include "AmPromptCollection.h"
|
|
#include "AmUriParser.h"
|
|
#include "../msg_storage/MsgStorageAPI.h"
|
|
#include "sems.h"
|
|
#include "log.h"
|
|
|
|
using std::map;
|
|
|
|
#define MOD_NAME "annrecorder"
|
|
|
|
#define DEFAULT_TYPE "vm"
|
|
#define DOMAIN_PROMPT_SUFFIX "-prompts"
|
|
|
|
#define TIMERID_START_TIMER 1
|
|
#define TIMERID_CONFIRM_TIMER 2
|
|
|
|
#define START_RECORDING_TIMEOUT 20
|
|
#define CONFIRM_RECORDING_TIMEOUT 20
|
|
|
|
#define SEP_CONFIRM_BEGIN 1
|
|
#define SEP_MSG_BEGIN 2
|
|
|
|
#define MAX_MESSAGE_TIME 120
|
|
|
|
EXPORT_SESSION_FACTORY(AnnRecorderFactory,MOD_NAME);
|
|
|
|
string AnnRecorderFactory::AnnouncePath;
|
|
string AnnRecorderFactory::DefaultAnnounce;
|
|
bool AnnRecorderFactory::SimpleMode=false;
|
|
AmDynInvokeFactory* AnnRecorderFactory::message_storage_fact = NULL;
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
AnnRecorderFactory::AnnRecorderFactory(const string& _app_name)
|
|
: AmSessionFactory(_app_name)
|
|
{
|
|
}
|
|
|
|
int AnnRecorderFactory::onLoad()
|
|
{
|
|
AmConfigReader cfg;
|
|
if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf")))
|
|
return -1;
|
|
|
|
// get application specific global parameters
|
|
configureModule(cfg);
|
|
|
|
AnnouncePath = cfg.getParameter("announce_path",ANNOUNCE_PATH);
|
|
if( !AnnouncePath.empty()
|
|
&& AnnouncePath[AnnouncePath.length()-1] != '/' )
|
|
AnnouncePath += "/";
|
|
DefaultAnnounce = cfg.getParameter("default_announce");
|
|
|
|
SimpleMode = (cfg.getParameter("simple_mode") == "yes");
|
|
|
|
AM_PROMPT_START;
|
|
AM_PROMPT_ADD(WELCOME, ANNREC_ANNOUNCE_PATH WELCOME".wav");
|
|
AM_PROMPT_ADD(YOUR_PROMPT, ANNREC_ANNOUNCE_PATH YOUR_PROMPT".wav");
|
|
AM_PROMPT_ADD(TO_RECORD, ANNREC_ANNOUNCE_PATH TO_RECORD".wav");
|
|
AM_PROMPT_ADD(CONFIRM, ANNREC_ANNOUNCE_PATH CONFIRM".wav");
|
|
AM_PROMPT_ADD(GREETING_SET, ANNREC_ANNOUNCE_PATH GREETING_SET".wav");
|
|
AM_PROMPT_ADD(BYE, ANNREC_ANNOUNCE_PATH BYE".wav");
|
|
AM_PROMPT_ADD(BEEP, ANNREC_ANNOUNCE_PATH BEEP".wav");
|
|
AM_PROMPT_END(prompts, cfg, MOD_NAME);
|
|
|
|
message_storage_fact = AmPlugIn::instance()->getFactory4Di("msg_storage");
|
|
if(!message_storage_fact) {
|
|
ERROR("sorry, could not get msg_storage, please load a suitable plug-in\n");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void AnnRecorderFactory::getAppParams(const AmSipRequest& req, map<string, string>& params) {
|
|
string language;
|
|
string domain;
|
|
string user;
|
|
string typ;
|
|
|
|
if (SimpleMode){
|
|
AmUriParser p;
|
|
p.uri = req.from_uri;
|
|
if (!p.parse_uri()) {
|
|
DBG("parsing From-URI '%s' failed\n", p.uri.c_str());
|
|
throw AmSession::Exception(500, MOD_NAME ": could not parse From-URI");
|
|
}
|
|
user = p.uri_user;
|
|
//domain = p.uri_domain;
|
|
domain = "default";
|
|
typ = DEFAULT_TYPE;
|
|
}
|
|
else {
|
|
string iptel_app_param = getHeader(req.hdrs, PARAM_HDR, true);
|
|
|
|
if (!iptel_app_param.length()) {
|
|
throw AmSession::Exception(500, MOD_NAME ": parameters not found");
|
|
}
|
|
|
|
language = get_header_keyvalue(iptel_app_param, "lng", "Language");
|
|
|
|
user = get_header_keyvalue(iptel_app_param,"uid", "UserID");
|
|
if (user.empty()) {
|
|
user = get_header_keyvalue(iptel_app_param,"usr", "User");
|
|
if (!user.length())
|
|
user = req.user;
|
|
}
|
|
|
|
domain = get_header_keyvalue(iptel_app_param, "did", "DomainID");
|
|
if (domain.empty()){
|
|
domain = get_header_keyvalue(iptel_app_param, "dom", "Domain");
|
|
if (domain.empty())
|
|
domain = req.domain;
|
|
}
|
|
|
|
typ = get_header_keyvalue(iptel_app_param, "typ", "Type");
|
|
if (!typ.length())
|
|
typ = DEFAULT_TYPE;
|
|
}
|
|
|
|
// checks
|
|
if (user.empty())
|
|
throw AmSession::Exception(500, MOD_NAME ": user missing");
|
|
|
|
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, domain.c_str(), language.c_str(), DefaultAnnounce.c_str());
|
|
if (file_exists(announce_file)) goto announce_found;
|
|
}
|
|
|
|
announce_file = add2path(AnnouncePath,2, domain.c_str(), DefaultAnnounce.c_str());
|
|
if (file_exists(announce_file)) goto announce_found;
|
|
|
|
if (!language.empty()) {
|
|
announce_file = add2path(AnnouncePath,2, language.c_str(), DefaultAnnounce.c_str());
|
|
if (file_exists(announce_file)) goto announce_found;
|
|
}
|
|
|
|
announce_file = add2path(AnnouncePath,1, DefaultAnnounce.c_str());
|
|
if (!file_exists(announce_file))
|
|
announce_file = "";
|
|
|
|
announce_found:
|
|
|
|
DBG(MOD_NAME " invocation parameters: \n");
|
|
DBG(" User: <%s> \n", user.c_str());
|
|
DBG(" Domain: <%s> \n", domain.c_str());
|
|
DBG(" Language: <%s> \n", language.c_str());
|
|
DBG(" Type: <%s> \n", typ.c_str());
|
|
DBG(" Def. File:<%s> \n", announce_file.c_str());
|
|
|
|
params["domain"] = domain;
|
|
params["user"] = user;
|
|
params["defaultfile"] = announce_file;
|
|
params["type"] = typ;
|
|
}
|
|
|
|
AmSession* AnnRecorderFactory::onInvite(const AmSipRequest& req, const string& app_name,
|
|
const map<string,string>& app_params)
|
|
{
|
|
map<string, string> params;
|
|
getAppParams(req, params);
|
|
return new AnnRecorderDialog(params, prompts, NULL);
|
|
}
|
|
|
|
AmSession* AnnRecorderFactory::onInvite(const AmSipRequest& req, const string& app_name,
|
|
const AmArg& session_params)
|
|
{
|
|
UACAuthCred* cred = AmUACAuth::unpackCredentials(session_params);
|
|
|
|
map<string, string> params;
|
|
getAppParams(req, params);
|
|
AmSession* s = new AnnRecorderDialog(params, prompts, cred);
|
|
|
|
if (NULL == cred) {
|
|
WARN("discarding unknown session parameters.\n");
|
|
} else {
|
|
AmUACAuth::enable(s);
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
AnnRecorderDialog::AnnRecorderDialog(const map<string, string>& params,
|
|
AmPromptCollection& prompts,
|
|
UACAuthCred* credentials)
|
|
: params(params),
|
|
prompts(prompts), cred(credentials),
|
|
playlist(this)
|
|
{
|
|
msg_storage = AnnRecorderFactory::message_storage_fact->getInstance();
|
|
if(!msg_storage){
|
|
ERROR("could not get a message storage reference\n");
|
|
throw AmSession::Exception(500,"could not get a message storage reference");
|
|
}
|
|
}
|
|
|
|
AnnRecorderDialog::~AnnRecorderDialog()
|
|
{
|
|
prompts.cleanup((long)this);
|
|
if (msg_filename.length())
|
|
unlink(msg_filename.c_str());
|
|
}
|
|
|
|
void AnnRecorderDialog::onSessionStart()
|
|
{
|
|
DBG("AnnRecorderDialog::onSessionStart\n");
|
|
|
|
prompts.addToPlaylist(WELCOME, (long)this, playlist);
|
|
prompts.addToPlaylist(YOUR_PROMPT, (long)this, playlist);
|
|
enqueueCurrent();
|
|
prompts.addToPlaylist(TO_RECORD, (long)this, playlist);
|
|
enqueueSeparator(SEP_MSG_BEGIN);
|
|
|
|
// set the playlist as input and output
|
|
setInOut(&playlist,&playlist);
|
|
state = S_WAIT_START;
|
|
|
|
AmSession::onSessionStart();
|
|
}
|
|
|
|
void AnnRecorderDialog::enqueueCurrent() {
|
|
wav_file.close();
|
|
FILE* fp = getCurrentMessage();
|
|
if (!fp) {
|
|
DBG("no recorded msg available, using default\n");
|
|
if (wav_file.open(params["defaultfile"], AmAudioFile::Read)) {
|
|
ERROR("opening default greeting file '%s'!\n", params["defaultfile"].c_str());
|
|
return;
|
|
}
|
|
} else {
|
|
if (wav_file.fpopen("aa.wav", AmAudioFile::Read, fp)) {
|
|
ERROR("fpopen message file!\n");
|
|
return;
|
|
}
|
|
}
|
|
playlist.addToPlaylist(new AmPlaylistItem(&wav_file, NULL));
|
|
}
|
|
|
|
void AnnRecorderDialog::onDtmf(int event, int duration_msec) {
|
|
DBG("DTMF %d, %d\n", event, duration_msec);
|
|
// remove timer
|
|
try {
|
|
removeTimers();
|
|
} catch(...) {
|
|
ERROR("Exception caught calling mod api\n");
|
|
}
|
|
|
|
switch (state) {
|
|
case S_WAIT_START: {
|
|
DBG("received key %d in state S_WAIT_START: start recording\n", event);
|
|
playlist.flush();
|
|
|
|
wav_file.close();
|
|
msg_filename = "/tmp/" + getLocalTag() + ".wav";
|
|
if(wav_file.open(msg_filename,AmAudioFile::Write,false)) {
|
|
ERROR("AnnRecorder: couldn't open %s for writing\n",
|
|
msg_filename.c_str());
|
|
dlg->bye();
|
|
setStopped();
|
|
}
|
|
wav_file.setRecordTime(MAX_MESSAGE_TIME*1000);
|
|
prompts.addToPlaylist(BEEP, (long)this, playlist);
|
|
playlist.addToPlaylist(new AmPlaylistItem(NULL,&wav_file));
|
|
|
|
state = S_RECORDING;
|
|
} break;
|
|
|
|
case S_RECORDING: {
|
|
DBG("received key %d in state S_RECORDING: replay recording\n", event);
|
|
prompts.addToPlaylist(BEEP, (long)this, playlist);
|
|
playlist.flush();
|
|
replayRecording();
|
|
|
|
} break;
|
|
|
|
case S_CONFIRM: {
|
|
DBG("received key %d in state S_CONFIRM save or redo\n", event);
|
|
playlist.flush();
|
|
|
|
wav_file.close();
|
|
if (event == 1) {
|
|
// save msg
|
|
saveAndConfirm();
|
|
} else {
|
|
prompts.addToPlaylist(TO_RECORD, (long)this, playlist);
|
|
state = S_WAIT_START;
|
|
}
|
|
} break;
|
|
|
|
default: {
|
|
DBG("ignoring key %d in state %d\n",
|
|
event, state);
|
|
}break;
|
|
}
|
|
|
|
}
|
|
|
|
void AnnRecorderDialog::saveAndConfirm() {
|
|
// wav_file.setCloseOnDestroy(false);
|
|
wav_file.close();
|
|
FILE* fp = fopen(msg_filename.c_str(), "r");
|
|
if (fp) {
|
|
saveMessage(fp);
|
|
prompts.addToPlaylist(GREETING_SET, (long)this, playlist);
|
|
}
|
|
prompts.addToPlaylist(BYE, (long)this, playlist);
|
|
state = S_BYE;
|
|
}
|
|
|
|
void AnnRecorderDialog::onBye(const AmSipRequest& req)
|
|
{
|
|
DBG("onBye: stopSession\n");
|
|
setStopped();
|
|
}
|
|
|
|
void AnnRecorderDialog::process(AmEvent* event)
|
|
{
|
|
|
|
AmPluginEvent* plugin_event = dynamic_cast<AmPluginEvent*>(event);
|
|
if(plugin_event && plugin_event->name == "timer_timeout") {
|
|
event->processed = true;
|
|
int timer_id = plugin_event->data.get(0).asInt();
|
|
if (timer_id == TIMERID_START_TIMER) {
|
|
if (S_WAIT_START == state) {
|
|
prompts.addToPlaylist(BYE, (long)this, playlist);
|
|
state = S_BYE;
|
|
}
|
|
return;
|
|
}
|
|
if (timer_id == TIMERID_CONFIRM_TIMER) {
|
|
saveAndConfirm();
|
|
return;
|
|
}
|
|
ERROR("unknown timer id!\n");
|
|
}
|
|
|
|
AmAudioEvent* audio_event = dynamic_cast<AmAudioEvent*>(event);
|
|
if(audio_event && (audio_event->event_id == AmAudioEvent::noAudio)){
|
|
|
|
if (S_BYE == state) {
|
|
dlg->bye();
|
|
setStopped();
|
|
return;
|
|
}
|
|
|
|
if (S_RECORDING == state) {
|
|
replayRecording();
|
|
}
|
|
}
|
|
|
|
AmPlaylistSeparatorEvent* pl_ev = dynamic_cast<AmPlaylistSeparatorEvent*>(event);
|
|
if (pl_ev) {
|
|
if ((pl_ev->event_id == SEP_MSG_BEGIN) &&
|
|
(S_WAIT_START == state)) {
|
|
// start timer
|
|
setTimer(TIMERID_START_TIMER, START_RECORDING_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
if ((pl_ev->event_id == SEP_CONFIRM_BEGIN) &&
|
|
(S_CONFIRM == state)) {
|
|
// start timer
|
|
setTimer(TIMERID_CONFIRM_TIMER, CONFIRM_RECORDING_TIMEOUT);
|
|
return;
|
|
}
|
|
return;
|
|
}
|
|
AmSession::process(event);
|
|
}
|
|
|
|
void AnnRecorderDialog::replayRecording() {
|
|
prompts.addToPlaylist(YOUR_PROMPT, (long)this, playlist);
|
|
DBG("msg_filename = '%s'\n", msg_filename.c_str());
|
|
if (!wav_file.open(msg_filename, AmAudioFile::Read))
|
|
playlist.addToPlaylist(new AmPlaylistItem(&wav_file, NULL));
|
|
prompts.addToPlaylist(CONFIRM, (long)this, playlist);
|
|
enqueueSeparator(SEP_CONFIRM_BEGIN);
|
|
state = S_CONFIRM;
|
|
}
|
|
|
|
inline UACAuthCred* AnnRecorderDialog::getCredentials() {
|
|
return cred.get();
|
|
}
|
|
|
|
void AnnRecorderDialog::saveMessage(FILE* fp) {
|
|
string msg_name = params["type"]+".wav";
|
|
DBG("message name is '%s'\n", msg_name.c_str());
|
|
|
|
AmArg di_args,ret;
|
|
di_args.push((params["domain"]+DOMAIN_PROMPT_SUFFIX).c_str()); // domain
|
|
di_args.push(params["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);
|
|
try {
|
|
msg_storage->invoke("msg_new",di_args,ret);
|
|
} catch(string& s) {
|
|
ERROR("invoking msg_new: '%s'\n", s.c_str());
|
|
} catch(...) {
|
|
ERROR("invoking msg_new.\n");
|
|
}
|
|
// TODO: evaluate ret return value
|
|
}
|
|
|
|
FILE* AnnRecorderDialog::getCurrentMessage() {
|
|
string msgname = params["type"]+".wav";
|
|
string& user = params["user"];
|
|
string domain = params["domain"]+DOMAIN_PROMPT_SUFFIX;
|
|
|
|
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) {
|
|
DBG("msg_get for user '%s' domain '%s' message '%s': %s\n",
|
|
user.c_str(), domain.c_str(),
|
|
msgname.c_str(),
|
|
MsgStrError(ret.get(0).asInt()));
|
|
|
|
if ((ret.size() > 1) && isArgAObject(ret.get(1))) {
|
|
MessageDataFile* f =
|
|
dynamic_cast<MessageDataFile*>(ret.get(1).asObject());
|
|
if (NULL != f)
|
|
delete f;
|
|
}
|
|
|
|
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 AnnRecorderDialog::enqueueSeparator(int id) {
|
|
playlist_separator.reset(new AmPlaylistSeparator(this, id));
|
|
playlist.addToPlaylist(new AmPlaylistItem(playlist_separator.get(), NULL));
|
|
}
|