1 /* 2 * Copyright (C) 2007 iptego GmbH 3 * 4 * This file is part of SEMS, a free SIP media server. 5 * 6 * SEMS is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * For a license to use the sems software under conditions 12 * other than those described here, or to purchase support for this 13 * software, please contact iptel.org by e-mail at the following addresses: 14 * info@iptel.org 15 * 16 * SEMS is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 * 21 * You should have received a copy of the GNU General Public License 22 * along with this program; if not, write to the Free Software 23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 24 */ 25 #include "AmUtils.h" 26 27 #include "VoiceboxDialog.h" 28 #include "Voicebox.h" 29 30 #include "../msg_storage/MsgStorageAPI.h" // error codes 31 32 #define enqueueFront(msg) \ 33 prompts->addToPlaylist(msg, (long)this, play_list, true) 34 35 #define enqueueBack(msg) \ 36 prompts->addToPlaylist(msg, (long)this, play_list, false) 37 38 // event ids of playlist separator events 39 #define PLAYLIST_SEPARATOR_MSG_BEGIN 1 // play back of message starts 40 41 const char* MsgStrError(int e) { 42 switch (e) { 43 case MSG_OK: return "MSG_OK"; break; 44 case MSG_EMSGEXISTS: return "MSG_EMSGEXISTS"; break; 45 case MSG_EUSRNOTFOUND: return "MSG_EUSRNOTFOUND"; break; 46 case MSG_EMSGNOTFOUND: return "MSG_EMSGNOTFOUND"; break; 47 case MSG_EALREADYCLOSED: return "MSG_EALREADYCLOSED"; break; 48 case MSG_EREADERROR: return "MSG_EREADERROR"; break; 49 case MSG_ENOSPC: return "MSG_ENOSPC"; break; 50 case MSG_ESTORAGE: return "MSG_ESTORAGE"; break; 51 default: return "Unknown Error"; 52 } 53 } 54 55 VoiceboxDialog::VoiceboxDialog(const string& user, 56 const string& domain, 57 const string& pin, 58 AmPromptCollection* prompts, 59 PromptOptions prompt_options) 60 : play_list(this), prompts(prompts), prompt_options(prompt_options), 61 user(user), domain(domain), 62 pin(pin), 63 userdir_open(false), do_save_cur_msg(false), 64 in_saved_msgs(false) 65 { 66 setDtmfDetectionEnabled(true); 67 msg_storage = VoiceboxFactory::MessageStorage->getInstance(); 68 if(!msg_storage){ 69 ERROR("could not get a message storage reference\n"); 70 throw AmSession::Exception(500,"could not get a message storage reference"); 71 } 72 } 73 74 VoiceboxDialog::~VoiceboxDialog() 75 { 76 // empty playlist items 77 play_list.flush(); 78 prompts->cleanup((long)this); 79 } 80 81 void VoiceboxDialog::onSessionStart() { 82 if (pin.empty()) { 83 state = Prompting; 84 doMailboxStart(); 85 } else { 86 state = EnteringPin; 87 enqueueFront("pin_prompt"); 88 } 89 90 // set the playlist as input and output 91 setInOut(&play_list,&play_list); 92 93 AmSession::onSessionStart(); 94 } 95 96 void VoiceboxDialog::onBye(const AmSipRequest& req) 97 { 98 closeMailbox(); 99 setStopped(); 100 } 101 102 void VoiceboxDialog::process(AmEvent* ev) 103 { 104 // audio events 105 AmAudioEvent* audio_ev = dynamic_cast<AmAudioEvent*>(ev); 106 if (audio_ev && 107 audio_ev->event_id == AmAudioEvent::noAudio) { 108 DBG("########## noAudio event #########\n"); 109 110 if (Bye == state) { 111 closeMailbox(); 112 dlg->bye(); 113 setStopped(); 114 } 115 116 return; 117 } 118 119 AmPlaylistSeparatorEvent* pl_ev = dynamic_cast<AmPlaylistSeparatorEvent*>(ev); 120 if (pl_ev) { 121 DBG("########## Playlist separator ####\n"); 122 123 if (Prompting == state) { 124 if (pl_ev->event_id == PLAYLIST_SEPARATOR_MSG_BEGIN){ 125 // mark message as saved 126 saveCurMessage(); 127 // now we can accept action on the message 128 DBG("Changed state to MsgAction.\n"); 129 state = MsgAction; 130 } 131 } 132 133 return; 134 } 135 136 AmSession::process(ev); 137 } 138 139 void VoiceboxDialog::onDtmf(int event, int duration) 140 { 141 DBG("VoiceboxDialog::onDtmf: event %d duration %d\n", 142 event, duration); 143 144 if (EnteringPin == state) { 145 play_list.flush(); 146 // check pin 147 if (event<10) { 148 entered_pin += int2str(event); 149 DBG("added '%s': PIN is now '%s'.\n", 150 int2str(event).c_str(), entered_pin.c_str()); 151 } 152 if (event==10 || event==11) { // # and * keys 153 if (entered_pin.compare(pin)) { // wrong pin 154 entered_pin.clear(); 155 play_list.flush(); 156 prompts->addToPlaylist("pin_prompt", (long)this, play_list, true); 157 } 158 } 159 if (!entered_pin.compare(pin)) { 160 state = Prompting; 161 doMailboxStart(); 162 } 163 } 164 165 if (MsgAction == state) { 166 if ((unsigned int)event == VoiceboxFactory::repeat_key) { 167 play_list.flush(); 168 repeatCurMessage(); 169 } else if ((unsigned int)event == VoiceboxFactory::save_key) { 170 state = Prompting; 171 play_list.flush(); 172 enqueueBack("msg_saved"); 173 saveCurMessage(); 174 edited_msgs.push_back(*cur_msg); 175 advanceMessage(); 176 checkFinalMessage(); 177 if (!isAtEnd()) { 178 enqueueCurMessage(); 179 } 180 } else if ((unsigned int)event == VoiceboxFactory::delete_key) { 181 state = Prompting; 182 play_list.flush(); 183 enqueueBack("msg_deleted"); 184 deleteCurMessage(); 185 advanceMessage(); 186 checkFinalMessage(); 187 if (!isAtEnd()) { 188 enqueueCurMessage(); 189 } 190 } else if ((unsigned int)event == VoiceboxFactory::startover_key) { 191 if (isAtLastMsg()) { 192 edited_msgs.push_back(*cur_msg); 193 state = Prompting; 194 mergeMsglists(); 195 gotoFirstSavedMessage(); 196 enqueueCurMessage(); 197 } 198 } 199 } 200 201 if (PromptTurnover == state) { 202 if (((unsigned int)event == VoiceboxFactory::startover_key) 203 && (isAtEnd())) { 204 state = Prompting; 205 mergeMsglists(); 206 gotoFirstSavedMessage(); 207 enqueueCurMessage(); 208 } 209 } 210 211 } 212 213 void VoiceboxDialog::openMailbox() { 214 cur_msg = new_msgs.begin(); 215 216 AmArg di_args,ret; 217 di_args.push(domain.c_str()); // domain 218 di_args.push(user.c_str()); // user 219 msg_storage->invoke("userdir_open",di_args,ret); 220 if (!ret.size() 221 || !isArgInt(ret.get(0))) { 222 ERROR("userdir_open for user '%s' domain '%s'" 223 " returned no (valid) result.\n", 224 user.c_str(), domain.c_str() 225 ); 226 return; 227 } 228 userdir_open = true; 229 int ecode = ret.get(0).asInt(); 230 if (MSG_EUSRNOTFOUND == ecode) { 231 DBG("empty mailbox for user '%s' domain '%s'.\n", 232 user.c_str(), domain.c_str() 233 ); 234 closeMailbox(); 235 return; 236 } 237 238 if (MSG_OK != ecode) { 239 ERROR("userdir_open for user '%s' domain '%s': %s\n", 240 user.c_str(), domain.c_str(), 241 MsgStrError(ret.get(0).asInt())); 242 closeMailbox(); 243 return; 244 } 245 246 if ((ret.size() < 2) || 247 (!isArgArray(ret.get(1)))) { 248 ERROR("userdir_open for user '%s' domain '%s'" 249 " returned too few parameters.\n", 250 user.c_str(), domain.c_str() 251 ); 252 closeMailbox(); 253 return; 254 } 255 256 for (size_t i=0;i<ret.get(1).size();i++) { 257 AmArg& elem = ret.get(1).get(i); 258 if (!isArgArray(elem) 259 || elem.size() != 3) { 260 ERROR("wrong element in userdir list.\n"); 261 continue; 262 } 263 264 string msg_name = elem.get(0).asCStr(); 265 int msg_unread = elem.get(1).asInt(); 266 int size = elem.get(2).asInt(); 267 268 if (size) { // TODO: treat empty messages as well! 269 if (msg_unread) { 270 new_msgs.push_back(Message(msg_name, size)); 271 } else { 272 saved_msgs.push_back(Message(msg_name, size)); 273 } 274 } 275 } 276 277 new_msgs.sort(); 278 new_msgs.reverse(); 279 saved_msgs.sort(); 280 saved_msgs.reverse(); 281 282 DBG("Got %zd new and %zd saved messages for user '%s' domain '%s'\n", 283 new_msgs.size(), saved_msgs.size(), 284 user.c_str(), domain.c_str()); 285 286 if (new_msgs.size()) { 287 cur_msg = new_msgs.begin(); 288 in_saved_msgs = false; 289 } else { 290 if (saved_msgs.size()) 291 cur_msg = saved_msgs.begin(); 292 in_saved_msgs = true; 293 } 294 } 295 296 void VoiceboxDialog::closeMailbox() { 297 if (!userdir_open) 298 return; 299 300 AmArg di_args,ret; 301 di_args.push(domain.c_str()); // domain 302 di_args.push(user.c_str()); // user 303 msg_storage->invoke("userdir_close",di_args,ret); 304 if (ret.size() && 305 isArgInt(ret.get(0)) && 306 ret.get(0).asInt() != MSG_OK 307 ) { 308 ERROR("userdir_close for user '%s' domain '%s': %s\n", 309 user.c_str(), domain.c_str(), 310 MsgStrError(ret.get(0).asInt())); 311 } 312 userdir_open = false; 313 } 314 315 FILE* VoiceboxDialog::getCurrentMessage() { 316 string msgname = cur_msg->name; 317 318 DBG("trying to get message '%s' for user '%s' domain '%s'\n", 319 msgname.c_str(), user.c_str(), domain.c_str()); 320 AmArg di_args,ret; 321 di_args.push(domain.c_str()); // domain 322 di_args.push(user.c_str()); // user 323 di_args.push(msgname.c_str()); // msg name 324 325 msg_storage->invoke("msg_get",di_args,ret); 326 if (!ret.size() 327 || !isArgInt(ret.get(0))) { 328 ERROR("msg_get for user '%s' domain '%s' msg '%s'" 329 " returned no (valid) result.\n", 330 user.c_str(), domain.c_str(), 331 msgname.c_str() 332 ); 333 return NULL; 334 } 335 int ecode = ret.get(0).asInt(); 336 if (MSG_OK != ecode) { 337 ERROR("msg_get for user '%s' domain '%s' message '%s': %s", 338 user.c_str(), domain.c_str(), 339 msgname.c_str(), 340 MsgStrError(ret.get(0).asInt())); 341 return NULL; 342 } 343 344 if ((ret.size() < 2) || 345 (!isArgAObject(ret.get(1)))) { 346 ERROR("msg_get for user '%s' domain '%s' message '%s': invalid return value\n", 347 user.c_str(), domain.c_str(), 348 msgname.c_str()); 349 return NULL; 350 } 351 MessageDataFile* f = 352 dynamic_cast<MessageDataFile*>(ret.get(1).asObject()); 353 if (NULL == f) 354 return NULL; 355 356 FILE* fp = f->fp; 357 delete f; 358 return fp; 359 } 360 361 void VoiceboxDialog::doMailboxStart() { 362 openMailbox(); 363 doListOverview(); 364 if (new_msgs.empty() && saved_msgs.empty()) { 365 state = Bye; 366 } else { 367 enqueueCurMessage(); 368 } 369 } 370 371 void VoiceboxDialog::doListOverview() { 372 373 if (new_msgs.empty() && saved_msgs.empty()) { 374 enqueueBack("no_msg"); 375 return; 376 } 377 378 enqueueFront("you_have"); 379 380 if (!new_msgs.empty()) { 381 if (prompt_options.has_digits && 382 (new_msgs.size() == 1)) { 383 // one new message 384 enqueueBack("new_msg"); 385 } else { 386 // five 387 if (prompt_options.has_digits) 388 enqueueCount(new_msgs.size()); 389 // new messages 390 enqueueBack("new_msgs"); 391 } 392 if (!saved_msgs.empty()) 393 enqueueBack("and"); 394 } 395 396 if (!saved_msgs.empty()) { 397 if (prompt_options.has_digits && 398 (saved_msgs.size() == 1)) { 399 // one saved message 400 enqueueBack("saved_msg"); 401 } else { 402 // fifteen 403 if (prompt_options.has_digits) 404 enqueueCount(saved_msgs.size()); 405 // saved messages 406 enqueueBack("saved_msgs"); 407 } 408 } 409 } 410 411 bool VoiceboxDialog::enqueueCurMessage() { 412 if (((in_saved_msgs) && (cur_msg == saved_msgs.end())) 413 ||((!in_saved_msgs) && (cur_msg == new_msgs.end()))) { 414 ERROR("check implementation!\n"); 415 return false; 416 } 417 418 FILE* fp=getCurrentMessage(); 419 if (NULL == fp) 420 return false; 421 422 if (!in_saved_msgs) { 423 if (cur_msg == new_msgs.begin()) 424 enqueueBack("first_new_msg"); 425 else 426 enqueueBack("next_new_msg"); 427 } else { 428 if (cur_msg == saved_msgs.begin()) 429 enqueueBack("first_saved_msg"); 430 else 431 enqueueBack("next_saved_msg"); 432 } 433 // notifies the dialog that playback of message starts 434 enqueueSeparator(PLAYLIST_SEPARATOR_MSG_BEGIN); 435 // enqueue msg 436 message.fpopen(cur_msg->name, AmAudioFile::Read, fp); 437 play_list.addToPlaylist(new AmPlaylistItem(&message, NULL)); 438 if (!isAtLastMsg()) 439 enqueueBack("msg_menu"); 440 else 441 enqueueBack("msg_end_menu"); 442 //can do save action on cur message? 443 do_save_cur_msg = !in_saved_msgs; 444 445 return true; 446 } 447 448 void VoiceboxDialog::repeatCurMessage() { 449 play_list.flush(); 450 message.rewind(); 451 play_list.addToPlaylist(new AmPlaylistItem(&message, NULL)); 452 enqueueBack("msg_menu"); 453 } 454 455 void VoiceboxDialog::advanceMessage() { 456 if (!in_saved_msgs) { 457 if (cur_msg != new_msgs.end()) 458 cur_msg++; 459 if (cur_msg == new_msgs.end()) { 460 cur_msg = saved_msgs.begin(); 461 in_saved_msgs = true; 462 } 463 } else { 464 if (cur_msg != saved_msgs.end()) 465 cur_msg++; 466 } 467 } 468 469 void VoiceboxDialog::gotoFirstSavedMessage() { 470 cur_msg = saved_msgs.begin(); 471 in_saved_msgs = true; 472 } 473 474 475 void VoiceboxDialog::curMsgOP(const char* op) { 476 if (!isAtEnd()) { 477 string msgname = cur_msg->name; 478 AmArg di_args,ret; 479 di_args.push(domain.c_str()); // domain 480 di_args.push(user.c_str()); // user 481 di_args.push(msgname.c_str()); // msg name 482 483 msg_storage->invoke(op,di_args,ret); 484 485 if ((ret.size() < 1) 486 || !isArgInt(ret.get(0))) { 487 ERROR("%s returned wrong result type\n", op); 488 return; 489 } 490 491 int errcode = ret.get(0).asInt(); 492 if (errcode != MSG_OK) { 493 ERROR("%s error: %s\n", 494 op, MsgStrError(errcode)); 495 } 496 } 497 } 498 499 void VoiceboxDialog::saveCurMessage() { 500 if (do_save_cur_msg) 501 curMsgOP("msg_markread"); 502 do_save_cur_msg = false; 503 } 504 505 void VoiceboxDialog::deleteCurMessage() { 506 curMsgOP("msg_delete"); 507 } 508 509 bool VoiceboxDialog::isAtLastMsg() { 510 if (in_saved_msgs) { 511 if (saved_msgs.empty()) 512 return true; 513 return cur_msg->name == saved_msgs.back().name; 514 515 } else { 516 if (!saved_msgs.empty() || (new_msgs.empty())) 517 return false; 518 return cur_msg->name == new_msgs.back().name; 519 } 520 } 521 522 bool VoiceboxDialog::isAtEnd() { 523 bool res = 524 (in_saved_msgs && (cur_msg == saved_msgs.end())) 525 ||(!in_saved_msgs && (cur_msg == new_msgs.end())); 526 return res; 527 } 528 529 void VoiceboxDialog::checkFinalMessage() { 530 if (isAtEnd()) { 531 if (!edited_msgs.empty()) { 532 enqueueBack("no_more_msg"); 533 state = PromptTurnover; 534 } else { 535 state = Bye; 536 enqueueBack("no_msg"); 537 } 538 } 539 } 540 541 542 void VoiceboxDialog::enqueueCount(unsigned int cnt) { 543 if (cnt > 99) { 544 ERROR("only support up to 99 messages count.\n"); 545 return; 546 } 547 548 if ((cnt <= 20) || (! (cnt%10))) { 549 enqueueBack(int2str(cnt)); 550 return; 551 } 552 div_t num = div(cnt, 10); 553 if (prompt_options.digits_right) { 554 // language has single digits after 10s 555 enqueueBack(int2str(num.quot * 10)); 556 enqueueBack("x"+int2str(num.rem)); 557 } else { 558 // language has single digits before 10s 559 enqueueBack("x"+int2str(num.rem)); 560 enqueueBack(int2str(num.quot * 10)); 561 } 562 } 563 564 // only one separator may be in playlist! 565 void VoiceboxDialog::enqueueSeparator(int id) { 566 playlist_separator.reset(new AmPlaylistSeparator(this, id)); 567 play_list.addToPlaylist(new AmPlaylistItem(playlist_separator.get(), NULL)); 568 } 569 570 // copy edited_msgs to saved_msgs 571 // so that the user can go throuh them again 572 void VoiceboxDialog::mergeMsglists() { 573 saved_msgs.clear(); 574 saved_msgs = edited_msgs; 575 edited_msgs.clear(); 576 } 577