From fe53372de8a97d3af08dc3e54d842a4291449190 Mon Sep 17 00:00:00 2001
From: Stefan Sayer <stefan.sayer@googlemail.com>
Date: Fri, 20 Feb 2015 21:55:59 +0100
Subject: [PATCH 7/7] confpin: simple pin conference app

---
 apps/confpin/confpin.conf                     |  39 +++++
 apps/confpin/lib/confpin.dsm                  | 237 ++++++++++++++++++++++++++
 apps/confpin/lib/sw_audio.dsm                 |  50 ++++++
 apps/confpin/lib/sw_audio_api.dsm             |  66 +++++++
 apps/confpin/prompts/Makefile                 |  22 +++
 apps/confpin/prompts/conference_first.txt     |   1 +
 apps/confpin/prompts/conference_greeting.txt  |   1 +
 apps/confpin/prompts/conference_join.txt      |   1 +
 apps/confpin/prompts/conference_joined.txt    |   1 +
 apps/confpin/prompts/conference_leave.txt     |   1 +
 apps/confpin/prompts/conference_music.txt     |   1 +
 apps/confpin/prompts/conference_pin_wrong.txt |   1 +
 apps/confpin/prompts/goodbye.txt              |   1 +
 apps/confpin/tools/clear_file                 |  14 ++
 apps/confpin/tools/fetch_file                 |  14 ++
 apps/confpin/tools/push_file                  |  14 ++
 16 files changed, 464 insertions(+)
 create mode 100644 apps/confpin/confpin.conf
 create mode 100644 apps/confpin/lib/confpin.dsm
 create mode 100644 apps/confpin/lib/sw_audio.dsm
 create mode 100644 apps/confpin/lib/sw_audio_api.dsm
 create mode 100644 apps/confpin/prompts/Makefile
 create mode 100644 apps/confpin/prompts/conference_first.txt
 create mode 100644 apps/confpin/prompts/conference_greeting.txt
 create mode 100644 apps/confpin/prompts/conference_join.txt
 create mode 100644 apps/confpin/prompts/conference_joined.txt
 create mode 100644 apps/confpin/prompts/conference_leave.txt
 create mode 100644 apps/confpin/prompts/conference_music.txt
 create mode 100644 apps/confpin/prompts/conference_pin_wrong.txt
 create mode 100644 apps/confpin/prompts/goodbye.txt
 create mode 100755 apps/confpin/tools/clear_file
 create mode 100755 apps/confpin/tools/fetch_file
 create mode 100755 apps/confpin/tools/push_file

--- /dev/null
+++ b/apps/confpin/confpin.conf
@@ -0,0 +1,39 @@
+# configuration for confpin app
+
+
+# audio cache configuration 
+# audio_cache - same as cache_dir in audio_cache.conf
+#audio_cache=/tmp
+#audio_cache=/home/stefan/frafos/customers/sipwise/pbx/new_repo/sipwise_modules/callqueues/prompts
+audio_cache=../apps/confpin/cache/
+# MySQL database connection for downloading audio prompts
+db_url=mysql://root:frafos@127.0.0.1/provisioning
+
+# default sound set if non provisioned with app-param
+sound_set_id=1
+
+digit_collection_timeout=10
+
+###### digits - to be put into DB
+#digits_right=yes or no
+# single digits before 10s - for e.g. German: no, for eg. English: yes 
+digits_right=yes
+# directory with digits - e.g. cache directory
+# the language (lng app param) will be added at the end, e.g.
+# digits_dir=../apps/callingcard/prompts/es/
+digits_dir=../../sipwise_modules/callqueues/prompts/
+
+#######
+# it's best to leave those below as-is
+#diag_path=/usr/lib/sems/dsm/confpin
+#mod_path=/usr/lib/sems/dsm/
+
+# in-tree testing:
+diag_path=../apps/confpin/lib
+mod_path=../apps/dsm/mods/lib/
+
+load_diags=confpin,sw_audio_api
+register_apps=confpin
+run_invite_event=yes
+set_param_variables=yes
+run_system_dsms=sw_audio_api
--- /dev/null
+++ b/apps/confpin/lib/confpin.dsm
@@ -0,0 +1,237 @@
+#[% TAGS [- -] %]
+---
+-- This DSM app
+--   * asks caller to enter pin for conference room
+--   * puts caller in conference room identified by roompin
+--
+-- Parameters (P-App-Param):
+--    confpin     - dsm app
+--    roompin     - pin from db for particular subscriber
+--    room	  - conference room consists subscriber and roompin
+--    sound_set_id - sound set to use
+--
+--  Example: P-App-Param: confpin;roompin="pin_from_db";room="$fU"_"pin_from_db";sound_set_id=3
+--
+
+import(mod_conference);
+import(mod_dlg);
+import(mod_utils);
+import(mod_groups);
+
+#include "sw_audio.dsm"
+
+function stopCall() {
+  stop(true);
+};
+
+function refuseWith500() {
+  set(connect_session=0);
+  dlg.reply(500, "Server Internal Error");
+  stop(false);
+};
+
+function enterConference() {
+    set($audio_id="conference_joined");
+    swMixInFile();
+    info(">> entering conference in room '$room'");
+    conference.setPlayoutType(adaptive);
+    conference.join($room);
+    set($joined=true);
+    groups.postEvent($room, joined);
+    groups.join($room);
+    groups.getSize($roomsize=$room);
+    if test($roomsize==1) {
+      set($audio_id="conference_first");
+      swMixInFile();
+      set($audio_id="conference_music");
+      set($sw_audio_loop="true");
+      swMixInFile();
+      clear($sw_audio_loop);
+      set($lonely_user=true);
+    }
+    info("entered conference, $roomsize people in here<<");
+};
+initial state START;
+
+transition "DB exception" START - exception / {
+  error("Error in initializing :");
+  refuseWith500();
+} -> END;
+
+transition "check for sound_set_id provisioning" START - invite; test($sound_set_id=="") / {
+  error("Error in provisioning: no sound_set_id application parameter set");
+  refuseWith500();
+} -> END;
+
+transition "Fetching File exception" START - exception; test(#type=="result") / {
+  error("error in DB query for fetching audio file:");
+  logAll(1);
+  stop(true);
+} -> END;
+
+transition "call starts" START - invite / {
+    -- process session establishment in DSM app
+    set(connect_session=0);
+    dlg.acceptInvite();
+    connectMedia();
+
+    -- connect to DB
+    mysql.connect();
+
+    if test($roompin=="")
+    {
+        info(pin is empty);
+        set($autoEnter=1);
+        repost();
+    }
+    else
+    {
+        info(">> start_pin_entry");
+        set($tryAgain=0);
+        set($pin="");
+
+        -- rfc4240
+        if test($room=="") {
+            info("room is empty");
+            dlg.reply(404,"Not found");
+        } else {
+            set($audio_id="conference_greeting");
+            swPlayFile();
+        }
+        info("start_pin_entry <<");
+    }
+} -> COLLECT_PIN;
+
+--------------------------------------------------------
+state COLLECT_PIN;
+transition "prompt ends, start collection timer" COLLECT_PIN - noAudio / setTimer(1, $config.digit_collection_timeout) -> COLLECT_PIN;
+
+transition "roompin empty, enter conference" COLLECT_PIN - test($autoEnter==1) / {
+    info(">> empty_pin");
+    enterConference();
+} -> CONFERENCE;
+
+transition "timer, short pin, too many tries" COLLECT_PIN - timer(#id==1); test(len($pin)<3); test($pin_tries==$config.pin_max_retries) / {
+    stopCall();
+} -> END;
+transition "timer, short pin, too many tries" COLLECT_PIN - timer(#id==1); test(len($pin)<3); test($pin_tries>$config.pin_max_retries) / {
+    stopCall();
+} -> END;
+transition "hash entered, short pin, too many tries" COLLECT_PIN - key(#key==11); test(len($pin)<3); test($pin_tries>$config.pin_max_retries) / {
+    stopCall();
+} -> END;
+transition "hash entered, short pin, too many tries" COLLECT_PIN - key(#key==11); test(len($pin)<3); test($pin_tries==$config.pin_max_retries) / {
+    stopCall();
+} -> END;
+
+transition "collect pin timer hit, short pin" COLLECT_PIN - timer(#id==1); test(len($pin)<3) / {
+    set($audio_id="conference_pin_wrong");
+    swPlayFile();
+    inc($pin_tries);
+} -> COLLECT_PIN;
+
+transition "hash entered, short pin" COLLECT_PIN - key(#key==11); test(len($pin)<3) / {
+    set($audio_id="conference_pin_wrong");
+    swPlayFile();
+    inc($pin_tries);
+} -> COLLECT_PIN;
+
+transition "number entered" COLLECT_PIN - key(#key<10) / {
+    -- stop prompt
+    closePlaylist(false);
+    append($pin, #key);
+    info("USER ENTERED key '#key'");
+    setTimer(1, $config.digit_collection_timeout);
+} -> COLLECT_PIN;
+
+transition "collect pin timer hit" COLLECT_PIN - timer(#id==1) -> TESTPINRESULT;
+transition "hash entered, finished pin" COLLECT_PIN - key(#key==11) / removeTimer(1) -> TESTPINRESULT;
+
+--------------------------------------------------------
+state TESTPINRESULT
+enter {
+    info("Key #key pressed, tryAgain is $tryAgain");
+    repost();
+};
+
+transition "incorrect_retry" TESTPINRESULT - test($pin!=$roompin); test($tryAgain<3) / {
+    info(">> incorrect_retry, pin '$pin', tryAgain '$tryAgain'");
+    inc($tryAgain);
+    set($pin="");
+    set($audio_id="conference_pin_wrong");
+    swPlayFile();
+    info("incorrect_retry <<");
+} -> COLLECT_PIN;
+
+transition "incorrect_no_retry" TESTPINRESULT - test($pin!=$roompin); test($tryAgain==3) / {
+    info(">> incorrect_no_retry, tryAgain '$tryAgain'");
+    set($audio_id="goodbye");
+    swPlayFile();
+    info("incorrect_no_retry <<");
+} -> PLAY_FIN;
+
+--------------------------------------------------------
+state PLAY_FIN;
+transition "bye_after_fin" PLAY_FIN - noAudio / stopCall() -> END;
+
+--------------------------------------------------------
+
+transition "correct_pin" TESTPINRESULT - test($pin==$roompin) / {
+    info(">> correct_pin");
+    enterConference();
+} -> CONFERENCE;
+
+state CONFERENCE;
+
+transition "got leave event" CONFERENCE - event(#leave==true) / {
+    logParams();
+    set($audio_id="conference_leave");
+    swMixInFile();
+} -> CONFERENCE;
+
+transition "got join event" CONFERENCE - event(#joined==true) / {
+    logParams();
+    if test($lonely_user==true) {
+      conference.flushMixInList();
+    }
+    set($audio_id="conference_join");
+    swMixInFile();
+} -> CONFERENCE;
+
+transition "kick_event" CONFERENCE - eventTest(#action==kick) / {
+    info(">> kick_event");
+    closePlaylist();
+    conference.leave();
+    set($leave=true);
+    groups.leaveAll();
+    groups.postEvent($room, leave);
+    stop(true);
+    info("kick_event <<");
+} -> END;
+
+# is this the same as kick?
+transition "leave_event" CONFERENCE - eventTest(#action==leave) / {
+    info(">> leave_event");
+    closePlaylist();
+    conference.leave();
+    set($leave=true);
+    groups.leaveAll();
+    groups.postEvent($room, leave);
+    stop(true);
+    info("leave_event <<");
+} -> END;
+
+transition "leave_conference" CONFERENCE - hangup / {
+    info(">> leave_conference");
+    closePlaylist();
+    conference.leave();
+    set($leave=true);
+    groups.leaveAll();
+    groups.postEvent($room, leave);
+    stop(false);
+    info("leave_conference <<");
+} -> END;
+
+transition "bye_recvd" (START,COLLECT_PIN,PLAY_FIN,TESTPINRESULT) - hangup / stop(false) -> END;
+
+state END;
--- /dev/null
+++ b/apps/confpin/lib/sw_audio.dsm
@@ -0,0 +1,50 @@
+-- sw_audio
+--
+-- The playFile() function plays a file with ID $audio_id from the $config.audio_cache directory.
+-- $sound_set_id is used as subdirectory under $config.audio_cache.
+-- If it doesn't exist, it is tried to be fetched from the MySQL DB, using the $audio_id.
+-- If that fails, an exception with type playFile is thrown
+--
+-- swMixInFile() mixes the file in to a mixInList setup by the conference module. it uses
+-- the $has_mixin variable to record that state, and setup the mixin if it's not there.
+--
+-- set($sw_audio_loop="true") makes the file to be looped
+
+import(mod_sys);
+import(mod_mysql);
+import(mod_conference);
+
+function swPlayFile() {
+  sets($__file_cache_path="$(config.audio_cache)/$(sound_set_id)/");
+  sets($__file_path="$(__file_cache_path)$(audio_id).wav");
+
+  if sys.file_not_exists($__file_path) {
+    if sys.file_not_exists($__file_cache_path) {
+      sys.mkdir($__file_cache_path);
+    }
+    -- connect to DB
+    if not mysql.connected() {
+      mysql.connect();
+      throwOnError();
+    }
+
+    mysql.getFileFromDB('SELECT data FROM provisioning.voip_sound_files vsf, provisioning.voip_sound_handles vsh WHERE vsh.name="$audio_id" AND vsf.set_id=$sound_set_id AND vsh.id=vsf.handle_id',$__file_path);
+    throwOnError();
+  }
+  if test($mix_file==true) {
+    conference.playMixInList($__file_path, $sw_audio_loop);
+  } else {
+    playFile($__file_path, $sw_audio_loop);
+  }
+  clear($__file_path);
+  clear($__file_cache_path);
+};
+
+function swMixInFile() {
+  set($mix_file=true);
+  if test($has_mixin!=true) {
+    conference.setupMixIn(0.5, 0);
+    set($has_mixin=true);
+  }
+  swPlayFile();
+};
\ No newline at end of file
--- /dev/null
+++ b/apps/confpin/lib/sw_audio_api.dsm
@@ -0,0 +1,66 @@
+-- sw_audio_api
+--
+-- this SystemDSM implements API functions to handle the prompt cache
+--
+
+import(mod_sys);
+import(mod_mysql);
+
+initial state SW_AUDIO_START;
+
+transition "server startup" SW_AUDIO_START - startup / {
+   log(2, "sw_audio cache handler started");
+   registerEventQueue("sw_audio");
+   mysql.connect();
+   logAll(3);
+} -> SW_AUDIO_WAIT_EVENT;
+
+state SW_AUDIO_WAIT_EVENT;
+
+transition "exception in server startup" (SW_AUDIO_START,SW_AUDIO_WAIT_EVENT) - exception / {
+ logAll(1);
+} -> SW_AUDIO_WAIT_EVENT;
+
+transition "server shutdown" SW_AUDIO_WAIT_EVENT - system(#type=="ServerShutdown") / {
+   log(2, "sw_audio cache handler stopping");
+   unregisterEventQueue("sw_audio");
+   stop(false);
+} -> SW_AUDIO_END;
+
+transition "clear File" SW_AUDIO_WAIT_EVENT - event(#cmd=="clearFile") / {
+  log(3, "sw_audio cache handler: clearFile");
+  logParams(3);
+  sets($__file_path="$(config.audio_cache)/#(sound_set_id)/#(audio_id).wav");
+  sys.unlink($__file_path);
+  clear($__file_path);
+}   -> SW_AUDIO_WAIT_EVENT;
+
+transition "fetch File" SW_AUDIO_WAIT_EVENT - event(#cmd=="fetchFile") / {
+  log(3, "sw_audio cache handler: fetchFile");
+  logParams(3);
+  sets($__file_path="$(config.audio_cache)/#(sound_set_id)/#(audio_id).wav");
+  mysql.getFileFromDB(SELECT data FROM provisioning.audio_files WHERE id="#audio_id",$__file_path);
+  clear($__file_path);
+}   -> SW_AUDIO_WAIT_EVENT;
+
+transition "push File" SW_AUDIO_WAIT_EVENT - event(#cmd=="pushFile") / {
+  log(3, "sw_audio cache handler: pushFile");
+  log(3, "WARNING! for debug purposes only");
+  logParams(3);
+  sets($__file_path="$(config.audio_cache)/#(sound_set_id)/#(audio_id).wav");
+  mysql.connect();
+  mysql.query(SELECT id as vsh_id FROM provisioning.voip_sound_handles where name="#audio_id");
+  logAll(3);
+  if test($db.rows==0) {
+     mysql.execute(INSERT INTO provisioning.voip_sound_handles (name) VALUES ("#audio_id"));
+    set($vsh_id=$db.insert_id);    
+  } else {
+    mysql.getResult(0);
+  };
+  log(3, $vsh_id);
+  mysql.putFileToDB('INSERT INTO provisioning.voip_sound_files (handle_id,set_id,data) VALUES ($vsh_id,#sound_set_id, "__FILE__")', $__file_path);
+  logAll(3);
+  clear($__file_path);
+}   -> SW_AUDIO_WAIT_EVENT;
+
+state SW_AUDIO_END;
\ No newline at end of file
--- /dev/null
+++ b/apps/confpin/prompts/Makefile
@@ -0,0 +1,22 @@
+SRCS=$(wildcard *.txt)
+WAVS=$(SRCS:.txt=.wav)
+DBS=$(SRCS:.txt=.dbs)
+
+all: $(WAVS) 
+
+db_upload: $(DBS)
+
+%.wav : %.txt Makefile
+	flite -f $< -o $(basename $@)_16k.wav
+##	text2wave $< -o $(basename $@)_16k.wav
+	sox $(basename $@)_16k.wav -r 8000 $@
+	rm $(basename $@)_16k.wav
+
+%.dbs:  %.wav Makefile
+	mkdir -p ../cache/1
+	cp $(basename $@).wav ../cache/1
+	../tools/push_file $(basename $@) 1
+	touch $(basename $@).dbs
+	
+clean:
+	rm -f *.dbs *.wav
\ No newline at end of file
--- /dev/null
+++ b/apps/confpin/prompts/conference_first.txt
@@ -0,0 +1 @@
+you are the first participant in the conference.
--- /dev/null
+++ b/apps/confpin/prompts/conference_greeting.txt
@@ -0,0 +1 @@
+welcome to the conference, please enter your pin number
--- /dev/null
+++ b/apps/confpin/prompts/conference_join.txt
@@ -0,0 +1 @@
+someone joined the conference
--- /dev/null
+++ b/apps/confpin/prompts/conference_joined.txt
@@ -0,0 +1 @@
+you joined the conference
--- /dev/null
+++ b/apps/confpin/prompts/conference_leave.txt
@@ -0,0 +1 @@
+someone left the conference
--- /dev/null
+++ b/apps/confpin/prompts/conference_music.txt
@@ -0,0 +1 @@
+bum bada bum bum bamm, bamm bada bamm bamm bamm, hum di dum di dum dum dumm
--- /dev/null
+++ b/apps/confpin/prompts/conference_pin_wrong.txt
@@ -0,0 +1 @@
+sorry, that pin is wrong. please try again
--- /dev/null
+++ b/apps/confpin/prompts/goodbye.txt
@@ -0,0 +1 @@
+good bye
--- /dev/null
+++ b/apps/confpin/tools/clear_file
@@ -0,0 +1,14 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+import sys
+from xmlrpclib import *
+
+if len(sys.argv) != 3:
+	print "usage: %s <prompt id> <sound set id>" % sys.argv[0]
+	sys.exit(1)
+
+s = ServerProxy('http://localhost:8090')
+print "Active calls: %d" % s.calls()
+#p ={ 'name' : sys.argv[1], 'path' : sys.argv[2] }
+#print s.di('sbc','loadProfile',p)
+print s.di('dsm', 'postDSMEvent', 'sw_audio', [['cmd', 'clearFile'],['audio_id', sys.argv[1]],['sound_set_id', sys.argv[2]]])
\ No newline at end of file
--- /dev/null
+++ b/apps/confpin/tools/fetch_file
@@ -0,0 +1,14 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+import sys
+from xmlrpclib import *
+
+if len(sys.argv) != 3:
+	print "usage: %s <prompt id> <sound set id>" % sys.argv[0]
+	sys.exit(1)
+
+s = ServerProxy('http://localhost:8090')
+print "Active calls: %d" % s.calls()
+#p ={ 'name' : sys.argv[1], 'path' : sys.argv[2] }
+#print s.di('sbc','loadProfile',p)
+print s.di('dsm', 'postDSMEvent', 'sw_audio', [['cmd', 'fetchFile'],['audio_id', sys.argv[1]],['sound_set_id', sys.argv[2]]])
\ No newline at end of file
--- /dev/null
+++ b/apps/confpin/tools/push_file
@@ -0,0 +1,14 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+import sys
+from xmlrpclib import *
+
+if len(sys.argv) != 3:
+	print "usage: %s <prompt id> <set id>" % sys.argv[0]
+	sys.exit(1)
+
+s = ServerProxy('http://localhost:8090')
+print "Active calls: %d" % s.calls()
+#p ={ 'name' : sys.argv[1], 'path' : sys.argv[2] }
+#print s.di('sbc','loadProfile',p)
+print s.di('dsm','postDSMEvent', 'sw_audio', [['cmd', 'pushFile'],['audio_id', sys.argv[1]],['sound_set_id', sys.argv[2]]])
