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.
579 lines
14 KiB
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();
|
|
}
|