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.
sems/apps/voicebox/VoiceboxDialog.cpp

579 lines
14 KiB

/*
* 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.flush();
prompts->cleanup((long)this);
}
void VoiceboxDialog::onSessionStart() {
if (pin.empty()) {
state = Prompting;
doMailboxStart();
} else {
state = EnteringPin;
enqueueFront("pin_prompt");
}
// set the playlist as input and output
setInOut(&play_list,&play_list);
AmSession::onSessionStart();
}
void VoiceboxDialog::onBye(const AmSipRequest& req)
{
dlg->reply(req,200,"OK");
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.flush();
// 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.flush();
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.flush();
repeatCurMessage();
} else if ((unsigned int)event == VoiceboxFactory::save_key) {
state = Prompting;
play_list.flush();
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.flush();
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'.\n",
user.c_str(), domain.c_str()
);
closeMailbox();
return;
}
if (MSG_OK != ecode) {
ERROR("userdir_open for user '%s' domain '%s': %s\n",
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.flush();
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();
}