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/examples/call_gen/CallGen.cpp

465 lines
13 KiB

/*
* $Id: CallGen.cpp 288 2007-03-28 16:32:02Z sayer $
*
* 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 "CallGen.h"
#include "AmConferenceStatus.h"
#include "AmUtils.h"
#include "log.h"
#include "AmUAC.h"
#include "AmPlugIn.h"
#include "AmSessionContainer.h"
#include "AmMediaProcessor.h"
#include <stdlib.h>
#include <time.h>
#define APP_NAME "callgen"
#define PLAY_FILE "play_file"
#define CALL_TIMER 1
#include <vector>
using std::vector;
EXPORT_SESSION_FACTORY(CallGenFactory,APP_NAME);
EXPORT_PLUGIN_CLASS_FACTORY(CallGenFactory,APP_NAME);
string CallGenFactory::DigitsDir;
AmFileCache CallGenFactory::play_file;
CallGenFactory::CallGenFactory(const string& _app_name)
: AmSessionFactory(_app_name),
AmDynInvokeFactory(_app_name),
configured(false), target_args(NULL), scheduled(0)
{
if (NULL == _instance) {
_instance = this;
}
}
CallGenFactory* CallGenFactory::_instance=0;
int CallGenFactory::onLoad()
{
return getInstance()->load();
}
int CallGenFactory::load() {
// only execute this once
if (configured)
return 0;
configured = true;
AmConfigReader cfg;
if(cfg.loadFile(AmConfig::ModConfigPath + string(APP_NAME)+ ".conf"))
return -1;
// get application specific global parameters
configureModule(cfg);
string play_fname = cfg.getParameter(PLAY_FILE, "default.wav");
if (play_file.load(play_fname)) {
ERROR("file %s could not be loaded.\n",
play_fname.c_str());
return -1;
}
// get prompts
AM_PROMPT_START;
AM_PROMPT_END(prompts, cfg, APP_NAME);
DigitsDir = cfg.getParameter("digits_dir");
if (DigitsDir.length() && DigitsDir[DigitsDir.length()-1]!='/')
DigitsDir+='/';
if (!DigitsDir.length()) {
WARN("No digits_dir specified in configuration.\n");
}
for (int i=0;i<10;i++)
prompts.setPrompt(int2str(i), DigitsDir+int2str(i)+".wav", APP_NAME);
prompts.setPrompt("*", DigitsDir+"s.wav", APP_NAME);
prompts.setPrompt("#", DigitsDir+"p.wav", APP_NAME);
start();
return 0;
}
void CallGenFactory::run() {
while (true) {
actions_mut.lock();
vector<AmArg> todo;
time_t now;
time(&now);
multimap<time_t, AmArg>::iterator it =
actions.begin();
while (it != actions.end()) {
if (it->first > now)
break;
todo.push_back(it->second);
actions.erase(it);
it = actions.begin();
}
actions_mut.unlock();
for (vector<AmArg>::iterator it=todo.begin();
it != todo.end(); it++) {
createCall(*it);
if (scheduled>0)
scheduled--;
}
checkTarget();
sleep(1);
}
}
void CallGenFactory::checkTarget() {
if (!target_args)
return;
DBG("%d active calls, %d current target, %d already scheduled\n",
active_calls.size(), target_args->get(0).asInt(), scheduled);
int missing_calls =
target_args->get(0).asInt() - active_calls.size() - scheduled;
if (missing_calls > 0) {
AmArg* to_schedule_args = new AmArg(*target_args);
(*to_schedule_args)[0] = AmArg(missing_calls);
AmArg ret;
scheduleCalls(*to_schedule_args, ret);
scheduled += missing_calls;
}
}
void CallGenFactory::on_stop(){
ERROR("not stoppable!\n");
}
AmSession* CallGenFactory::onInvite(const AmSipRequest& req)
{
ERROR("DUH - need session params!.\n");
return NULL;
}
// outgoing calls
AmSession* CallGenFactory::onInvite(const AmSipRequest& req, AmArg& args)
{
size_t cnt = 0;
int ncalls = args.get(cnt++).asInt();
int wait_time_base = args.get(cnt++).asInt();
int wait_time_rand = args.get(cnt++).asInt();
string ruri_user = args.get(cnt++).asCStr();
string ruri_host = args.get(cnt++).asCStr();
int ruri_rand_digits = args.get(cnt++).asInt();
int play_rand_digits = args.get(cnt++).asInt();
int call_time_base = args.get(cnt++).asInt();
int call_time_rand = args.get(cnt++).asInt();
return new CallGenDialog(prompts, play_rand_digits,
call_time_base, call_time_rand);
}
void CallGenFactory::invoke(const string& method,
const AmArg& args,
AmArg& ret)
{
if (method == "createCalls"){
args.assertArrayFmt("iiissiiii");
instance()->createCalls(args, ret);
} else if (method == "scheduleCalls"){
args.assertArrayFmt("iiissiiii");
instance()->scheduleCalls(args, ret);
} else if (method == "setTarget"){
args.assertArrayFmt("iiissiiii");
instance()->setTarget(args, ret);
} else if (method == "callGenStats"){
instance()->callGenStats(args, ret);
} else if(method == "help"){
ret.push(
"callgen - simple call generator\n"
" method: createCalls - create calls (online - takes its time to return)\n"
" method: scheduleCalls - schedule calls\n"
" method: setTarget - set call count target\n"
"\n"
" parameters for these functions are always: \n"
" int ncalls - number of calls to [make, schedule, set target]\n"
" int wait_time_base - wait time btw calls, base value\n"
" int wait_time_rand - wait time btw calls, random add (total = base + rand)\n"
" string ruri_user - user part of ruri\n"
" string ruri_host - host part of ruri\n"
" int ruri_rand_digits - no of random digits to add to ruri user\n"
" int play_rand_digits - no of random digits to play at the beginning\n"
" int call_time_base - call timer, base value \n"
" int call_time_rand - call timer, random add (total = base + rand)\n"
"\n"
" method: callGenStats - return some statistics\n"
);
} else if(method == "_list"){
ret.push("createCalls");
ret.push("scheduleCalls");
ret.push("setTarget");
ret.push("callGenStats");
ret.push("help");
} else
throw AmDynInvoke::NotImplemented(method);
}
void CallGenFactory::createCall(const AmArg& args) {
size_t cnt = 0;
int ncalls = args.get(cnt++).asInt();
int wait_time_base = args.get(cnt++).asInt();
int wait_time_rand = args.get(cnt++).asInt();
string ruri_user = args.get(cnt++).asCStr();
string ruri_host = args.get(cnt++).asCStr();
int ruri_rand_digits = args.get(cnt++).asInt();
int play_rand_digits = args.get(cnt++).asInt();
int call_time_base = args.get(cnt++).asInt();
int call_time_rand = args.get(cnt++).asInt();
string from = "sip:callgen@"+AmConfig::LocalSIPIP;
string call_ruri = "sip:"+ruri_user;
for (int i=0;i<ruri_rand_digits;i++)
call_ruri+=int2str(rand()%10);
call_ruri+="@"+ruri_host;
AmArg* c_args = new AmArg(args);
DBG("placing new call to %s\n", call_ruri.c_str());
AmSession* s = AmUAC::dialout("callgen", // user
APP_NAME,
call_ruri,
"<" + from + ">", from,
call_ruri,
string(""), // callid
string(""), // headers
c_args);
}
void CallGenFactory::createCalls(const AmArg& args, AmArg& ret) {
size_t cnt = 0;
int ncalls = args.get(cnt++).asInt();
int wait_time_base = args.get(cnt++).asInt();
int wait_time_rand = args.get(cnt++).asInt();
for (int i=0;i<ncalls;i++) {
createCall(args);
int wait_nsec = wait_time_base;
if (wait_time_rand>0)
wait_nsec+=(rand()%wait_time_rand);
DBG("sleeping %d seconds\n", wait_nsec);
if (wait_nsec >0 && (i+1 < ncalls))
sleep(wait_nsec);
}
ret.push(0);
ret.push("OK");
}
void CallGenFactory::scheduleCalls(const AmArg& args, AmArg& ret) {
size_t cnt = 0;
int ncalls = args.get(cnt++).asInt();
int wait_time_base = args.get(cnt++).asInt();
int wait_time_rand = args.get(cnt++).asInt();
DBG("scheduling %d calls...\n", ncalls);
time_t now;
time(&now);
actions_mut.lock();
for (int i=0;i<ncalls;i++) {
actions.insert(std::make_pair(now, args));
int wait_nsec = wait_time_base;
if (wait_time_rand>0)
wait_nsec+=(rand()%wait_time_rand);
now+=wait_nsec;
}
actions_mut.unlock();
}
void CallGenFactory::setTarget(const AmArg& args, AmArg& ret) {
AmArg* old_args = target_args;
target_args = new AmArg(args);
if (old_args)
delete old_args;
DBG("target now set to %d calls\n",
target_args->get(0).asInt());
}
void CallGenFactory::callGenStats(const AmArg& args, AmArg& ret) {
int target = 0;
if (target_args)
target = target_args->get(0).asInt();
string res = "CallGen statistics: \n " +
int2str(active_calls.size()) + " active calls\n " +
int2str(target) + " current target\n " +
int2str(scheduled) +" scheduled\n ";
calls_list_mut.lock();
res += int2str(past_calls.size()) + " total calls\n ";
calls_list_mut.unlock();
ret.push(res.c_str());
}
void CallGenFactory::reportCall(string callid,
CallGenEvent ev,
time_t connect_ts,
time_t disconnect_ts) {
calls_list_mut.lock();
// FIXME: not 100% correct: disconnect should be moving to past_calls
if (ev == CGDestroy) {
active_calls.erase(callid);
CallInfo ci;
ci.connect_ts = connect_ts;
ci.disconnect_ts = disconnect_ts;
ci.status = ev;
past_calls[callid] = ci;
} else {
active_calls[callid].connect_ts = connect_ts;
active_calls[callid].disconnect_ts = disconnect_ts;
active_calls[callid].status = ev;
}
calls_list_mut.unlock();
}
CallGenDialog::CallGenDialog(AmPromptCollection& prompts,
int play_rand_digits, int call_time_base, int call_time_rand)
: play_list(this), prompts(prompts),
connect_ts(-1), disconnect_ts(-1),
play_rand_digits(play_rand_digits),
call_time_base(call_time_base),
call_time_rand(call_time_rand),
play_file(&CallGenFactory::play_file)
{
}
CallGenDialog::~CallGenDialog()
{
prompts.cleanup((long)this);
play_list.close(false);
report(CGDestroy);
}
void CallGenDialog::onInvite(const AmSipRequest& r) {
report(CGCreate);
AmSession::onInvite(r);
}
void CallGenDialog::report(CallGenEvent what) {
CallGenFactory::instance()->reportCall(getLocalTag(),
what,
connect_ts,
disconnect_ts);
}
void CallGenDialog::onSessionStart(const AmSipReply& rep) {
time(&connect_ts);
report(CGConnect);
// add some random digits
for (int i=0;i<play_rand_digits;i++)
prompts.addToPlaylist(int2str(rand()%10), (long)this, play_list);
if (play_rand_digits > 0)
prompts.addToPlaylist("#", (long)this, play_list);
play_file.loop.set(true);
play_list.addToPlaylist(new AmPlaylistItem(&play_file, NULL));
// // todo (?): set loop...
// prompts.addToPlaylist(PLAY_FILE, (long)this, play_list);
setInOut(&play_list, &play_list);
int call_timer = call_time_base;
if (call_time_rand>0)
call_timer+=rand()%call_time_rand;
if (call_timer > 0) {
AmDynInvokeFactory* UserTimer = AmPlugIn::instance()->getFactory4Di("user_timer");
if(!UserTimer){
ERROR("could not load user_timer from session_timer plug-in\n");
return;
}
AmDynInvoke* user_timer = UserTimer->getInstance();
if (!user_timer) {
ERROR("could not get user_timer\n");
return;
}
DBG("setting call timer to %d seconds\n", call_timer);
AmArg di_args,ret;
di_args.push(CALL_TIMER);
di_args.push(call_timer);
di_args.push(getLocalTag().c_str());
user_timer->invoke("setTimer",di_args,ret);
}
}
void CallGenDialog::process(AmEvent* event)
{
AmPluginEvent* plugin_event = dynamic_cast<AmPluginEvent*>(event);
if(plugin_event && plugin_event->name == "timer_timeout" &&
plugin_event->data.get(0).asInt() == CALL_TIMER) {
time(&disconnect_ts);
report(CGDisconnect);
play_list.close();
setInOut(NULL,NULL);
setStopped();
dlg.bye();
}
else
AmSession::process(event);
}
void CallGenDialog::onBye(const AmSipRequest& req) {
time(&disconnect_ts);
report(CGDisconnect);
play_list.close();
setInOut(NULL,NULL);
setStopped();
}