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