Changed the way callback JS functions are called.

Added a global Javascript loader.
Moved the main routing script to the [global] section.


git-svn-id: http://yate.null.ro/svn/yate/trunk@5127 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2012-06-16 23:21:13 +00:00
parent 808b738e60
commit f5e76ddb6b
5 changed files with 226 additions and 22 deletions

View File

@ -6,9 +6,18 @@
; Note that a trailing path separator should be added
;scripts_dir=share/scripts/
[scripts]
; routing: string: Name of the file holding the routing instructions
; Example: routing=route.js
;routing=
[scripts]
; Add one entry in this section for each script that is to be loaded on Yate startup
; Each line has to be on the form:
; name=script_file_name
; The name must be unique and it will identify the running script instance.
; The file name should hold either the absolute path and name or the path
; and name relative to the scripts_dir in section [general]
; Examples:
; faxes=fax_handler.js
; callback=js_lib/callback.js

View File

@ -79,6 +79,7 @@ protected:
class JsCode : public ScriptCode, public ExpEvaluator
{
friend class TelEngine::JsFunction;
friend class TelEngine::JsParser;
friend class ParseNested;
friend class JsRunner;
public:
@ -228,6 +229,7 @@ public:
{ }
virtual Status reset();
virtual Status call(const String& name, ObjList& args, ExpOperation* thisObj = 0);
virtual bool callable(const String& name);
protected:
virtual Status resume();
private:
@ -2074,14 +2076,20 @@ ScriptRun::Status JsRunner::resume()
ScriptRun::Status JsRunner::call(const String& name, ObjList& args, ExpOperation* thisObj)
{
Lock mylock(this);
if (Invalid == state())
if (Invalid == state()) {
TelEngine::destruct(thisObj);
return Invalid;
}
const JsCode* c = static_cast<const JsCode*>(code());
if (!(c && context()))
if (!(c && context())) {
TelEngine::destruct(thisObj);
return Invalid;
}
JsFunction* func = c->getGlobalFunction(name);
if (!func)
if (!func) {
TelEngine::destruct(thisObj);
return Failed;
}
reset();
// prepare a function call stack
ExpOperation oper(ExpEvaluator::OpcFunc,name,args.count());
@ -2095,6 +2103,15 @@ ScriptRun::Status JsRunner::call(const String& name, ObjList& args, ExpOperation
return s;
}
bool JsRunner::callable(const String& name)
{
Lock mylock(this);
if (Invalid == state())
return false;
const JsCode* c = static_cast<const JsCode*>(code());
return (c && context() && c->getGlobalFunction(name));
}
}; // anonymous namespace
@ -2159,7 +2176,7 @@ bool JsFunction::runDefined(ObjList& stack, const ExpOperation& oper, GenObject*
// Adjust a script file include path
void JsParser::adjustPath(String& script)
void JsParser::adjustPath(String& script) const
{
if (script.null() || script.startsWith(Engine::pathSeparator()))
return;
@ -2184,6 +2201,13 @@ ScriptRun* JsParser::createRunner(ScriptCode* code, ScriptContext* context) cons
return runner;
}
// Check if function or method exists
bool JsParser::callable(const String& name)
{
const JsCode* c = static_cast<const JsCode*>(code());
return (c && c->getGlobalFunction(name));
}
// Parse a piece of Javascript text
bool JsParser::parse(const char* text, bool fragment)
{

View File

@ -93,6 +93,11 @@ ScriptRun* ScriptParser::createRunner(ScriptCode* code, ScriptContext* context)
return runner;
}
bool ScriptParser::callable(const String& name)
{
return false;
}
// RTTI Interface access
void* ScriptContext::getObject(const String& name) const
@ -274,9 +279,16 @@ ScriptRun::Status ScriptRun::run()
// Execute a function or method call
ScriptRun::Status ScriptRun::call(const String& name, ObjList& args, ExpOperation* thisObj)
{
TelEngine::destruct(thisObj);
return Failed;
}
// Check if a function or method call exists
bool ScriptRun::callable(const String& name)
{
return false;
}
// Execute an assignment operation
bool ScriptRun::runAssign(const ExpOperation& oper, GenObject* context)
{

View File

@ -1414,6 +1414,13 @@ public:
*/
virtual Status call(const String& name, ObjList& args, ExpOperation* thisObj = 0);
/**
* Check if a script has a certain function or method
* @param name Name of the function to check
* @return True if function exists in code
*/
virtual bool callable(const String& name);
/**
* Try to assign a value to a single field in the script context
* @param oper Field to assign to, contains the field name and new value
@ -1500,6 +1507,13 @@ public:
inline ScriptRun* createRunner(ScriptContext* context = 0) const
{ return createRunner(code(),context); }
/**
* Check if a script has a certain function or method
* @param name Name of the function to check
* @return True if function exists in code
*/
virtual bool callable(const String& name);
protected:
/**
* Default constructor for derived classes
@ -1962,11 +1976,18 @@ public:
inline ScriptRun* createRunner(ScriptContext* context = 0) const
{ return createRunner(code(),context); }
/**
* Check if a script has a certain function or method
* @param name Name of the function to check
* @return True if function exists in code
*/
virtual bool callable(const String& name);
/**
* Adjust a file script path to include default if needed
* @param script File path to adjust
*/
void adjustPath(String& script);
void adjustPath(String& script) const;
/**
* Retrieve the base script path

View File

@ -73,11 +73,35 @@ public:
virtual bool msgDisconnect(Message& msg, const String& reason);
bool init();
private:
bool runFunction(const char* name, Message& msg);
bool runFunction(const String& name, Message& msg);
ScriptRun* m_runner;
State m_state;
};
class JsGlobal : public NamedString
{
public:
JsGlobal(const char* scriptName, const char* fileName);
virtual ~JsGlobal();
bool fileChanged(const char* fileName) const;
inline JsParser& parser()
{ return m_jsCode; }
inline ScriptContext* context()
{ return m_context; }
bool runMain();
static void markUnused();
static void freeUnused();
static void initScript(const String& scriptName, const String& fileName);
inline static void unloadAll()
{ s_globals.clear(); }
private:
JsParser m_jsCode;
RefPointer<ScriptContext> m_context;
unsigned int m_fileTime;
bool m_inUse;
static ObjList s_globals;
};
#define MKDEBUG(lvl) params().addParam(new ExpOperation((long int)Debug ## lvl,"Debug" # lvl))
class JsEngine : public JsObject
{
@ -190,8 +214,10 @@ INIT_PLUGIN(JsModule);
UNLOAD_PLUGIN(unloadNow)
{
if (unloadNow)
if (unloadNow) {
JsGlobal::unloadAll();
return __plugin.unload();
}
return true;
}
@ -527,20 +553,23 @@ bool JsAssist::init()
return (ScriptRun::Succeeded == rval);
}
bool JsAssist::runFunction(const char* name, Message& msg)
bool JsAssist::runFunction(const String& name, Message& msg)
{
if (!m_runner)
if (!(m_runner && m_runner->callable(name)))
return false;
DDebug(&__plugin,DebugInfo,"Running function %s in '%s'",name,id().c_str());
JsParser jp;
String tmp;
tmp << name << "()";
jp.parse(tmp);
ScriptRun* runner = jp.createRunner(m_runner->context());
JsMessage* jm = new JsMessage(&msg,m_runner->context()->mutex(),false);
ExpEvaluator::pushOne(runner->stack(),new ExpWrapper(jm,"message"));
ScriptRun::Status rval = runner->run();
DDebug(&__plugin,DebugInfo,"Running function %s in '%s'",name.c_str(),id().c_str());
ScriptRun* runner = __plugin.parser().createRunner(m_runner->context());
if (!runner)
return false;
JsMessage* jm = new JsMessage(&msg,runner->context()->mutex(),false);
jm->ref();
ObjList args;
args.append(new ExpWrapper(jm,"message"));
ScriptRun::Status rval = runner->call(name,args);
jm->clearMsg();
TelEngine::destruct(jm);
TelEngine::destruct(runner);
return (ScriptRun::Succeeded == rval);
}
@ -587,6 +616,100 @@ bool JsAssist::msgDisconnect(Message& msg, const String& reason)
}
ObjList JsGlobal::s_globals;
JsGlobal::JsGlobal(const char* scriptName, const char* fileName)
: NamedString(scriptName,fileName),
m_fileTime(0), m_inUse(true)
{
m_jsCode.basePath(s_basePath);
m_jsCode.adjustPath(*this);
DDebug(&__plugin,DebugAll,"Loading global Javascript '%s' from '%s'",name().c_str(),c_str());
File::getFileTime(c_str(),m_fileTime);
if (m_jsCode.parseFile(*this))
Debug(&__plugin,DebugInfo,"Parsed '%s' script: %s",name().c_str(),c_str());
else if (*this)
Debug(&__plugin,DebugWarn,"Failed to parse '%s' script: %s",name().c_str(),c_str());
}
JsGlobal::~JsGlobal()
{
DDebug(&__plugin,DebugAll,"Unloading global Javascript '%s'",name().c_str());
if (m_jsCode.callable("onUnload")) {
ScriptRun* runner = m_jsCode.createRunner(m_context);
if (runner) {
ObjList args;
runner->call("onUnload",args);
TelEngine::destruct(runner);
}
}
}
bool JsGlobal::fileChanged(const char* fileName) const
{
if (m_jsCode.basePath() != s_basePath)
return true;
String tmp(fileName);
m_jsCode.adjustPath(tmp);
if (tmp != *this)
return true;
unsigned int time;
File::getFileTime(tmp,time);
return (time != m_fileTime);
}
void JsGlobal::markUnused()
{
ListIterator iter(s_globals);
while (JsGlobal* script = static_cast<JsGlobal*>(iter.get()))
script->m_inUse = false;
}
void JsGlobal::freeUnused()
{
ListIterator iter(s_globals);
while (JsGlobal* script = static_cast<JsGlobal*>(iter.get()))
if (!script->m_inUse)
s_globals.remove(script);
}
void JsGlobal::initScript(const String& scriptName, const String& fileName)
{
if (fileName.null())
return;
JsGlobal* script = static_cast<JsGlobal*>(s_globals[scriptName]);
if (script) {
if (script->fileChanged(fileName)) {
s_globals.remove(script,false);
TelEngine::destruct(script);
}
else {
script->m_inUse = true;
return;
}
}
script = new JsGlobal(scriptName,fileName);
script->runMain();
s_globals.append(script);
}
bool JsGlobal::runMain()
{
ScriptRun* runner = m_jsCode.createRunner(m_context);
if (!runner)
return false;
if (!m_context)
m_context = runner->context();
JsObject::initialize(runner->context());
JsEngine::initialize(runner->context());
JsMessage::initialize(runner->context());
JsFile::initialize(runner->context());
ScriptRun::Status st = runner->run();
TelEngine::destruct(runner);
return (ScriptRun::Succeeded == st);
}
JsModule::JsModule()
: ChanAssistList("javascript",true)
{
@ -702,6 +825,9 @@ bool JsModule::received(Message& msg, int id)
}
}
break;
case Halt:
JsGlobal::unloadAll();
return false;
} // switch (id)
return ChanAssistList::received(msg,id);
}
@ -747,18 +873,30 @@ void JsModule::initialize()
lock();
m_assistCode.clear();
m_assistCode.basePath(tmp);
tmp = cfg.getValue("scripts","routing");
tmp = cfg.getValue("general","routing");
m_assistCode.adjustPath(tmp);
if (m_assistCode.parseFile(tmp))
Debug(this,DebugInfo,"Parsed routing script: %s",tmp.c_str());
else if (tmp)
Debug(this,DebugWarn,"Failed to parse script: %s",tmp.c_str());
unlock();
JsGlobal::markUnused();
NamedList* sect = cfg.getSection("scripts");
if (sect) {
unsigned int len = sect->length();
for (unsigned int i=0; i<len; i++) {
NamedString *n = sect->getParam(i);
if (n)
JsGlobal::initScript(n->name(),*n);
}
}
JsGlobal::freeUnused();
}
void JsModule::init(int priority)
{
ChanAssistList::init(priority);
installRelay(Halt);
installRelay(Route,priority);
installRelay(Ringing,priority);
installRelay(Answered,priority);