diff --git a/conf.d/javascript.conf.sample b/conf.d/javascript.conf.sample index 386fcbd3..99e178fd 100644 --- a/conf.d/javascript.conf.sample +++ b/conf.d/javascript.conf.sample @@ -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 diff --git a/libs/yscript/javascript.cpp b/libs/yscript/javascript.cpp index 8f2844f2..323076ec 100644 --- a/libs/yscript/javascript.cpp +++ b/libs/yscript/javascript.cpp @@ -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(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(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(code()); + return (c && c->getGlobalFunction(name)); +} + // Parse a piece of Javascript text bool JsParser::parse(const char* text, bool fragment) { diff --git a/libs/yscript/script.cpp b/libs/yscript/script.cpp index 5789811b..72593087 100644 --- a/libs/yscript/script.cpp +++ b/libs/yscript/script.cpp @@ -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) { diff --git a/libs/yscript/yatescript.h b/libs/yscript/yatescript.h index dac654f4..8f2f16aa 100644 --- a/libs/yscript/yatescript.h +++ b/libs/yscript/yatescript.h @@ -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 diff --git a/modules/javascript.cpp b/modules/javascript.cpp index 54b935e4..fd82959f 100644 --- a/modules/javascript.cpp +++ b/modules/javascript.cpp @@ -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 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(iter.get())) + script->m_inUse = false; +} + +void JsGlobal::freeUnused() +{ + ListIterator iter(s_globals); + while (JsGlobal* script = static_cast(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(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; igetParam(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);