mirror of https://github.com/sipwise/sems.git
git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@1039 8eb893ce-cfd4-0310-b710-fb5ebe64c474sayer/1.4-spce2.6
parent
b38fe059b8
commit
c93ea5738f
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSM.h"
|
||||
#include "AmConfig.h"
|
||||
#include "AmUtils.h"
|
||||
#include "AmPlugIn.h"
|
||||
#include "log.h"
|
||||
#include "AmConfigReader.h"
|
||||
#include "DSMDialog.h"
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
// session creator export
|
||||
extern "C" void* session_factory_create() {
|
||||
return DSMFactory::instance();
|
||||
}
|
||||
|
||||
DSMFactory* DSMFactory::_instance=0;
|
||||
|
||||
DSMFactory* DSMFactory::instance()
|
||||
{
|
||||
if(_instance == NULL)
|
||||
_instance = new DSMFactory(MOD_NAME);
|
||||
return _instance;
|
||||
}
|
||||
|
||||
string DSMFactory::InboundStartDiag;
|
||||
string DSMFactory::OutboundStartDiag;
|
||||
|
||||
DSMFactory::DSMFactory(const string& _app_name)
|
||||
: AmSessionFactory(_app_name),
|
||||
loaded(false)
|
||||
{
|
||||
}
|
||||
|
||||
int DSMFactory::onLoad()
|
||||
{
|
||||
if (loaded)
|
||||
return 0;
|
||||
loaded = true;
|
||||
|
||||
AmConfigReader cfg;
|
||||
if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf")))
|
||||
return -1;
|
||||
|
||||
// get application specific global parameters
|
||||
configureModule(cfg);
|
||||
|
||||
vector<string> prompts_files =
|
||||
explode(cfg.getParameter("load_prompts"), ",");
|
||||
for (vector<string>::iterator it=
|
||||
prompts_files.begin(); it != prompts_files.end(); it++) {
|
||||
DBG("loading prompts from '%s'\n", it->c_str());
|
||||
std::ifstream ifs(it->c_str());
|
||||
string s;
|
||||
while (ifs.good() && !ifs.eof()) {
|
||||
getline(ifs, s);
|
||||
if (s.length() && s.find_first_not_of(" \t")!= string::npos &&
|
||||
s[s.find_first_not_of(" \t")] != '#') {
|
||||
vector<string> p=explode(s, "=");
|
||||
if (p.size()==2) {
|
||||
prompts.setPrompt(p[0], p[1], MOD_NAME);
|
||||
DBG("added prompt '%s' as '%s'\n",
|
||||
p[0].c_str(), p[1].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string DiagPath = cfg.getParameter("diag_path");
|
||||
if (DiagPath.length() && DiagPath[DiagPath.length()-1] != '/')
|
||||
DiagPath += '/';
|
||||
|
||||
string ModPath = cfg.getParameter("mod_path");
|
||||
|
||||
string LoadDiags = cfg.getParameter("load_diags");
|
||||
vector<string> diags_names = explode(LoadDiags, ",");
|
||||
for (vector<string>::iterator it=
|
||||
diags_names.begin(); it != diags_names.end(); it++) {
|
||||
if (!diags.loadFile(DiagPath+*it+".dsm", *it, ModPath)) {
|
||||
ERROR("loading %s from %s\n",
|
||||
it->c_str(), (DiagPath+*it+".dsm").c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
InboundStartDiag = cfg.getParameter("inbound_start_diag");
|
||||
if (InboundStartDiag.empty()) {
|
||||
INFO("no 'inbound_start_diag' set in config. inbound calls disabled.\n");
|
||||
}
|
||||
OutboundStartDiag = cfg.getParameter("outbound_start_diag");
|
||||
if (OutboundStartDiag.empty()) {
|
||||
INFO("no 'outbound_start_diag' set in config. outbound calls disabled.\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
AmSession* DSMFactory::onInvite(const AmSipRequest& req)
|
||||
{
|
||||
if (InboundStartDiag.empty()) {
|
||||
ERROR("no inbound calls allowed\n");
|
||||
throw AmSession::Exception(488, "Not Acceptable Here");
|
||||
}
|
||||
return new DSMDialog(prompts, diags, InboundStartDiag, NULL);
|
||||
}
|
||||
|
||||
AmSession* DSMFactory::onInvite(const AmSipRequest& req,
|
||||
AmArg& session_params)
|
||||
{
|
||||
if (OutboundStartDiag.empty()) {
|
||||
ERROR("no outbound calls allowed\n");
|
||||
throw AmSession::Exception(488, "Not Acceptable Here");
|
||||
}
|
||||
|
||||
UACAuthCred* cred = NULL;
|
||||
if (session_params.getType() == AmArg::AObject) {
|
||||
ArgObject* cred_obj = session_params.asObject();
|
||||
if (cred_obj)
|
||||
cred = dynamic_cast<UACAuthCred*>(cred_obj);
|
||||
}
|
||||
|
||||
AmSession* s = new DSMDialog(prompts, diags, OutboundStartDiag, cred);
|
||||
|
||||
if (NULL == cred) {
|
||||
WARN("discarding unknown session parameters.\n");
|
||||
} else {
|
||||
AmSessionEventHandlerFactory* uac_auth_f =
|
||||
AmPlugIn::instance()->getFactory4Seh("uac_auth");
|
||||
if (uac_auth_f != NULL) {
|
||||
DBG("UAC Auth enabled for new DSM session.\n");
|
||||
AmSessionEventHandler* h = uac_auth_f->getHandler(s);
|
||||
if (h != NULL )
|
||||
s->addHandler(h);
|
||||
} else {
|
||||
ERROR("uac_auth interface not accessible. "
|
||||
"Load uac_auth for authenticated dialout.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
|
||||
#ifndef _DSM_H_
|
||||
#define _DSM_H_
|
||||
|
||||
#include "AmApi.h"
|
||||
|
||||
#include "AmPromptCollection.h"
|
||||
|
||||
#include "DSMStateEngine.h"
|
||||
#include "DSMStateDiagramCollection.h"
|
||||
#include "DSMSession.h"
|
||||
|
||||
|
||||
#include <string>
|
||||
using std::string;
|
||||
|
||||
#include <memory>
|
||||
|
||||
/** \brief Factory for announcement sessions */
|
||||
class DSMFactory
|
||||
: public AmSessionFactory
|
||||
{
|
||||
AmPromptCollection prompts;
|
||||
DSMStateDiagramCollection diags;
|
||||
|
||||
static string InboundStartDiag;
|
||||
static string OutboundStartDiag;
|
||||
|
||||
static DSMFactory* _instance;
|
||||
DSMFactory(const string& _app_name);
|
||||
bool loaded;
|
||||
public:
|
||||
static DSMFactory* instance();
|
||||
|
||||
|
||||
int onLoad();
|
||||
AmSession* onInvite(const AmSipRequest& req);
|
||||
AmSession* onInvite(const AmSipRequest& req,
|
||||
AmArg& session_params);
|
||||
};
|
||||
|
||||
#endif
|
||||
// Local Variables:
|
||||
// mode:C++
|
||||
// End:
|
||||
|
||||
@ -0,0 +1,394 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSMChartReader.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <dlfcn.h> // dlopen & friends
|
||||
|
||||
#include <vector>
|
||||
using std::vector;
|
||||
|
||||
DSMChartReader::DSMChartReader() {
|
||||
}
|
||||
|
||||
DSMChartReader::~DSMChartReader() {
|
||||
}
|
||||
|
||||
bool DSMChartReader::is_wsp(const char c) {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r';
|
||||
}
|
||||
|
||||
bool DSMChartReader::is_snt(const char c) {
|
||||
return c== ';' || c == '{' || c == '}';
|
||||
}
|
||||
|
||||
string DSMChartReader::getToken(string str, size_t& pos) {
|
||||
while (pos<str.length() && is_wsp(str[pos]))
|
||||
pos++;
|
||||
|
||||
size_t pos1 = pos;
|
||||
|
||||
if (is_snt(str[pos])) {
|
||||
string res = " ";
|
||||
res[0] = str[pos];
|
||||
pos++;
|
||||
return res;
|
||||
}
|
||||
char last_chr = ' ';
|
||||
while (pos1<str.length() && !is_wsp(str[pos1]) && !is_snt(str[pos1])) {
|
||||
if (str[pos1] == '"') {
|
||||
pos1++;
|
||||
while (pos1<str.length() && !((str[pos1] == '"') && (last_chr != '\\'))) {
|
||||
last_chr = str[pos1];
|
||||
pos1++;
|
||||
}
|
||||
}
|
||||
if (str[pos1] == '(') {
|
||||
int lvl = 0;
|
||||
pos1++;
|
||||
while (pos1<str.length() && (lvl || (str[pos1] != ')'))) {
|
||||
|
||||
if (str[pos1] == '(')
|
||||
lvl++;
|
||||
else if (str[pos1] == ')')
|
||||
lvl--;
|
||||
|
||||
if (str[pos1] == '"') {
|
||||
pos1++;
|
||||
while (pos1<str.length() && !((str[pos1] == '"') && (last_chr != '\\'))) {
|
||||
last_chr = str[pos1];
|
||||
pos1++;
|
||||
}
|
||||
}
|
||||
last_chr = str[pos1];
|
||||
pos1++;
|
||||
}
|
||||
}
|
||||
|
||||
pos1++;
|
||||
}
|
||||
|
||||
string res;
|
||||
if (str[pos] == '"')
|
||||
res = str.substr(pos+1, pos1-pos-2);
|
||||
else
|
||||
res = str.substr(pos, pos1-pos);
|
||||
|
||||
pos = pos1;
|
||||
return res;
|
||||
}
|
||||
|
||||
DSMAction* DSMChartReader::actionFromToken(const string& str) {
|
||||
for (vector<DSMModule*>::iterator it=
|
||||
mods.begin(); it!= mods.end(); it++) {
|
||||
DSMAction* a = (*it)->getAction(str);
|
||||
if (a) return a;
|
||||
}
|
||||
|
||||
return core_mod.getAction(str);
|
||||
}
|
||||
|
||||
DSMCondition* DSMChartReader::conditionFromToken(const string& str) {
|
||||
for (vector<DSMModule*>::iterator it=
|
||||
mods.begin(); it!= mods.end(); it++) {
|
||||
DSMCondition* c=(*it)->getCondition(str);
|
||||
if (c) return c;
|
||||
}
|
||||
return core_mod.getCondition(str);
|
||||
}
|
||||
|
||||
void splitCmd(const string& from_str,
|
||||
string& cmd, string& params) {
|
||||
size_t b_pos = from_str.find('(');
|
||||
if (b_pos != string::npos) {
|
||||
cmd = from_str.substr(0, b_pos);
|
||||
params = from_str.substr(b_pos + 1, from_str.rfind(')') - b_pos -1);
|
||||
} else
|
||||
cmd = from_str;
|
||||
}
|
||||
|
||||
bool DSMChartReader::importModule(const string& mod_cmd, const string& mod_path) {
|
||||
string cmd;
|
||||
string params;
|
||||
|
||||
splitCmd(mod_cmd, cmd, params);
|
||||
if (!params.length()) {
|
||||
ERROR("import needs module name\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
string fname = mod_path;
|
||||
if (fname.length() &&fname[fname.length()-1]!= '/')
|
||||
fname+='/';
|
||||
fname += params + ".so";
|
||||
|
||||
void* h_dl = dlopen(fname.c_str(),RTLD_NOW | RTLD_GLOBAL);
|
||||
if(!h_dl){
|
||||
ERROR("import module: %s: %s\n",fname.c_str(),dlerror());
|
||||
return false;
|
||||
}
|
||||
|
||||
SCFactoryCreate fc = NULL;
|
||||
if ((fc = (SCFactoryCreate)dlsym(h_dl,SC_FACTORY_EXPORT_STR)) == NULL) {
|
||||
ERROR("invalid SC module '%s'\n", fname.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
DSMModule* mod = (DSMModule*)fc();
|
||||
if (!mod) {
|
||||
ERROR("module '%s' did not return functions.\n",
|
||||
fname.c_str());
|
||||
return false;
|
||||
}
|
||||
mods.push_back(mod);
|
||||
DBG("loaded module '%s' from '%s'\n",
|
||||
params.c_str(), fname.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DSMChartReader::decode(DSMStateDiagram* e, const string& chart,
|
||||
const string& mod_path, DSMElemContainer* owner) {
|
||||
vector<DSMElement*> stack;
|
||||
size_t pos = 0;
|
||||
while (pos < chart.length()) {
|
||||
string token = getToken(chart, pos);
|
||||
if (!token.length())
|
||||
continue;
|
||||
|
||||
if (token.length()>6 && token.substr(0, 6) == "import") {
|
||||
if (!importModule(token, mod_path)) {
|
||||
ERROR("error loading module in '%s'\n",
|
||||
token.c_str());
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "initial") {
|
||||
stack.push_back(new AttribInitial());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "state") {
|
||||
stack.push_back(new State());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "transition") {
|
||||
stack.push_back(new DSMTransition());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stack.empty()) {
|
||||
if (token == ";")
|
||||
continue;
|
||||
ERROR("I do not understand '%s'\n", token.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
DSMElement* stack_top = &(*stack.back());
|
||||
|
||||
State* state = dynamic_cast<State*>(stack_top);
|
||||
if (state) {
|
||||
if (!state->name.length()) {
|
||||
// DBG("naming state '%s'\n", token.c_str());
|
||||
state->name = token;
|
||||
continue;
|
||||
}
|
||||
if (token == "enter") {
|
||||
stack.push_back(new ActionList(ActionList::AL_enter));
|
||||
continue;
|
||||
}
|
||||
if (token == "exit") {
|
||||
stack.push_back(new ActionList(ActionList::AL_exit));
|
||||
continue;
|
||||
}
|
||||
if (token == ";") {
|
||||
bool is_initial = false;
|
||||
stack.pop_back();
|
||||
if (!stack.empty()) {
|
||||
AttribInitial* ai = dynamic_cast<AttribInitial*>(&(*stack.back()));
|
||||
if (ai) {
|
||||
is_initial = true;
|
||||
stack.pop_back();
|
||||
delete ai;
|
||||
}
|
||||
}
|
||||
e->addState(*state, is_initial);
|
||||
delete state;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ActionList* al = dynamic_cast<ActionList*>(stack_top);
|
||||
if (al) {
|
||||
if (token == ";") {
|
||||
continue;
|
||||
}
|
||||
if (token == "{") {
|
||||
continue;
|
||||
}
|
||||
if ((token == "}") || (token == "->")) {
|
||||
stack.pop_back();
|
||||
if (stack.empty()) {
|
||||
ERROR("no item for action list\n");
|
||||
delete al;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (al->al_type == ActionList::AL_enter ||
|
||||
al->al_type == ActionList::AL_exit) {
|
||||
State* s = dynamic_cast<State*>(&(*stack.back()));
|
||||
if (!s) {
|
||||
ERROR("no State for action list\n");
|
||||
delete al;
|
||||
return false;
|
||||
}
|
||||
if (al->al_type == ActionList::AL_enter)
|
||||
s->pre_actions = al->actions;
|
||||
else if (al->al_type == ActionList::AL_exit)
|
||||
s->post_actions = al->actions;
|
||||
} else if (al->al_type == ActionList::AL_trans) {
|
||||
DSMTransition* t = dynamic_cast<DSMTransition*>(&(*stack.back()));
|
||||
if (!t) {
|
||||
ERROR("no DSMTransition for action list\n");
|
||||
delete al;
|
||||
return false;
|
||||
}
|
||||
t->actions = al->actions;
|
||||
} else {
|
||||
ERROR("internal: unknown transition list type\n");
|
||||
}
|
||||
delete al;
|
||||
continue;
|
||||
}
|
||||
|
||||
// token is action
|
||||
// DBG("adding action '%s'\n", token.c_str());
|
||||
DSMAction* a = actionFromToken(token);
|
||||
if (!a)
|
||||
return false;
|
||||
owner->transferElem(a);
|
||||
al->actions.push_back(a);
|
||||
continue;
|
||||
}
|
||||
|
||||
DSMConditionList* cl = dynamic_cast<DSMConditionList*>(stack_top);
|
||||
if (cl) {
|
||||
if (token == ";")
|
||||
continue;
|
||||
|
||||
if ((token == "{") || (token == "}")) {
|
||||
// readability
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((token == "/") || (token == "->")) {
|
||||
// end of condition list
|
||||
stack.pop_back();
|
||||
if (stack.empty()) {
|
||||
ERROR("no transition to apply conditions to\n");
|
||||
delete cl;
|
||||
return false;
|
||||
}
|
||||
DSMTransition* tr = dynamic_cast<DSMTransition*>(&(*stack.back()));
|
||||
if (!tr) {
|
||||
ERROR("no transition to apply conditions to\n");
|
||||
delete cl;
|
||||
return false;
|
||||
}
|
||||
|
||||
tr->precond = cl->conditions;
|
||||
delete cl;
|
||||
|
||||
// start AL_trans action list
|
||||
if (token == "/") {
|
||||
stack.push_back(new ActionList(ActionList::AL_trans));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// DBG("new condition: '%s'\n", token.c_str());
|
||||
DSMCondition* c = conditionFromToken(token);
|
||||
if (!c)
|
||||
return false;
|
||||
|
||||
owner->transferElem(c);
|
||||
cl->conditions.push_back(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
DSMTransition* tr = dynamic_cast<DSMTransition*>(stack_top);
|
||||
if (tr) {
|
||||
if (!tr->name.length()) {
|
||||
tr->name = token;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tr->from_state.length()) {
|
||||
tr->from_state = token;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "-->") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "->") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == "-") {
|
||||
stack.push_back(new DSMConditionList());
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((token == "-/") || (token == "/")) {
|
||||
stack.push_back(new ActionList(ActionList::AL_trans));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token == ";") {
|
||||
if (!e->addTransition(*tr)) {
|
||||
delete tr;
|
||||
return false;
|
||||
}
|
||||
delete tr;
|
||||
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!tr->to_state.length()) {
|
||||
tr->to_state = token;
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _DSMChartReader_h_
|
||||
#define _DSMChartReader_h_
|
||||
|
||||
#include "DSMStateEngine.h"
|
||||
#include "DSMCoreModule.h"
|
||||
#include "DSMElemContainer.h"
|
||||
|
||||
#include <string>
|
||||
using std::string;
|
||||
|
||||
class NamedAction : public DSMAction {
|
||||
public:
|
||||
NamedAction(const string& m_name) {
|
||||
name = m_name;
|
||||
}
|
||||
void execute(AmSession* sess) { };
|
||||
};
|
||||
|
||||
class AttribInitial : public DSMElement {
|
||||
public:
|
||||
AttribInitial() { }
|
||||
};
|
||||
|
||||
class AttribName : public DSMElement {
|
||||
public:
|
||||
AttribName(const string& m_name) { name = m_name; }
|
||||
};
|
||||
|
||||
class ActionList : public DSMElement {
|
||||
public:
|
||||
enum AL_type {
|
||||
AL_enter,
|
||||
AL_exit,
|
||||
AL_trans
|
||||
};
|
||||
|
||||
AL_type al_type;
|
||||
|
||||
ActionList(AL_type al_type)
|
||||
: al_type(al_type) { }
|
||||
|
||||
vector<DSMAction*> actions;
|
||||
};
|
||||
|
||||
struct DSMConditionList : public DSMElement {
|
||||
DSMConditionList() { }
|
||||
vector<DSMCondition*> conditions;
|
||||
};
|
||||
|
||||
class DSMChartReader {
|
||||
|
||||
bool is_wsp(const char c);
|
||||
bool is_snt(const char c);
|
||||
|
||||
string getToken(string str, size_t& pos);
|
||||
DSMAction* actionFromToken(const string& str);
|
||||
DSMCondition* conditionFromToken(const string& str);
|
||||
|
||||
bool importModule(const string& mod_cmd, const string& mod_path);
|
||||
vector<DSMModule*> mods;
|
||||
DSMCoreModule core_mod;
|
||||
|
||||
public:
|
||||
DSMChartReader();
|
||||
~DSMChartReader();
|
||||
bool decode(DSMStateDiagram* e, const string& chart,
|
||||
const string& mod_path, DSMElemContainer* owner);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,583 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSMCoreModule.h"
|
||||
#include "DSMSession.h"
|
||||
#include "AmSession.h"
|
||||
#include "AmUtils.h"
|
||||
|
||||
DSMCoreModule::DSMCoreModule() {
|
||||
}
|
||||
|
||||
string trim(string const& str,char const* sepSet)
|
||||
{
|
||||
string::size_type const first = str.find_first_not_of(sepSet);
|
||||
return ( first==string::npos )
|
||||
? std::string() :
|
||||
str.substr(first, str.find_last_not_of(sepSet)-first+1);
|
||||
}
|
||||
|
||||
void DSMCoreModule::splitCmd(const string& from_str,
|
||||
string& cmd, string& params) {
|
||||
size_t b_pos = from_str.find('(');
|
||||
if (b_pos != string::npos) {
|
||||
cmd = from_str.substr(0, b_pos);
|
||||
params = from_str.substr(b_pos + 1, from_str.rfind(')') - b_pos -1);
|
||||
} else
|
||||
cmd = from_str;
|
||||
}
|
||||
|
||||
DSMAction* DSMCoreModule::getAction(const string& from_str) {
|
||||
string cmd;
|
||||
string params;
|
||||
splitCmd(from_str, cmd, params);
|
||||
|
||||
#define DEF_CMD(cmd_name, class_name) \
|
||||
\
|
||||
if (cmd == cmd_name) { \
|
||||
class_name * a = \
|
||||
new class_name(params); \
|
||||
a->name = from_str; \
|
||||
return a; \
|
||||
}
|
||||
|
||||
DEF_CMD("repost", SCRepostAction);
|
||||
DEF_CMD("jumpFSM", SCJumpFSMAction);
|
||||
DEF_CMD("callFSM", SCCallFSMAction);
|
||||
DEF_CMD("returnFSM", SCReturnFSMAction);
|
||||
|
||||
DEF_CMD("stop", SCStopAction);
|
||||
|
||||
DEF_CMD("playPrompt", SCPlayPromptAction);
|
||||
DEF_CMD("playFile", SCPlayFileAction);
|
||||
DEF_CMD("recordFile", SCRecordFileAction);
|
||||
DEF_CMD("stopRecord", SCStopRecordAction);
|
||||
DEF_CMD("closePlaylist", SCClosePlaylistAction);
|
||||
|
||||
DEF_CMD("set", SCSetAction);
|
||||
DEF_CMD("append", SCAppendAction);
|
||||
DEF_CMD("log", SCLogAction);
|
||||
|
||||
DEF_CMD("setTimer", SCSetTimerAction);
|
||||
|
||||
if (cmd == "DI") {
|
||||
SCDIAction * a = new SCDIAction(params, false);
|
||||
a->name = from_str;
|
||||
return a;
|
||||
}
|
||||
|
||||
if (cmd == "DIgetResult") {
|
||||
SCDIAction * a = new SCDIAction(params, true);
|
||||
a->name = from_str;
|
||||
return a;
|
||||
}
|
||||
|
||||
ERROR("could not find action named '%s'\n", cmd.c_str());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DSMCondition* DSMCoreModule::getCondition(const string& from_str) {
|
||||
string cmd;
|
||||
string params;
|
||||
splitCmd(from_str, cmd, params);
|
||||
|
||||
if (cmd == "keyPress") {
|
||||
DSMCondition* c = new DSMCondition();
|
||||
c->name = "key pressed: " + params;
|
||||
c->type = DSMCondition::Key;
|
||||
c->params["key"] = params;
|
||||
return c;
|
||||
}
|
||||
|
||||
if (cmd == "test")
|
||||
return new TestDSMCondition(params, DSMCondition::Any);
|
||||
|
||||
if (cmd == "keyTest")
|
||||
return new TestDSMCondition(params, DSMCondition::Key);
|
||||
|
||||
if (cmd == "timerTest")
|
||||
return new TestDSMCondition(params, DSMCondition::Timer);
|
||||
|
||||
if (cmd == "noAudioTest")
|
||||
return new TestDSMCondition(params, DSMCondition::NoAudio);
|
||||
|
||||
if (cmd == "hangup")
|
||||
return new TestDSMCondition(params, DSMCondition::Hangup);
|
||||
|
||||
ERROR("could not find condition for '%s'\n", cmd.c_str());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
inline string resolveVars(const string s, AmSession* sess,
|
||||
DSMSession* sc_sess, map<string,string>* event_params) {
|
||||
if (s.length()) {
|
||||
switch(s[0]) {
|
||||
case '$': return sc_sess->var[s.substr(1)];
|
||||
case '#':
|
||||
if (event_params)
|
||||
return (*event_params)[s.substr(1)];
|
||||
else
|
||||
return string();
|
||||
case '@': {
|
||||
string s1 = s.substr(1);
|
||||
if (s1 == "local_tag")
|
||||
return sess->getLocalTag();
|
||||
else if (s1 == "user")
|
||||
return sess->dlg.user;
|
||||
else if (s1 == "domain")
|
||||
return sess->dlg.domain;
|
||||
else if (s1 == "remote_tag")
|
||||
return sess->getRemoteTag();
|
||||
else if (s1 == "callid")
|
||||
return sess->getCallID();
|
||||
else if (s1 == "local_uri")
|
||||
return sess->dlg.local_uri;
|
||||
else if (s1 == "remote_uri")
|
||||
return sess->dlg.remote_uri;
|
||||
else
|
||||
return string();
|
||||
}
|
||||
default: return trim(s, "\"");
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#define GET_SCSESSION() \
|
||||
DSMSession* sc_sess = dynamic_cast<DSMSession*>(sess); \
|
||||
if (!sc_sess) { \
|
||||
ERROR("wrong session type\n"); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
|
||||
bool SCPlayPromptAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
sc_sess->playPrompt(resolveVars(arg, sess, sc_sess, event_params));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SCPlayFileAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
|
||||
bool loop =
|
||||
resolveVars(par2, sess, sc_sess, event_params) == "true";
|
||||
DBG("par1 = '%s', par2 = %s\n", par1.c_str(), par2.c_str());
|
||||
sc_sess->playFile(resolveVars(par1, sess, sc_sess, event_params),
|
||||
loop);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SCRecordFileAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
sc_sess->recordFile(resolveVars(arg, sess, sc_sess, event_params));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SCStopRecordAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
sc_sess->stopRecord();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SCClosePlaylistAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
bool notify =
|
||||
resolveVars(arg, sess, sc_sess, event_params) == "true";
|
||||
sc_sess->closePlaylist(notify);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SCStopAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
if (resolveVars(arg, sess, sc_sess, event_params) == "true") {
|
||||
DBG("sending bye\n");
|
||||
sess->dlg.bye();
|
||||
}
|
||||
sess->setStopped();
|
||||
return false;
|
||||
}
|
||||
|
||||
#define DEF_SCModActionExec(clsname) \
|
||||
\
|
||||
bool clsname::execute(AmSession* sess, \
|
||||
DSMCondition::EventType event, \
|
||||
map<string,string>* event_params) { \
|
||||
return true; \
|
||||
} \
|
||||
|
||||
DEF_SCModActionExec(SCRepostAction);
|
||||
DSMAction::SEAction SCRepostAction::getSEAction(string& param) {
|
||||
return Repost;
|
||||
}
|
||||
|
||||
DEF_SCModActionExec(SCJumpFSMAction);
|
||||
DSMAction::SEAction SCJumpFSMAction::getSEAction(string& param) {
|
||||
param = arg;
|
||||
return Jump;
|
||||
}
|
||||
|
||||
DEF_SCModActionExec(SCCallFSMAction);
|
||||
DSMAction::SEAction SCCallFSMAction::getSEAction(string& param) {
|
||||
param = arg;
|
||||
return Call;
|
||||
}
|
||||
|
||||
DEF_SCModActionExec(SCReturnFSMAction);
|
||||
DSMAction::SEAction SCReturnFSMAction::getSEAction(string& param) {
|
||||
return Return;
|
||||
}
|
||||
|
||||
#undef DEF_SCModActionExec
|
||||
|
||||
|
||||
CONST_TwoParAction(SCPlayFileAction, ",", true);
|
||||
|
||||
CONST_TwoParAction(SCLogAction, ",", false);
|
||||
bool SCLogAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
|
||||
unsigned int lvl;
|
||||
if (str2i(resolveVars(par1, sess, sc_sess, event_params), lvl)) {
|
||||
ERROR("unknown log level '%s'\n", par1.c_str());
|
||||
return false;
|
||||
}
|
||||
string l_line = resolveVars(par2, sess, sc_sess, event_params).c_str();
|
||||
_LOG((int)lvl, "FSM: %s %s\n", (par2 != l_line)?par2.c_str():"",
|
||||
l_line.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
CONST_TwoParAction(SCSetAction,"=", false);
|
||||
bool SCSetAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
string var_name = (par1.length() && par1[0] == '$')?
|
||||
par1.substr(1) : par1;
|
||||
|
||||
sc_sess->var[var_name] = resolveVars(par2, sess, sc_sess, event_params);
|
||||
DBG("set variable '%s'='%s'\n",
|
||||
var_name.c_str(), sc_sess->var[var_name].c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
CONST_TwoParAction(SCAppendAction,",", false);
|
||||
bool SCAppendAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
|
||||
string var_name = (par1.length() && par1[0] == '$')?
|
||||
par1.substr(1) : par1;
|
||||
|
||||
sc_sess->var[var_name] += resolveVars(par2, sess, sc_sess, event_params);
|
||||
|
||||
DBG("$%s now '%s'\n",
|
||||
var_name.c_str(), sc_sess->var[var_name].c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
CONST_TwoParAction(SCSetTimerAction,",", false);
|
||||
bool SCSetTimerAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
|
||||
unsigned int timerid;
|
||||
if (str2i(resolveVars(par1, sess, sc_sess, event_params), timerid)) {
|
||||
ERROR("timer id '%s' not decipherable\n",
|
||||
resolveVars(par1, sess, sc_sess, event_params).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int timeout;
|
||||
if (str2i(resolveVars(par2, sess, sc_sess, event_params), timeout)) {
|
||||
ERROR("timeout value '%s' not decipherable\n",
|
||||
resolveVars(par2, sess, sc_sess, event_params).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
DBG("setting timer %u with timeout %u\n", timerid, timeout);
|
||||
AmDynInvokeFactory* user_timer_fact =
|
||||
AmPlugIn::instance()->getFactory4Di("user_timer");
|
||||
|
||||
if(!user_timer_fact) {
|
||||
ERROR("load sess_timer module for timers.\n");
|
||||
return false;
|
||||
}
|
||||
AmDynInvoke* user_timer = user_timer_fact->getInstance();
|
||||
if(!user_timer) {
|
||||
ERROR("load sess_timer module for timers.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AmArg di_args,ret;
|
||||
di_args.push((int)timerid);
|
||||
di_args.push((int)timeout); // in seconds
|
||||
di_args.push(sess->getLocalTag().c_str());
|
||||
user_timer->invoke("setTimer", di_args, ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// TODO: replace with real expression matching
|
||||
TestDSMCondition::TestDSMCondition(const string& expr, DSMCondition::EventType evt) {
|
||||
|
||||
type = evt;
|
||||
|
||||
if (expr.empty()) {
|
||||
ttype = Always;
|
||||
return;
|
||||
}
|
||||
|
||||
ttype = None;
|
||||
|
||||
size_t p = expr.find("==");
|
||||
size_t p2;
|
||||
if (p != string::npos) {
|
||||
ttype = Eq; p2 = p+2;
|
||||
} else {
|
||||
p = expr.find("!=");
|
||||
if (p != string::npos) {
|
||||
ttype = Neq; p2 = p+2;
|
||||
} else {
|
||||
p = expr.find("<");
|
||||
if (p != string::npos) {
|
||||
ttype = Less; p2 = p+1;
|
||||
} else {
|
||||
p = expr.find(">");
|
||||
if (p != string::npos) {
|
||||
ttype = Gt; p2 = p+1;
|
||||
} else {
|
||||
ERROR("expression '%s' not understood\n",
|
||||
expr.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lhs = trim(expr.substr(0, p), " ");
|
||||
rhs = trim(expr.substr(p2,expr.length()-p2+1), " ");
|
||||
|
||||
name = expr;
|
||||
}
|
||||
|
||||
bool TestDSMCondition::match(AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
if (ttype == None || (type != DSMCondition::Any && type != event))
|
||||
return false;
|
||||
|
||||
if (ttype == Always)
|
||||
return true;
|
||||
|
||||
DSMSession* sc_sess = dynamic_cast<DSMSession*>(sess);
|
||||
if (!sc_sess) {
|
||||
ERROR("wrong session type\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
string l;
|
||||
string r;
|
||||
if (lhs.length() > 5 &&
|
||||
(lhs.substr(0, 4) == "len(") && lhs[lhs.length()-1] == ')') {
|
||||
l = int2str(resolveVars(lhs.substr(4, lhs.length()-5), sess, sc_sess, event_params).length());
|
||||
} else {
|
||||
l = resolveVars(lhs, sess, sc_sess, event_params);
|
||||
}
|
||||
if (rhs.length() > 5 &&
|
||||
rhs.substr(0, 4) == "len(" && rhs[rhs.length()-1] == ')') {
|
||||
r = resolveVars(rhs.substr(4, rhs.length()-5), sess, sc_sess, event_params).length();
|
||||
} else {
|
||||
r = resolveVars(rhs, sess, sc_sess, event_params);
|
||||
}
|
||||
|
||||
// string r = resolveVars(rhs, sess, sc_sess, event_params);
|
||||
|
||||
DBG("test '%s' vs '%s'\n", l.c_str(), r.c_str());
|
||||
|
||||
switch (ttype) {
|
||||
case Eq: return l == r;
|
||||
case Neq: return l != r;
|
||||
case Less: {
|
||||
char* endptr = NULL;
|
||||
long l_i = strtol(l.c_str(), &endptr, 10);
|
||||
if (endptr && *endptr == '\0') {
|
||||
long r_i = strtol(r.c_str(), &endptr, 10);
|
||||
if (endptr && *endptr == '\0')
|
||||
return l_i < r_i;
|
||||
}
|
||||
return l < r;
|
||||
}
|
||||
case Gt: {
|
||||
char* endptr = NULL;
|
||||
long l_i = strtol(l.c_str(), &endptr, 10);
|
||||
if (endptr && *endptr == '\0') {
|
||||
long r_i = strtol(r.c_str(), &endptr, 10);
|
||||
if (endptr && *endptr == '\0')
|
||||
return l_i > r_i;
|
||||
}
|
||||
return l > r;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SCDIAction::SCDIAction(const string& arg, bool get_res)
|
||||
: get_res(get_res) {
|
||||
params = explode(arg,",");
|
||||
if (params.size()<2) {
|
||||
ERROR("DI needs at least: mod_name, "
|
||||
"function_name\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool SCDIAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
|
||||
if (params.size() < 2) {
|
||||
ERROR("DI needs at least: mod_name, "
|
||||
"function_name (in '%s'\n", name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<string>::iterator p_it=params.begin();
|
||||
string fact_name = trim(*p_it, " \"");
|
||||
AmDynInvokeFactory* fact =
|
||||
AmPlugIn::instance()->getFactory4Di(fact_name);
|
||||
|
||||
if(!fact) {
|
||||
ERROR("load module for factory '%s'.\n", fact_name.c_str());
|
||||
return false;
|
||||
}
|
||||
AmDynInvoke* di_inst = fact->getInstance();
|
||||
if(!di_inst) {
|
||||
ERROR("load module for factory '%s'\n", fact_name.c_str());
|
||||
return false;
|
||||
}
|
||||
p_it++;
|
||||
|
||||
string func_name = trim(*p_it, " \"");
|
||||
p_it++;
|
||||
|
||||
AmArg di_args;
|
||||
|
||||
while (p_it != params.end()) {
|
||||
string p = trim(*p_it, " \t");
|
||||
if (p.length() && p[0] == '"') {
|
||||
di_args.push(trim(p,"\"").c_str());
|
||||
} else if (p.length() > 5 &&
|
||||
p.substr(0, 5) =="(int)") {
|
||||
p = resolveVars(p.substr(5), sess, sc_sess, event_params);
|
||||
char* endptr = NULL;
|
||||
long p_i = strtol(p.c_str(), &endptr, 10);
|
||||
if (endptr && *endptr == '\0') {
|
||||
di_args.push((int)p_i);
|
||||
} else {
|
||||
ERROR("converting value '%s' to int\n",
|
||||
p.c_str());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
di_args.push(resolveVars(p, sess, sc_sess, event_params).c_str());
|
||||
}
|
||||
p_it++;
|
||||
}
|
||||
|
||||
sc_sess->di_res.clear();
|
||||
DBG("executing DI function '%s'\n", func_name.c_str());
|
||||
try {
|
||||
di_inst->invoke(func_name, di_args, sc_sess->di_res);
|
||||
} catch (const AmDynInvoke::NotImplemented& ni) {
|
||||
ERROR("not implemented DI function '%s'\n",
|
||||
ni.what.c_str());
|
||||
return false;
|
||||
} catch (const AmArg::OutOfBoundsException& oob) {
|
||||
ERROR("out of bounds in DI call '%s'\n",
|
||||
name.c_str());
|
||||
return false;
|
||||
} catch (const AmArg::TypeMismatchException& oob) {
|
||||
ERROR("type mismatch in DI call '%s'\n",
|
||||
name.c_str());
|
||||
return false;
|
||||
} catch (...) {
|
||||
ERROR("unexpected Exception in DI call '%s'\n",
|
||||
name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_res) {
|
||||
// rudimentary variables conversion...
|
||||
if (isArgCStr(sc_sess->di_res))
|
||||
sc_sess->var["DI_res"] = sc_sess->di_res.asCStr();
|
||||
else if (isArgInt(sc_sess->di_res))
|
||||
sc_sess->var["DI_res"] = int2str(sc_sess->di_res.asInt());
|
||||
else if (isArgArray(sc_sess->di_res)) {
|
||||
// copy results to $DI_res0..$DI_resn
|
||||
for (size_t i=0;i<sc_sess->di_res.size();i++) {
|
||||
switch (sc_sess->di_res.get(i).getType()) {
|
||||
case AmArg::CStr: {
|
||||
sc_sess->var["DI_res"+int2str(i)] =
|
||||
sc_sess->di_res.get(i).asCStr();
|
||||
} break;
|
||||
case AmArg::Int: {
|
||||
sc_sess->var["DI_res"+int2str(i)] =
|
||||
int2str(sc_sess->di_res.get(i).asInt());
|
||||
} break;
|
||||
default: {
|
||||
ERROR("unsupported AmArg return type!");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ERROR("unsupported AmArg return type!");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _DSM_CORE_MODULE_H
|
||||
#define _DSM_CORE_MODULE_H
|
||||
#include "DSMModule.h"
|
||||
#include "DSMStateEngine.h"
|
||||
|
||||
#include <string>
|
||||
using std::string;
|
||||
#include <map>
|
||||
using std::map;
|
||||
|
||||
class AmSession;
|
||||
class DSMSession;
|
||||
|
||||
class DSMCoreModule
|
||||
: public DSMModule {
|
||||
|
||||
void splitCmd(const string& from_str,
|
||||
string& cmd, string& params);
|
||||
public:
|
||||
DSMCoreModule();
|
||||
|
||||
DSMAction* getAction(const string& from_str);
|
||||
DSMCondition* getCondition(const string& from_str);
|
||||
};
|
||||
|
||||
DEF_SCStrArgAction(SCPlayPromptAction);
|
||||
DEF_SCStrArgAction(SCRecordFileAction);
|
||||
DEF_SCStrArgAction(SCStopRecordAction);
|
||||
DEF_SCStrArgAction(SCClosePlaylistAction);
|
||||
DEF_SCStrArgAction(SCStopAction);
|
||||
|
||||
DEF_SCModSEStrArgAction(SCRepostAction);
|
||||
DEF_SCModSEStrArgAction(SCJumpFSMAction);
|
||||
DEF_SCModSEStrArgAction(SCCallFSMAction);
|
||||
DEF_SCModSEStrArgAction(SCReturnFSMAction);
|
||||
|
||||
|
||||
DEF_TwoParAction(SCSetAction);
|
||||
DEF_TwoParAction(SCAppendAction);
|
||||
DEF_TwoParAction(SCSetTimerAction);
|
||||
DEF_TwoParAction(SCLogAction);
|
||||
DEF_TwoParAction(SCPlayFileAction);
|
||||
|
||||
class SCDIAction
|
||||
: public DSMAction {
|
||||
vector<string> params;
|
||||
bool get_res;
|
||||
public:
|
||||
SCDIAction(const string& arg, bool get_res);
|
||||
bool execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params);
|
||||
};
|
||||
|
||||
// TODO: replace with real expression matching
|
||||
class TestDSMCondition
|
||||
: public DSMCondition {
|
||||
enum CondType {
|
||||
None,
|
||||
Always,
|
||||
Eq,
|
||||
Neq,
|
||||
Less,
|
||||
Gt
|
||||
};
|
||||
string lhs;
|
||||
string rhs;
|
||||
CondType ttype;
|
||||
|
||||
public:
|
||||
TestDSMCondition(const string& expr, DSMCondition::EventType e);
|
||||
bool match(AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params);
|
||||
};
|
||||
|
||||
string resolveVars(const string s, AmSession* sess,
|
||||
DSMSession* sc_sess, map<string,string>* event_params);
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSMDialog.h"
|
||||
#include "AmUtils.h"
|
||||
|
||||
DSMDialog::DSMDialog(AmPromptCollection& prompts,
|
||||
DSMStateDiagramCollection& diags,
|
||||
const string& startDiagName,
|
||||
UACAuthCred* credentials)
|
||||
: prompts(prompts), diags(diags), startDiagName(startDiagName),
|
||||
playlist(this), cred(credentials),
|
||||
rec_file(NULL)
|
||||
{
|
||||
diags.addToEngine(&engine);
|
||||
}
|
||||
|
||||
DSMDialog::~DSMDialog()
|
||||
{
|
||||
for (vector<AmAudioFile*>::iterator it=
|
||||
audiofiles.begin();it!=audiofiles.end();it++)
|
||||
delete *it;
|
||||
}
|
||||
|
||||
void DSMDialog::onSessionStart(const AmSipRequest& req)
|
||||
{
|
||||
DBG("DSMDialog::onSessionStart\n");
|
||||
startSession();
|
||||
}
|
||||
|
||||
void DSMDialog::onSessionStart(const AmSipReply& rep)
|
||||
{
|
||||
DBG("DSMDialog::onSessionStart (SEMS originator mode)\n");
|
||||
startSession();
|
||||
}
|
||||
|
||||
void DSMDialog::startSession(){
|
||||
engine.init(this, startDiagName);
|
||||
|
||||
setReceiving(true);
|
||||
|
||||
if (!getInput())
|
||||
setInput(&playlist);
|
||||
|
||||
setOutput(&playlist);
|
||||
}
|
||||
|
||||
void DSMDialog::onDtmf(int event, int duration_msec) {
|
||||
DBG("* Got DTMF key %d duration %d\n",
|
||||
event, duration_msec);
|
||||
|
||||
map<string, string> params;
|
||||
params["key"] = int2str(event);
|
||||
params["duration"] = int2str(duration_msec);
|
||||
engine.runEvent(this, DSMCondition::Key, ¶ms);
|
||||
}
|
||||
|
||||
void DSMDialog::onBye(const AmSipRequest& req)
|
||||
{
|
||||
DBG("onBye\n");
|
||||
engine.runEvent(this, DSMCondition::Hangup, NULL);
|
||||
}
|
||||
|
||||
void DSMDialog::process(AmEvent* event)
|
||||
{
|
||||
|
||||
AmAudioEvent* audio_event = dynamic_cast<AmAudioEvent*>(event);
|
||||
if(audio_event &&
|
||||
((audio_event->event_id == AmAudioEvent::cleared) ||
|
||||
(audio_event->event_id == AmAudioEvent::noAudio))){
|
||||
// todo: run event
|
||||
engine.runEvent(this, DSMCondition::NoAudio, NULL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AmPluginEvent* plugin_event = dynamic_cast<AmPluginEvent*>(event);
|
||||
if(plugin_event && plugin_event->name == "timer_timeout") {
|
||||
int timer_id = plugin_event->data.get(0).asInt();
|
||||
map<string, string> params;
|
||||
params["id"] = int2str(timer_id);
|
||||
engine.runEvent(this, DSMCondition::Timer, ¶ms);
|
||||
}
|
||||
|
||||
AmSession::process(event);
|
||||
}
|
||||
|
||||
inline UACAuthCred* DSMDialog::getCredentials() {
|
||||
return cred.get();
|
||||
}
|
||||
|
||||
void DSMDialog::playPrompt(const string& name) {
|
||||
DBG("playing prompt '%s'\n", name.c_str());
|
||||
prompts.addToPlaylist(name, (long)this, playlist);
|
||||
}
|
||||
|
||||
void DSMDialog::closePlaylist(bool notify) {
|
||||
DBG("close playlist\n");
|
||||
playlist.close(notify);
|
||||
}
|
||||
|
||||
void DSMDialog::playFile(const string& name, bool loop) {
|
||||
AmAudioFile* af = new AmAudioFile();
|
||||
if(af->open(name,AmAudioFile::Read)) {
|
||||
ERROR("audio file '%s' could not be opened for reading.\n",
|
||||
name.c_str());
|
||||
delete af;
|
||||
return;
|
||||
}
|
||||
if (loop)
|
||||
af->loop.set(true);
|
||||
|
||||
playlist.addToPlaylist(new AmPlaylistItem(af, NULL));
|
||||
audiofiles.push_back(af);
|
||||
}
|
||||
|
||||
void DSMDialog::recordFile(const string& name) {
|
||||
if (rec_file)
|
||||
stopRecord();
|
||||
|
||||
DBG("start record to '%s'\n", name.c_str());
|
||||
rec_file = new AmAudioFile();
|
||||
if(rec_file->open(name,AmAudioFile::Write)) {
|
||||
ERROR("audio file '%s' could not be opened for recording.\n",
|
||||
name.c_str());
|
||||
delete rec_file;
|
||||
rec_file = NULL;
|
||||
return;
|
||||
}
|
||||
setInput(rec_file);
|
||||
}
|
||||
|
||||
void DSMDialog::stopRecord() {
|
||||
if (rec_file) {
|
||||
setInput(&playlist);
|
||||
rec_file->close();
|
||||
delete rec_file;
|
||||
rec_file = NULL;
|
||||
} else {
|
||||
WARN("stopRecord: we are not recording\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _DSM_DIALOG_H
|
||||
#define _DSM_DIALOG_H
|
||||
#include "AmSession.h"
|
||||
#include "AmPromptCollection.h"
|
||||
|
||||
#include "ampi/UACAuthAPI.h"
|
||||
|
||||
#include "DSMSession.h"
|
||||
#include "DSMStateEngine.h"
|
||||
#include "DSMStateDiagramCollection.h"
|
||||
|
||||
class DSMDialog : public AmSession,
|
||||
public DSMSession,
|
||||
public CredentialHolder
|
||||
{
|
||||
std::auto_ptr<UACAuthCred> cred;
|
||||
|
||||
DSMStateEngine engine;
|
||||
AmPromptCollection& prompts;
|
||||
DSMStateDiagramCollection& diags;
|
||||
string startDiagName;
|
||||
AmPlaylist playlist;
|
||||
|
||||
vector<AmAudioFile*> audiofiles;
|
||||
AmAudioFile* rec_file;
|
||||
|
||||
public:
|
||||
DSMDialog(AmPromptCollection& prompts,
|
||||
DSMStateDiagramCollection& diags,
|
||||
const string& startDiagName,
|
||||
UACAuthCred* credentials = NULL);
|
||||
~DSMDialog();
|
||||
|
||||
void onSessionStart(const AmSipRequest& req);
|
||||
void onSessionStart(const AmSipReply& rep);
|
||||
void startSession();
|
||||
void onBye(const AmSipRequest& req);
|
||||
void onDtmf(int event, int duration_msec);
|
||||
|
||||
void process(AmEvent* event);
|
||||
|
||||
UACAuthCred* getCredentials();
|
||||
|
||||
void playPrompt(const string& name);
|
||||
void closePlaylist(bool notify);
|
||||
void playFile(const string& name, bool loop);
|
||||
void recordFile(const string& name);
|
||||
void stopRecord();
|
||||
};
|
||||
|
||||
#endif
|
||||
// Local Variables:
|
||||
// mode:C++
|
||||
// End:
|
||||
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSMElemContainer.h"
|
||||
#include "DSMStateEngine.h"
|
||||
|
||||
DSMElemContainer::DSMElemContainer() {
|
||||
}
|
||||
|
||||
DSMElemContainer::~DSMElemContainer() {
|
||||
for (vector<DSMElement*>::iterator it=
|
||||
owned_elems.begin(); it != owned_elems.end(); it++)
|
||||
delete *it;
|
||||
}
|
||||
|
||||
void DSMElemContainer::transferElem(DSMElement* elem) {
|
||||
owned_elems.push_back(elem);
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _SC_ELEM_CONTAINER_H
|
||||
#define _SC_ELEM_CONTAINER_H
|
||||
|
||||
class DSMElement;
|
||||
#include <vector>
|
||||
using std::vector;
|
||||
|
||||
class DSMElemContainer {
|
||||
vector<DSMElement*> owned_elems;
|
||||
public:
|
||||
DSMElemContainer();
|
||||
virtual ~DSMElemContainer();
|
||||
/** take ownership ever this element (GC) */
|
||||
void transferElem(DSMElement* elem);
|
||||
|
||||
};
|
||||
#endif
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSMModule.h"
|
||||
|
||||
DSMModule::DSMModule() {
|
||||
}
|
||||
|
||||
DSMModule::~DSMModule() {
|
||||
}
|
||||
|
||||
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _DSM_MODULE_H
|
||||
#define _DSM_MODULE_H
|
||||
#include "DSMStateEngine.h"
|
||||
|
||||
#include <string>
|
||||
using std::string;
|
||||
|
||||
// script modules interface
|
||||
// factory only: it produces actions and conditions from script statements.
|
||||
class DSMModule {
|
||||
|
||||
public:
|
||||
DSMModule();
|
||||
virtual ~DSMModule();
|
||||
|
||||
virtual DSMAction* getAction(const string& from_str) = 0;
|
||||
virtual DSMCondition* getCondition(const string& from_str) = 0;
|
||||
};
|
||||
|
||||
typedef void* (*SCFactoryCreate)();
|
||||
|
||||
#define SCSTR(x) #x
|
||||
#define SCXSTR(x) SCSTR(x)
|
||||
|
||||
#define SC_FACTORY_EXPORT sc_factory_create
|
||||
#define SC_FACTORY_EXPORT_STR SCXSTR(SC_FACTORY_EXPORT)
|
||||
|
||||
#if __GNUC__ < 3
|
||||
#define EXPORT_SC_FACTORY(fctname,class_name,args...) \
|
||||
extern "C" void* fctname() \
|
||||
{ \
|
||||
return new class_name(##args); \
|
||||
}
|
||||
#else
|
||||
#define EXPORT_SC_FACTORY(fctname,class_name,...) \
|
||||
extern "C" void* fctname() \
|
||||
{ \
|
||||
return new class_name(__VA_ARGS__); \
|
||||
}
|
||||
#endif
|
||||
|
||||
#define SC_EXPORT(class_name) \
|
||||
EXPORT_SC_FACTORY(SC_FACTORY_EXPORT,class_name)
|
||||
|
||||
|
||||
|
||||
class SCStrArgAction
|
||||
: public DSMAction {
|
||||
protected:
|
||||
string arg;
|
||||
public:
|
||||
SCStrArgAction(const string& arg)
|
||||
: arg(arg) { }
|
||||
};
|
||||
|
||||
#define DEF_SCStrArgAction(CL_Name) \
|
||||
class CL_Name \
|
||||
: public SCStrArgAction { \
|
||||
public: \
|
||||
CL_Name(const string& arg) : SCStrArgAction(arg) { } \
|
||||
bool execute(AmSession* sess, \
|
||||
DSMCondition::EventType event, \
|
||||
map<string,string>* event_params); \
|
||||
}; \
|
||||
|
||||
|
||||
#define DEF_SCModSEStrArgAction(CL_Name) \
|
||||
class CL_Name \
|
||||
: public SCStrArgAction { \
|
||||
public: \
|
||||
CL_Name(const string& arg) : SCStrArgAction(arg) { } \
|
||||
bool execute(AmSession* sess, \
|
||||
DSMCondition::EventType event, \
|
||||
map<string,string>* event_params); \
|
||||
SEAction getSEAction(std::string&); \
|
||||
}; \
|
||||
|
||||
#define DEF_TwoParAction(CL_Name) \
|
||||
class CL_Name \
|
||||
: public DSMAction { \
|
||||
string par1; \
|
||||
string par2; \
|
||||
public: \
|
||||
CL_Name(const string& arg); \
|
||||
bool execute(AmSession* sess, \
|
||||
DSMCondition::EventType event, \
|
||||
map<string,string>* event_params); \
|
||||
}; \
|
||||
|
||||
#define CONST_TwoParAction(CL_name, sep, optional) \
|
||||
CL_name::CL_name(const string& arg) { \
|
||||
vector<string> args = explode(arg,sep); \
|
||||
if (!optional && args.size()!=2) { \
|
||||
ERROR("expression '%s' not valid\n", arg.c_str()); \
|
||||
return; \
|
||||
} \
|
||||
par1 = args.size()?trim(args[0], " \t"):""; \
|
||||
par2 = args.size()>1?trim(args[1], " \t"):""; \
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSMSession.h"
|
||||
|
||||
DSMSession::DSMSession() {
|
||||
}
|
||||
|
||||
DSMSession::~DSMSession() {
|
||||
}
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _DSM_SESSION_H
|
||||
#define _DSM_SESSION_H
|
||||
|
||||
#include <AmArg.h>
|
||||
|
||||
#include <string>
|
||||
using std::string;
|
||||
#include <vector>
|
||||
using std::vector;
|
||||
#include <map>
|
||||
using std::map;
|
||||
|
||||
class DSMSession {
|
||||
|
||||
public:
|
||||
DSMSession();
|
||||
virtual ~DSMSession();
|
||||
|
||||
virtual void playPrompt(const string& name) = 0;
|
||||
virtual void playFile(const string& name, bool loop) = 0;
|
||||
virtual void recordFile(const string& name) = 0;
|
||||
virtual void stopRecord() = 0;
|
||||
virtual void closePlaylist(bool notify) = 0;
|
||||
|
||||
/* holds variables which are accessed by $varname */
|
||||
map<string, string> var;
|
||||
|
||||
/* result of the last DI call */
|
||||
AmArg di_res;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSMStateDiagramCollection.h"
|
||||
|
||||
#include "DSMChartReader.h"
|
||||
#include <fstream>
|
||||
using std::ifstream;
|
||||
|
||||
DSMStateDiagramCollection::DSMStateDiagramCollection() {
|
||||
}
|
||||
|
||||
DSMStateDiagramCollection::~DSMStateDiagramCollection() {
|
||||
}
|
||||
|
||||
bool DSMStateDiagramCollection::loadFile(const string& filename, const string& name,
|
||||
const string& mod_path) {
|
||||
DSMChartReader cr;
|
||||
|
||||
ifstream ifs(filename.c_str());
|
||||
if (!ifs.good()) {
|
||||
ERROR("loading state diagram '%s'\n",
|
||||
filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
diags.push_back(DSMStateDiagram(name));
|
||||
string s;
|
||||
while (ifs.good() && !ifs.eof()) {
|
||||
string r;
|
||||
getline(ifs, r);
|
||||
// skip comments
|
||||
size_t fpos = r.find_first_not_of(" \t");
|
||||
if (fpos != string::npos &&
|
||||
r.length() > fpos+1 &&
|
||||
r.substr(fpos, 2) == "--")
|
||||
continue;
|
||||
|
||||
s += r + "\n";
|
||||
}
|
||||
DBG("dsm text\n------------------\n%s\n------------------\n", s.c_str());
|
||||
if (!cr.decode(&diags.back(), s, mod_path, this)) {
|
||||
ERROR("DonkeySM decode script error!\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DSMStateDiagramCollection::addToEngine(DSMStateEngine* e) {
|
||||
DBG("adding %zd diags to engine\n", diags.size());
|
||||
for (vector <DSMStateDiagram>::iterator it =
|
||||
diags.begin(); it != diags.end(); it++)
|
||||
e->addDiagram(&(*it));
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _StateDiagramReader_H
|
||||
#define _StateDiagramReader_H
|
||||
|
||||
#include "DSMStateEngine.h"
|
||||
#include <string>
|
||||
using std::string;
|
||||
|
||||
|
||||
class DSMStateDiagramCollection
|
||||
: public DSMElemContainer
|
||||
{
|
||||
vector <DSMStateDiagram> diags;
|
||||
|
||||
public:
|
||||
DSMStateDiagramCollection();
|
||||
~DSMStateDiagramCollection();
|
||||
|
||||
bool loadFile(const string& filename, const string& name, const string& mod_path);
|
||||
void addToEngine(DSMStateEngine* e);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,334 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "DSMStateEngine.h"
|
||||
#include "AmSession.h"
|
||||
#include "log.h"
|
||||
|
||||
|
||||
DSMStateDiagram::DSMStateDiagram(const string& name)
|
||||
: name(name) {
|
||||
}
|
||||
|
||||
DSMStateDiagram::~DSMStateDiagram() {
|
||||
}
|
||||
|
||||
void DSMStateDiagram::addState(const State& state, bool is_initial) {
|
||||
DBG("adding state '%s'\n", state.name.c_str());
|
||||
for (vector<DSMAction*>::const_iterator it=
|
||||
state.pre_actions.begin(); it != state.pre_actions.end(); it++) {
|
||||
DBG(" pre-action '%s'\n", (*it)->name.c_str());
|
||||
}
|
||||
for (vector<DSMAction*>::const_iterator it=
|
||||
state.post_actions.begin(); it != state.post_actions.end(); it++) {
|
||||
DBG(" post-action '%s'\n", (*it)->name.c_str());
|
||||
}
|
||||
|
||||
states.push_back(state);
|
||||
if (is_initial) {
|
||||
if (!initial_state.empty()) {
|
||||
ERROR("trying to override initial state '%s' with '%s'\n",
|
||||
initial_state.c_str(), state.name.c_str());
|
||||
} else {
|
||||
initial_state = state.name;
|
||||
DBG("set initial state '%s'\n", state.name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DSMStateDiagram::addTransition(const DSMTransition& trans) {
|
||||
DBG("adding Transition '%s' %s -()-> %s\n",
|
||||
trans.name.c_str(), trans.from_state.c_str(), trans.to_state.c_str());
|
||||
for (vector<DSMCondition*>::const_iterator it=
|
||||
trans.precond.begin(); it != trans.precond.end(); it++) {
|
||||
DBG(" DSMCondition '%s'\n", (*it)->name.c_str());
|
||||
}
|
||||
for (vector<DSMAction*>::const_iterator it=
|
||||
trans.actions.begin(); it != trans.actions.end(); it++) {
|
||||
DBG(" Action '%s'\n", (*it)->name.c_str());
|
||||
}
|
||||
|
||||
State* source_st = getState(trans.from_state);
|
||||
if (!source_st) {
|
||||
ERROR("state '%s' for transition '%s' not found\n",
|
||||
trans.from_state.c_str(), trans.name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
source_st->transitions.push_back(trans);
|
||||
return true;
|
||||
}
|
||||
|
||||
State* DSMStateDiagram::getState(const string& s_name) {
|
||||
// find target state
|
||||
for (vector<State>::iterator target_st =
|
||||
states.begin(); target_st != states.end(); target_st++) {
|
||||
if (target_st->name == s_name)
|
||||
return &(*target_st);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
State* DSMStateDiagram::getInitialState() {
|
||||
if (initial_state.empty()) {
|
||||
ERROR("diag '%s' doesn't have an initial state!\n",
|
||||
name.c_str());
|
||||
return false;
|
||||
}
|
||||
return getState(initial_state);
|
||||
}
|
||||
|
||||
|
||||
DSMStateEngine::DSMStateEngine()
|
||||
: current(NULL) {
|
||||
}
|
||||
|
||||
DSMStateEngine::~DSMStateEngine() {
|
||||
|
||||
}
|
||||
|
||||
bool DSMStateEngine::runactions(vector<DSMAction*>::iterator from,
|
||||
vector<DSMAction*>::iterator to,
|
||||
AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params, bool& is_consumed) {
|
||||
// DBG("running %zd actions\n", to - from);
|
||||
for (vector<DSMAction*>::iterator it=from; it != to; it++) {
|
||||
DBG("executing '%s'\n", (*it)->name.c_str());
|
||||
if ((*it)->execute(sess, event, event_params)) {
|
||||
string se_modifier;
|
||||
switch ((*it)->getSEAction(se_modifier)) {
|
||||
case DSMAction::Repost:
|
||||
is_consumed = false;
|
||||
break;
|
||||
case DSMAction::Jump:
|
||||
DBG("jumping %s\n", se_modifier.c_str());
|
||||
if (jumpDiag(se_modifier, sess, event, event_params)) {
|
||||
// is_consumed = false;
|
||||
return true;
|
||||
} break;
|
||||
case DSMAction::Call:
|
||||
if (callDiag(se_modifier, sess, event, event_params)) {
|
||||
// is_consumed = false;
|
||||
return true;
|
||||
} break;
|
||||
case DSMAction::Return:
|
||||
if (returnDiag(sess)) {
|
||||
//is_consumed = false;
|
||||
return true;
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DSMStateEngine::addDiagram(DSMStateDiagram* diag) {
|
||||
diags.push_back(diag);
|
||||
}
|
||||
|
||||
bool DSMStateEngine::init(AmSession* sess, const string& startDiagram) {
|
||||
|
||||
if (!jumpDiag(startDiagram, sess, DSMCondition::Any, NULL)) {
|
||||
ERROR("initializing with start diag '%s'\n",
|
||||
startDiagram.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
DBG("run null event...\n");
|
||||
runEvent(sess, DSMCondition::Any, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DSMCondition::match(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
|
||||
if ((type != Any) && (event != type))
|
||||
return false;
|
||||
|
||||
if (!event_params)
|
||||
return true;
|
||||
|
||||
for (map<string,string>::iterator it=params.begin();
|
||||
it!=params.end(); it++) {
|
||||
map<string,string>::iterator val = event_params->find(it->first);
|
||||
if (val == event_params->end() || val->second != it->second)
|
||||
return false;
|
||||
}
|
||||
|
||||
DBG("condition matched: '%s'\n", name.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void DSMStateEngine::runEvent(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
if (!current || !current_diag)
|
||||
return;
|
||||
|
||||
bool is_consumed = true;
|
||||
do {
|
||||
is_consumed = true;
|
||||
|
||||
for (vector<DSMTransition>::iterator tr = current->transitions.begin();
|
||||
tr != current->transitions.end();tr++) {
|
||||
DBG("checking transition '%s'\n", tr->name.c_str());
|
||||
|
||||
vector<DSMCondition*>::iterator con=tr->precond.begin();
|
||||
while (con!=tr->precond.end()) {
|
||||
if (!(*con)->match(sess, event, event_params))
|
||||
break;
|
||||
con++;
|
||||
}
|
||||
if (con == tr->precond.end()) {
|
||||
DBG("transition '%s' matched.\n", tr->name.c_str());
|
||||
|
||||
// matched all preconditions
|
||||
// find target state
|
||||
State* target_st = current_diag->getState(tr->to_state);
|
||||
if (!target_st) {
|
||||
ERROR("script writer error: transition '%s' from "
|
||||
"state '%s' to unknown state '%s'\n",
|
||||
tr->name.c_str(),
|
||||
current->name.c_str(),
|
||||
tr->to_state.c_str());
|
||||
}
|
||||
|
||||
// run post-actions
|
||||
if (current->post_actions.size()) {
|
||||
DBG("running %zd post_actions of state '%s'\n",
|
||||
current->post_actions.size(), current->name.c_str());
|
||||
if (runactions(current->post_actions.begin(),
|
||||
current->post_actions.end(),
|
||||
sess, event, event_params, is_consumed)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// run transition actions
|
||||
if (tr->actions.size()) {
|
||||
DBG("running %zd actions of transition '%s'\n",
|
||||
tr->actions.size(), tr->name.c_str());
|
||||
if (runactions(tr->actions.begin(),
|
||||
tr->actions.end(),
|
||||
sess, event, event_params, is_consumed)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// go into new state
|
||||
DBG("changing to new state '%s'\n", target_st->name.c_str());
|
||||
current = target_st;
|
||||
|
||||
// execute pre-actions
|
||||
if (current->pre_actions.size()) {
|
||||
DBG("running %zd pre_actions of state '%s'\n",
|
||||
current->pre_actions.size(), current->name.c_str());
|
||||
if (runactions(current->pre_actions.begin(),
|
||||
current->pre_actions.end(),
|
||||
sess, event, event_params, is_consumed)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!is_consumed);
|
||||
}
|
||||
|
||||
bool DSMStateEngine::callDiag(const string& diag_name, AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
if (!current || !current_diag) {
|
||||
ERROR("no current diag to push\n");
|
||||
return false;
|
||||
}
|
||||
stack.push_back(std::make_pair(current_diag, current));
|
||||
return jumpDiag(diag_name, sess, event, event_params);
|
||||
}
|
||||
|
||||
bool DSMStateEngine::jumpDiag(const string& diag_name, AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
for (vector<DSMStateDiagram*>::iterator it=
|
||||
diags.begin(); it != diags.end(); it++) {
|
||||
if ((*it)->getName() == diag_name) {
|
||||
current_diag = *it;
|
||||
current = current_diag->getInitialState();
|
||||
if (!current) {
|
||||
ERROR("diag '%s' does not have initial state.\n",
|
||||
diag_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// execute pre-actions
|
||||
DBG("running %zd pre_actions of init state '%s'\n",
|
||||
current->pre_actions.size(), current->name.c_str());
|
||||
|
||||
bool is_finished;
|
||||
is_finished = true;
|
||||
runactions(current->pre_actions.begin(),
|
||||
current->pre_actions.end(),
|
||||
sess, event, event_params, is_finished);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ERROR("diag '%s' not found.\n", diag_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DSMStateEngine::returnDiag(AmSession* sess) {
|
||||
if (stack.empty()) {
|
||||
ERROR("returning from empty stack\n");
|
||||
return false;
|
||||
}
|
||||
current_diag = stack.back().first;
|
||||
current = stack.back().second;
|
||||
stack.pop_back();
|
||||
DBG("returned to diag '%s' state '%s'\n",
|
||||
current_diag->getName().c_str(),
|
||||
current->name.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
State::State() {
|
||||
}
|
||||
|
||||
State::~State() {
|
||||
}
|
||||
|
||||
DSMTransition::DSMTransition(){
|
||||
}
|
||||
|
||||
DSMTransition::~DSMTransition(){
|
||||
}
|
||||
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _STATE_ENGINE_H
|
||||
#define _STATE_ENGINE_H
|
||||
|
||||
#include "DSMElemContainer.h"
|
||||
|
||||
class AmSession;
|
||||
#include <map>
|
||||
using std::map;
|
||||
#include <vector>
|
||||
using std::vector;
|
||||
#include <string>
|
||||
using std::string;
|
||||
|
||||
using std::pair;
|
||||
|
||||
#include "log.h"
|
||||
|
||||
class DSMElement {
|
||||
public:
|
||||
DSMElement() { }
|
||||
virtual ~DSMElement() { }
|
||||
string name; // documentary only
|
||||
|
||||
};
|
||||
|
||||
class DSMCondition
|
||||
: public DSMElement {
|
||||
public:
|
||||
enum EventType {
|
||||
Any,
|
||||
Key,
|
||||
Timer,
|
||||
|
||||
NoAudio,
|
||||
|
||||
Hangup,
|
||||
Hold,
|
||||
UnHold,
|
||||
|
||||
XmlrpcResponse
|
||||
};
|
||||
|
||||
DSMCondition() { }
|
||||
virtual ~DSMCondition() { }
|
||||
|
||||
EventType type;
|
||||
map<string, string> params;
|
||||
|
||||
virtual bool match(AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params);
|
||||
};
|
||||
|
||||
class DSMAction
|
||||
: public DSMElement {
|
||||
public:
|
||||
/** modifies State Engine operation */
|
||||
enum SEAction {
|
||||
None, // no modification
|
||||
Repost, // repost current event
|
||||
Jump, // jump FSM
|
||||
Call, // call FSM
|
||||
Return // return from FSM call
|
||||
};
|
||||
|
||||
DSMAction() { /* DBG("const action\n"); */ }
|
||||
virtual ~DSMAction() { /* DBG("dest action\n"); */ }
|
||||
|
||||
/** @return whether state engine is to be modified (via getSEAction) */
|
||||
virtual bool execute(AmSession* sess, DSMCondition::EventType event, \
|
||||
map<string,string>* event_params) = 0;
|
||||
|
||||
/** @return state engine modification */
|
||||
virtual SEAction getSEAction(string& param) { return None; }
|
||||
};
|
||||
|
||||
class DSMTransition;
|
||||
|
||||
class State
|
||||
: public DSMElement {
|
||||
public:
|
||||
State();
|
||||
~State();
|
||||
vector<DSMAction*> pre_actions;
|
||||
vector<DSMAction*> post_actions;
|
||||
|
||||
vector<DSMTransition> transitions;
|
||||
};
|
||||
|
||||
class DSMTransition
|
||||
: public DSMElement {
|
||||
public:
|
||||
DSMTransition();
|
||||
~DSMTransition();
|
||||
|
||||
vector<DSMCondition*> precond;
|
||||
vector<DSMAction*> actions;
|
||||
string from_state;
|
||||
string to_state;
|
||||
};
|
||||
|
||||
class DSMStateDiagram {
|
||||
vector<State> states;
|
||||
string name;
|
||||
string initial_state;
|
||||
|
||||
public:
|
||||
DSMStateDiagram(const string& name);
|
||||
~DSMStateDiagram();
|
||||
|
||||
State* getInitialState();
|
||||
State* getState(const string& s_name);
|
||||
|
||||
void addState(const State& state, bool is_initial = false);
|
||||
bool addTransition(const DSMTransition& trans);
|
||||
const string& getName() { return name; }
|
||||
};
|
||||
|
||||
class DSMStateEngine {
|
||||
State* current;
|
||||
DSMStateDiagram* current_diag;
|
||||
vector<DSMStateDiagram*> diags;
|
||||
|
||||
vector<pair<DSMStateDiagram*, State*> > stack;
|
||||
|
||||
bool callDiag(const string& diag_name, AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params);
|
||||
bool jumpDiag(const string& diag_name, AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params);
|
||||
bool returnDiag(AmSession* sess);
|
||||
bool runactions(vector<DSMAction*>::iterator from,
|
||||
vector<DSMAction*>::iterator to,
|
||||
AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params, bool& is_consumed);
|
||||
public:
|
||||
DSMStateEngine();
|
||||
~DSMStateEngine();
|
||||
|
||||
void addDiagram(DSMStateDiagram* diag);
|
||||
bool init(AmSession* sess, const string& startDiagram);
|
||||
|
||||
void runEvent(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
@ -0,0 +1,9 @@
|
||||
plug_in_name = dsm
|
||||
|
||||
module_ldflags =
|
||||
module_cflags = -DMOD_NAME=\"$(plug_in_name)\"
|
||||
|
||||
extra_install = $(plug_in_name)_audio
|
||||
|
||||
COREPATH ?=../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -0,0 +1,139 @@
|
||||
|
||||
The DonkeySM - state machines as SEMS applications
|
||||
|
||||
DonkeySM is a state machine interpreter for SEMS. Application
|
||||
or service logic can comfortably and accurately be defined
|
||||
as state machine, in a simple textual state machine definition
|
||||
language, and executed by the dsm module as application in SEMS.
|
||||
|
||||
A DSM consists of states and transitions between the states.
|
||||
One state is the initial state with which the DSM is started.
|
||||
The transitions have conditions. If something happens,
|
||||
i.e. the session processes an event, the transitions are checked
|
||||
in order. If all conditions match, the transition is executed.
|
||||
Transitions can contain actions, which are run when the transition
|
||||
is executed. States can have actions that are executed on entering
|
||||
the state, and on leaving the state. If a transition is executed,
|
||||
first the leaving actions of the old state, then the transition
|
||||
actions, and finally the entering actions of the new state are
|
||||
executed.
|
||||
|
||||
DSMs can be defined in a hierarchical manner: Another DSM can be
|
||||
called as sub-DSM, or one can jump to another DSM, ignoring where
|
||||
we came from.
|
||||
|
||||
A session (call) in the DonkeySM has a set of named (string) variables.
|
||||
The variables may be used as parameter to most conditions and
|
||||
actions, by prepending the variable name with a dollar sign. The
|
||||
parameters of an event (e.g. the key on key press) may be accessed
|
||||
by prepending the name with a hash. There are also 'selects' with
|
||||
which a set of dialog properties can be accessed (e.g. @local_tag).
|
||||
|
||||
The DonkeySM can be extended by modules, which add new conditions
|
||||
and actions to the language. This way, menuing system etc can be
|
||||
implemented as DSM, while complex logic or processing can efficitently
|
||||
be implemented in C++. DonkeySM also has built in actions to call
|
||||
DI methods from other modules.
|
||||
|
||||
It can cache a set of prompts, configured at start, in memory
|
||||
using PromptCollection.
|
||||
|
||||
A patch for fmsc 1.0.4 from the graphical FSM editor fsme
|
||||
(http://fsme.sf.net) is available, so DSMs can be defined in
|
||||
click-n-drag fashion and compiled to SEMS DSM diagrams.
|
||||
|
||||
More info
|
||||
=========
|
||||
o doc/dsm_syntax.txt has a quick reference for dsm syntax
|
||||
o doc/examples/ and lib/ some example DSMs
|
||||
o mods/ (will) have modules
|
||||
|
||||
Internals
|
||||
=========
|
||||
The DSMStateEngine has a set of DSM diagrams which are loaded by
|
||||
the DSMStateDiagramCollection from text file and interpreted by
|
||||
the DSMChartReader, a simple stack based tokenizing compiler.
|
||||
|
||||
DSMDialogs, which implement the DSMSession interface (additionally
|
||||
to being an AmSession), run DSMStateEngine::runEvent for every event
|
||||
that occurs that should be processed by the engine (e.g. Audio event,
|
||||
onBye, ...).
|
||||
|
||||
The DSMStateEngine checks every condition of the active state whether
|
||||
it matches. If all match, the exit actions of the current state, the
|
||||
transition actions and then the enter actions of the next state are
|
||||
executed. The DSMCondition::match and DSMAction::execute functions
|
||||
get the event parameters and the session as parameters, so that they
|
||||
can operate on variables, implement selects etc.
|
||||
|
||||
The DSMDialog implementation is very simple, it uses a playlist and
|
||||
has PromptCollection to simply play prompts etc.
|
||||
|
||||
DSMCoreModule is a 'built in' module that implements the basic
|
||||
conditions (test(), hangup() etc) and actions (set(), playFile(), DI
|
||||
etc).
|
||||
|
||||
Roadmap
|
||||
=======
|
||||
|
||||
On the roadmap is the possibility for modules to be the session factory
|
||||
used for creating the new session. As the DSMSession is mostly an abstract
|
||||
interface, other session types can easily be implemented, and their
|
||||
functionality be exposed to the DSM interpreter by custom actions and
|
||||
conditions that interact with that specific session type.
|
||||
|
||||
Another direction is python/lua/... interpreter module, so that
|
||||
conditions and actions can be expressed in a more powerful language.
|
||||
|
||||
A set of modules exposing more of the core functionality.
|
||||
|
||||
As the call state representation is nicely encapsulated here, this can
|
||||
also provide an abstraction layer on which active call replication can
|
||||
be implemented (rather than re-doing that for every application).
|
||||
|
||||
Q&A
|
||||
===
|
||||
|
||||
Why "Donkey" SM?
|
||||
Thanks Atle for the name: "I dont know why.. but my first thought
|
||||
when I read good name for FSM interpreter was Donkey. The reason for the
|
||||
name is that you put alot of things ontop of a donkey, and let it carry
|
||||
it arround.. and this is the same.. you put loads of stuf ontop.. and it
|
||||
carries it."
|
||||
|
||||
What is repost()?
|
||||
If an event should be reevaluated, e.g. by the transitions of another
|
||||
state or the initial state of another DSM which is called with callFSM/
|
||||
jumpFSM, repost() can be called, which signals the interpreter to
|
||||
evaluate the current event again.
|
||||
|
||||
Is the diagram interpreted or compiled?
|
||||
DonkeySM reads the DSM script and creates an internal representation
|
||||
(DSM classes in STL containers). This way it can be executed very
|
||||
efficiently.
|
||||
|
||||
map<string, string> var; - Are you crazy? E stands for Express!
|
||||
yes, right, there would be more efficient ways to implement
|
||||
that. Anyway, in my experience one mostly has to manipulate and
|
||||
check strings, and it is just very comfortable to do it this way.
|
||||
OTOH, if in a normal call there is a transition maybe on average
|
||||
every 10 seconds, for which 5 conditions are checked, it is not
|
||||
so much an impact on performance, considering that we are processing
|
||||
an audio packet every 20 ms.
|
||||
|
||||
You rely too heavily on polymorphism and RTTI - one dynamic_cast for
|
||||
each condition of each transition is way too heavy!
|
||||
Sure, but as noted above, there should not be heavy processing done
|
||||
in the DSM. If you need this, then consider writing your app entirely
|
||||
in C++.
|
||||
|
||||
SEMS has a dynamically typed type (AmArg), why not use that one for
|
||||
variables? That would also make DI simpler.
|
||||
a patch is very welcome, best to semsdev list: semsdev@iptel.org or
|
||||
the tracker: http://tracker.iptel.org
|
||||
|
||||
some performance numbers?
|
||||
unfortunately not yet for running DSMs. DSM processing is actually fast:
|
||||
a (quite simple) 3 state 5 transition DSM compiles in 0.2ms on P-M 2GHz.
|
||||
So the diagram could actually be read when the call is setup, or DSMs could
|
||||
load other DSMs (e.g. loadFSM() action)
|
||||
@ -0,0 +1,102 @@
|
||||
=============================
|
||||
-- comment
|
||||
import(mod_name);
|
||||
|
||||
[initial] state name
|
||||
[ enter {
|
||||
action;
|
||||
action;
|
||||
...
|
||||
} ]
|
||||
[ exit {
|
||||
action;
|
||||
action;
|
||||
...
|
||||
} ]
|
||||
;
|
||||
|
||||
transition name s1 - [ { condition; condition; ... } ] [/ { action; action; ...} ] -> s2;
|
||||
|
||||
=============================
|
||||
#paramname uses the event parameter 'paramname' (from current event)
|
||||
$varname uses the variable varname (from session's variable map)
|
||||
@selectname uses the "select" 'selectname' (from the session's dialog)
|
||||
=============================
|
||||
actions:
|
||||
-- reprocess the current event after transition:
|
||||
repost()
|
||||
|
||||
-- call/jump/return sub-FSM
|
||||
jumpFSM(name)
|
||||
callFSM(name)
|
||||
returnFSM()
|
||||
|
||||
playPrompt(param)
|
||||
from promptCollection, e.g. playPrompt("hello");
|
||||
playFile(filename)
|
||||
recordFile(filename)
|
||||
stopRecord()
|
||||
closePlaylist
|
||||
|
||||
set($var, value)
|
||||
e.g. set($var, "text"); set($var, $var2); set($var, #key)
|
||||
append($var, value)
|
||||
e.g. append($var, "text"); append($var, #key);
|
||||
append($var, @select); append($var, $var2);
|
||||
|
||||
log(level, text)
|
||||
e.g. log(1, $var1)
|
||||
|
||||
setTimer(timer_id, timeout)
|
||||
e.g. setTimer(1, $timeout)
|
||||
|
||||
DI(factory, function [, params...])
|
||||
e.g. DI(factory, function, $var_param, (int)int_param, "$str param", @select_par, ...)
|
||||
DI(user_timer, setTimer, (int)1, (int)5, @local_tag);
|
||||
DIgetResult(factory, function, param,...)
|
||||
saves result from DI call to DI_res or DI_res0, DI_res1, ...
|
||||
|
||||
stop(<send bye>)
|
||||
e.g. stop(false), stop(true)
|
||||
=============================
|
||||
conditions:
|
||||
|
||||
test(#key == 1)
|
||||
test(#key != 1)
|
||||
test(#key < 1)
|
||||
test(#key > 1)
|
||||
|
||||
test($var == 1)
|
||||
test($var != 1)
|
||||
test($var < 1)
|
||||
test($var < 1)
|
||||
|
||||
test(len($var) < len(#key))
|
||||
test(len($var) < len(@user))
|
||||
|
||||
-- like test(expr), but only on key press
|
||||
keyTest(expr)
|
||||
timerTest(expr)
|
||||
noAudioTest(expr)
|
||||
|
||||
keyPress(no)
|
||||
-- bye received:
|
||||
hangup
|
||||
|
||||
|
||||
=============================
|
||||
@selects :
|
||||
local_tag
|
||||
user
|
||||
domain
|
||||
remote_tag
|
||||
callid
|
||||
local_uri
|
||||
remote_uri
|
||||
=============================
|
||||
imports:
|
||||
module imported with import(mod_name); loads mod_name.so in
|
||||
module load path. modules provide conditions and actions.
|
||||
modules' actions/conditions-factory is checked first
|
||||
(modules can override core conditions/actions)
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
initial state START
|
||||
enter {
|
||||
log(3, $pin);
|
||||
returnFSM();
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
|
||||
initial state START
|
||||
enter {
|
||||
playPrompt(hello);
|
||||
};
|
||||
|
||||
state ADDKEY;
|
||||
state FIN;
|
||||
|
||||
transition "start pin entry" START - / set($pin = "") -> ADDKEY;
|
||||
|
||||
transition "add a key" ADDKEY - keyTest(#key < 10) /
|
||||
{
|
||||
closePlaylist();
|
||||
append($pin, #key);
|
||||
playPrompt(#key);
|
||||
callFSM(callsub)
|
||||
} -> ADDKEY;
|
||||
|
||||
|
||||
transition bye_recvd ADDKEY - hangup() / stop(false) -> FIN;
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
|
||||
initial state START
|
||||
enter {
|
||||
playPrompt(hello);
|
||||
-- test only: should go in onLoad() code,
|
||||
-- otherwise one connection per call
|
||||
DI(xmlrpc2di, newConnection, vbs, 127.0.0.1,(int)8302, "");
|
||||
};
|
||||
|
||||
state ADDKEY;
|
||||
state PLAY_FIN;
|
||||
|
||||
state BYE
|
||||
enter {
|
||||
stop(true);
|
||||
};
|
||||
|
||||
state FIN
|
||||
enter {
|
||||
stop(false);
|
||||
};
|
||||
|
||||
transition bye_after_fin PLAY_FIN - noAudioTest(1 == 1) -> BYE;
|
||||
|
||||
|
||||
state TESTPINRESULT
|
||||
enter {
|
||||
log(3, $validPin)
|
||||
log(3, $tryAgain)
|
||||
};
|
||||
|
||||
transition "start pin entry" START - / set($pin = "") -> ADDKEY;
|
||||
|
||||
transition "add a key" ADDKEY - keyTest(#key < 10) /
|
||||
{
|
||||
closePlaylist();
|
||||
append($pin, #key);
|
||||
playPrompt(#key);
|
||||
} -> ADDKEY;
|
||||
|
||||
transition bye_recvd ADDKEY - hangup() -> FIN;
|
||||
transition bye_recvd PLAY_FIN - hangup() -> FIN;
|
||||
|
||||
transition check_pin_server ADDKEY - keyTest(#key > 9) / {
|
||||
closePlaylist();
|
||||
playPrompt(entering);
|
||||
DIgetResult(xmlrpc2di, sendRequestList, vbs, checkPIN, @user, $pin);
|
||||
set($status= $DI_res0);
|
||||
set($description= $DI_res1);
|
||||
set($domt= $DI_res2);
|
||||
set($imguid= $DI_res3);
|
||||
set($inid= $DI_res4);
|
||||
set($vbsSessionId=$DI_res5);
|
||||
set($validPin= $DI_res6);
|
||||
set($tryAgain= $DI_res7);
|
||||
set($participantType=$DI_res8);
|
||||
set($inProgress= $DI_res9);
|
||||
set($registered= $DI_res10);
|
||||
set($startTime= $DI_res11);
|
||||
repost();
|
||||
} -> TESTPINRESULT;
|
||||
|
||||
transition incorrect_retry TESTPINRESULT -
|
||||
test($validPin == 0); test($tryAgain == 1)
|
||||
/ {
|
||||
set($pin="");
|
||||
playPrompt(retry_pin);
|
||||
} -> ADDKEY;
|
||||
|
||||
transition incorrect_no_retry TESTPINRESULT -
|
||||
test($validPin == 0); test($tryAgain == 0)
|
||||
/ {
|
||||
playPrompt(no_retry_pin);
|
||||
} -> PLAY_FIN;
|
||||
|
||||
transition correct_pin TESTPINRESULT - test($validPin == 0); / jumpFSM(ok_pin) -> PLAY_FIN;
|
||||
@ -0,0 +1,9 @@
|
||||
-- this is a commend
|
||||
-- this is a comment
|
||||
|
||||
-- this is a comment
|
||||
|
||||
initial state START
|
||||
enter {
|
||||
playPrompt(ok_pin);
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
|
||||
initial state START
|
||||
enter {
|
||||
playPrompt(hello);
|
||||
DI(user_timer, setTimer, (int)1, (int)5, @local_tag);
|
||||
DI(user_timer, setTimer, (int)2, (int)20, @local_tag);
|
||||
recordFile(/tmp/test_rec.wav);
|
||||
};
|
||||
|
||||
state ADDKEY;
|
||||
state FIN;
|
||||
|
||||
transition "start pin entry" START - / set($pin = "") -> ADDKEY;
|
||||
|
||||
transition "play from timer" ADDKEY - timerTest(#id == 1) / {
|
||||
playPrompt(hello);
|
||||
stopRecord();
|
||||
closePlaylist();
|
||||
playFile("/tmp/test_rec.wav");
|
||||
}
|
||||
-> ADDKEY;
|
||||
|
||||
transition "add a key" ADDKEY - keyTest(#key < 10) /
|
||||
{
|
||||
append($pin, #key);
|
||||
playPrompt(#key);
|
||||
} -> ADDKEY;
|
||||
|
||||
transition "timeout stop it" ADDKEY - timerTest(#id == 2) / stop(true) -> FIN;
|
||||
|
||||
transition finished ADDKEY - keyTest(#key > 9) / playPrompt(entering) -> FIN;
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
o explicit condition ordering (explicit priority)
|
||||
o checking of FSM consitency (e.g. target state names)
|
||||
o better error reporting in script compilation (e.g. line number)
|
||||
o better error handling (exceptions)
|
||||
o 'not' operator on conditions
|
||||
o 'or' operator
|
||||
o provide session init function for modules
|
||||
o embed python interpreter in module ?
|
||||
-> py( ... python code ... ) condition and action
|
||||
o correct en/decode DI args (e.g. arrays)
|
||||
@ -0,0 +1,14 @@
|
||||
|
||||
# diagrams (DSM descriptions)
|
||||
diag_path=../apps/dsm/lib
|
||||
load_diags=inbound_call,outbound_call
|
||||
|
||||
# for import(mod_name)
|
||||
mod_path=lib
|
||||
|
||||
# DSM to start for in/outbound call
|
||||
inbound_start_diag=inbound_call
|
||||
outbound_start_diag=outbound_call
|
||||
|
||||
# prompts files (for prompt collection
|
||||
load_prompts=../apps/dsm/etc/dsm_in_prompts.conf,../apps/dsm/etc/dsm_out_prompts.conf
|
||||
@ -0,0 +1,6 @@
|
||||
1=../apps/webconference/wav/1.wav
|
||||
2=../apps/webconference/wav/2.wav
|
||||
3=../apps/webconference/wav/3.wav
|
||||
4=../apps/webconference/wav/4.wav
|
||||
5=../apps/webconference/wav/5.wav
|
||||
6=../apps/webconference/wav/6.wav
|
||||
@ -0,0 +1,3 @@
|
||||
1=../apps/webconference/wav/1.wav
|
||||
2=../apps/webconference/wav/2.wav
|
||||
3=../apps/webconference/wav/3.wav
|
||||
@ -0,0 +1,153 @@
|
||||
344a345
|
||||
>
|
||||
393a395,397
|
||||
> out << "state " << sm.name;
|
||||
>
|
||||
>
|
||||
604a609,715
|
||||
>
|
||||
> QString makeSC( StateMachine& sm, QTextStream& out, const char* header, bool debug )
|
||||
> {
|
||||
> if( sm.name.isEmpty() ) return invalidName;
|
||||
> if( !sm.state(sm.initialState) ) return invalidInitial;
|
||||
>
|
||||
> QStringList states = sm.stateList();
|
||||
> if( states.isEmpty() ) return noStates;
|
||||
>
|
||||
> QStringList::Iterator it;
|
||||
>
|
||||
> for( it = states.begin(); it != states.end(); ++it ) {
|
||||
> State* s = sm.state( *it );
|
||||
> if (sm.initialState == *it)
|
||||
> out << "initial ";
|
||||
>
|
||||
> out << "state " << *it ;
|
||||
> if (!s->incomeActions.empty()) {
|
||||
> out << endl << " enter ";
|
||||
> out << " {" << endl << " ";
|
||||
> OutputList::Iterator zIt;
|
||||
> for( zIt = s->incomeActions.begin(); zIt != s->incomeActions.end(); ++zIt ) {
|
||||
> if (zIt != s->incomeActions.begin())
|
||||
> out << endl << " ";
|
||||
> out << (*zIt).first << "; ";
|
||||
> }
|
||||
> out << endl << " }";
|
||||
> }
|
||||
>
|
||||
> if (!s->outcomeActions.empty()) {
|
||||
> out << endl << " exit ";
|
||||
> out << " {" << endl << " ";
|
||||
> OutputList::Iterator zIt;
|
||||
> for( zIt = s->outcomeActions.begin(); zIt != s->outcomeActions.end(); ++zIt ) {
|
||||
> if (zIt != s->outcomeActions.begin())
|
||||
> out << endl << " ";
|
||||
> out << (*zIt).first << "; ";
|
||||
> }
|
||||
> out << endl << " }";
|
||||
> }
|
||||
>
|
||||
> out << ";" << endl << endl;
|
||||
>
|
||||
> State::TransitionList::Iterator ti;
|
||||
> bool first = true;
|
||||
>
|
||||
> State::TransitionList tl = s->transitionList();
|
||||
>
|
||||
> for( ti = tl.begin(); ti != tl.end(); ++ti ) {
|
||||
>
|
||||
> out << "transition ";
|
||||
> if ((*ti)->name().contains(' '))
|
||||
> out << "\"";
|
||||
> out << (*ti)->name();
|
||||
> if ((*ti)->name().contains(' '))
|
||||
> out << "\"";
|
||||
>
|
||||
> out << " " << s->name() << " ";
|
||||
> out << "-";
|
||||
>
|
||||
> // out << "transition " << (*ti)->name() << " " << s->name() << " ";
|
||||
> // out << "-";
|
||||
>
|
||||
>
|
||||
> if (!(*ti)->condition.isEmpty()) {
|
||||
> QString cond = (*ti)->condition;
|
||||
>
|
||||
> int i=0;
|
||||
> while (i < cond.length()) {
|
||||
> i = cond.find('\n', i);
|
||||
> if (i < 0)
|
||||
> break;
|
||||
> cond = cond.replace(i, 1, "\n ");
|
||||
> i+=1;
|
||||
> }
|
||||
>
|
||||
> if (cond.contains('\n'))
|
||||
> out << " {" << endl << " ";
|
||||
>
|
||||
> out << " " << cond << " ";
|
||||
>
|
||||
> if (cond.contains('\n'))
|
||||
> out << endl << " " << " }" << endl;
|
||||
> }
|
||||
>
|
||||
> if (!(*ti)->actions.empty()) {
|
||||
> if (!(*ti)->condition.isEmpty())
|
||||
> out << " " ;
|
||||
> out << "/ ";
|
||||
> if ((*ti)->actions.size() > 1)
|
||||
> out << " { ";
|
||||
>
|
||||
> OutputList::Iterator zIt;
|
||||
> for( zIt = (*ti)->actions.begin(); zIt != (*ti)->actions.end(); ++zIt )
|
||||
> out << (*zIt).first << "; ";
|
||||
>
|
||||
> if ((*ti)->actions.size() > 1)
|
||||
> out << "} ";
|
||||
>
|
||||
> }
|
||||
> out << "-> " << (*ti)->target << ";" << endl << endl ;
|
||||
> }
|
||||
> }
|
||||
>
|
||||
> return QString();
|
||||
> }
|
||||
>
|
||||
775a887,889
|
||||
>
|
||||
> makeSC(sm, out, "", debug);
|
||||
> return 0;
|
||||
777,792c891,907
|
||||
< if( !impl ) {
|
||||
< // qDebug( "writing declaration to %s", outputFile );
|
||||
< QString error = makeHeader( sm, out, debug );
|
||||
< if( !error.isEmpty() ) {
|
||||
< SHOW_ERROR( error );
|
||||
< exit( 1 );
|
||||
< }
|
||||
< }
|
||||
< else {
|
||||
< // qDebug( "writing implementation with header %s to %s", headerFile, outputFile );
|
||||
< QString error = makeBody( sm, out, headerFile, debug );
|
||||
< if( !error.isEmpty() ) {
|
||||
< SHOW_ERROR( error );
|
||||
< exit( 1 );
|
||||
< }
|
||||
< }
|
||||
---
|
||||
>
|
||||
> // if( !impl ) {
|
||||
> // // qDebug( "writing declaration to %s", outputFile );
|
||||
> // QString error = makeHeader( sm, out, debug );
|
||||
> // if( !error.isEmpty() ) {
|
||||
> // SHOW_ERROR( error );
|
||||
> // exit( 1 );
|
||||
> // }
|
||||
> // }
|
||||
> // else {
|
||||
> // // qDebug( "writing implementation with header %s to %s", headerFile, outputFile );
|
||||
> // QString error = makeBody( sm, out, headerFile, debug );
|
||||
> // if( !error.isEmpty() ) {
|
||||
> // SHOW_ERROR( error );
|
||||
> // exit( 1 );
|
||||
> // }
|
||||
> // }
|
||||
@ -0,0 +1,11 @@
|
||||
Note: This is just rather a proof of concept. Improvements are welcome.
|
||||
|
||||
You will need qt3 (not 4) to compile fsme/fsmc. As of july 08,
|
||||
the current version is 1.0.4.
|
||||
|
||||
DSM Conditions are "Events" in fsme. To make a new state, click on the
|
||||
empty leaf in the treeview on the left side. Sub-leafs are transitions,
|
||||
open the tree and click on one to make a new transition.
|
||||
|
||||
It would actually be nice to print the condition name as comment, and
|
||||
use the event body as condition (the same for actions).
|
||||
@ -0,0 +1,27 @@
|
||||
-- this is just a nonsense script
|
||||
import(mod_sys);
|
||||
|
||||
initial state start;
|
||||
|
||||
transition "secret door" start - test($pin == 155) -> the_secret_room;
|
||||
transition "another key below 7" start - keyTest(#key < 7) / { append($pin, #key); playPrompt(#key); } -> start;
|
||||
|
||||
transition "everything else" start - keyTest() / log(2, $pin); stop(true) -> end;
|
||||
|
||||
state end;
|
||||
|
||||
state the_secret_room
|
||||
enter {
|
||||
recordFile(/tmp/record.wav)
|
||||
setTimer(1, 10);
|
||||
-- another way to set a timer: DI(user_timer, setTimer, (int)1, (int)10, @local_tag);
|
||||
};
|
||||
transition replay the_secret_room - timerTest(#id == 1) / { stopRecord(); playFile(/tmp/record.wav); } -> the_replay;
|
||||
|
||||
state the_replay;
|
||||
transition fin the_replay - noAudioTest() / stop(true) -> end;
|
||||
|
||||
transition "bye recvd" start - hangup() / stop(false) -> end;
|
||||
transition "bye recvd" the_replay - hangup() / stop(false) -> end;
|
||||
transition "bye recvd" the_secret_room - hangup() / stop(false) -> end;
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
-- another nonsensical fsm...
|
||||
initial state start;
|
||||
transition "just an example" start - / { playPrompt(1); playPrompt(2); playPrompt(3); } -> end;
|
||||
state end;
|
||||
transition "stop it" end - noAudioTest() / stop(true) -> end;
|
||||
transition "bye recvd" end - hangup() / stop(false) -> end;
|
||||
@ -0,0 +1,9 @@
|
||||
plug_in_name = mod_sys
|
||||
|
||||
DSMPATH ?= ../..
|
||||
|
||||
module_ldflags =
|
||||
module_cflags = -DMOD_NAME=\"$(plug_in_name)\" -I$(DSMPATH)
|
||||
|
||||
COREPATH ?=$(DSMPATH)/../../core
|
||||
include $(COREPATH)/plug-in/Makefile.app_module
|
||||
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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 "ModSys.h"
|
||||
#include "log.h"
|
||||
#include "AmUtils.h"
|
||||
|
||||
#include "DSMSession.h"
|
||||
#include "AmSession.h"
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "DSMCoreModule.h"
|
||||
|
||||
SC_EXPORT(SCSysModule);
|
||||
|
||||
SCSysModule::SCSysModule() {
|
||||
}
|
||||
|
||||
SCSysModule::~SCSysModule() {
|
||||
}
|
||||
|
||||
void splitCmd(const string& from_str,
|
||||
string& cmd, string& params) {
|
||||
size_t b_pos = from_str.find('(');
|
||||
if (b_pos != string::npos) {
|
||||
cmd = from_str.substr(0, b_pos);
|
||||
params = from_str.substr(b_pos + 1, from_str.rfind(')') - b_pos -1);
|
||||
} else
|
||||
cmd = from_str;
|
||||
}
|
||||
|
||||
DSMAction* SCSysModule::getAction(const string& from_str) {
|
||||
string cmd;
|
||||
string params;
|
||||
splitCmd(from_str, cmd, params);
|
||||
|
||||
#define DEF_CMD(cmd_name, class_name) \
|
||||
\
|
||||
if (cmd == cmd_name) { \
|
||||
class_name * a = \
|
||||
new class_name(params); \
|
||||
a->name = from_str; \
|
||||
return a; \
|
||||
}
|
||||
DEF_CMD("sys.mkdir", SCMkDirAction);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DSMCondition* SCSysModule::getCondition(const string& from_str) {
|
||||
string cmd;
|
||||
string params;
|
||||
splitCmd(from_str, cmd, params);
|
||||
|
||||
if (cmd == "sys.file_exists") {
|
||||
return new FileExistsCondition(params, false);
|
||||
}
|
||||
|
||||
// ahem... missing not?
|
||||
if (cmd == "sys.file_not_exists") {
|
||||
return new FileExistsCondition(params, true);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define GET_SCSESSION() \
|
||||
DSMSession* sc_sess = dynamic_cast<DSMSession*>(sess); \
|
||||
if (!sc_sess) { \
|
||||
ERROR("wrong session type\n"); \
|
||||
return false; \
|
||||
}
|
||||
|
||||
bool FileExistsCondition::match(AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
DBG("checking file '%s'\n", arg.c_str());
|
||||
string fname = resolveVars(arg, sess, sc_sess, event_params);
|
||||
bool ex = file_exists(fname);
|
||||
DBG("file '%s' %s\n", fname.c_str(), ex?"exists":"does not exist");
|
||||
if (inv) {
|
||||
DBG("returning %s\n", (!ex)?"true":"false");
|
||||
return !ex;
|
||||
} else {
|
||||
DBG("returning %s\n", (ex)?"true":"false");
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SCMkDirAction::execute(AmSession* sess,
|
||||
DSMCondition::EventType event,
|
||||
map<string,string>* event_params) {
|
||||
GET_SCSESSION();
|
||||
string d = resolveVars(arg, sess, sc_sess, event_params);
|
||||
DBG("mkdir '%s'\n", d.c_str());
|
||||
if (mkdir(d.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) {
|
||||
ERROR("kmdir failed for '%s': %s\n",
|
||||
d.c_str(), strerror(errno));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* $Id: $
|
||||
*
|
||||
* Copyright (C) 2008 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
|
||||
*/
|
||||
#ifndef _MOD_SYS_H
|
||||
#define _MOD_SYS_H
|
||||
#include "DSMModule.h"
|
||||
|
||||
class SCSysModule
|
||||
: public DSMModule {
|
||||
|
||||
public:
|
||||
SCSysModule();
|
||||
~SCSysModule();
|
||||
|
||||
DSMAction* getAction(const string& from_str);
|
||||
DSMCondition* getCondition(const string& from_str);
|
||||
};
|
||||
|
||||
class FileExistsCondition
|
||||
: public DSMCondition {
|
||||
string arg;
|
||||
bool inv;
|
||||
|
||||
public:
|
||||
|
||||
FileExistsCondition(const string& arg, bool inv)
|
||||
: arg(arg), inv(inv) { }
|
||||
bool match(AmSession* sess, DSMCondition::EventType event,
|
||||
map<string,string>* event_params);
|
||||
};
|
||||
|
||||
DEF_SCStrArgAction(SCMkDirAction);
|
||||
#endif
|
||||
Loading…
Reference in new issue