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:
parent
808b738e60
commit
f5e76ddb6b
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue