Simplify declare function in scripts

Using declare function is made easier by accept a single path and returning
a promise to read parameter attributes.
This commit is contained in:
Zaid Abdulla committed 2016-11-07 00:29:38 -08:00
1 parent 044734b567
commit b4bbd8f58c
4 files changed
+336 -208

No files matched your search

+2 -2
View File
@@ -167,7 +167,7 @@ refresh = (callback) ->
for r in res
provisions[r._id] = {}
provisions[r._id].md5 = crypto.createHash('md5').update(r.script).digest('hex')
provisions[r._id].script = new vm.Script(r.script, {filename: r._id, timeout: 50})
provisions[r._id].script = new vm.Script("\"use strict\";(function(){\n#{r.script}\n})();", {filename: r._id, lineOffset: 1, timeout: 50})
if -- counter == 0
computeHash()
@@ -188,7 +188,7 @@ refresh = (callback) ->
for r in res
virtualParameters[r._id] = {}
virtualParameters[r._id].md5 = crypto.createHash('md5').update(r.script).digest('hex')
virtualParameters[r._id].script = new vm.Script(r.script, {filename: r._id, timeout: 50})
virtualParameters[r._id].script = new vm.Script("\"use strict\";(function(){\n#{r.script}\n})();", {filename: r._id, lineOffset: 1, timeout: 50})
if -- counter == 0
computeHash()
-152
View File
@@ -1,152 +0,0 @@
###
# Copyright 2013-2016 Zaid Abdulla
#
# GenieACS is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# GenieACS 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
###
vm = require 'vm'
common = require './common'
device = require './device'
# Used for throwing to exit user script
class Exit;
sandbox = {
timestamp: null
deviceData: null
revision: null
maxRevision: null
declarations: null
extensionsCache: null
extensions: null
context: vm.createContext()
}
Object.defineProperty(sandbox.context, 'Date', {value: class
constructor: (arg) ->
if arguments.length
return new (Function.prototype.bind.apply(Date, arguments))
return new Date(sandbox.timestamp)
@now: () ->
return sandbox.timestamp
})
Object.defineProperty(sandbox.context, 'ext', {value: () ->
if sandbox.extensions?.length
throw new Error('Ext function should not be called from within a try/catch block')
extCall = (String(a) for a in arguments)
key = JSON.stringify(extCall)
if not sandbox.extensionsCache[sandbox.revision]? or key not of sandbox.extensionsCache[sandbox.revision]
sandbox.extensions = [extCall]
throw new Exit()
return sandbox.extensionsCache[sandbox.revision][key]
})
Object.defineProperty(sandbox.context, 'declare', {value: (decs) ->
if ++ sandbox.revision > sandbox.maxRevision + 1
throw new Error('Declare function should not be called from within a try/catch block')
if sandbox.revision == sandbox.maxRevision + 1
for k, v of decs
timestamp = 1
exist = null
attributeTimestamps = {}
attributeValues = {}
for attrName, dec of v
if attrName is 'exist'
if Array.isArray(dec)
timestamp = dec[0]
exist = dec[1] if 1 of dec
else
timestamp = dec
continue
if Array.isArray(dec)
attributeTimestamps[attrName] = dec[0]
attributeValues[attrName] = dec[1] if 1 of dec
else
attributeTimestamps[attrName] = dec
sandbox.declarations.push([common.parsePath(k), timestamp, attributeTimestamps, exist, attributeValues])
throw new Exit()
res = {}
for k, v of decs
path = common.parsePath(k)
single = true
for p in path
if p == '*' or common.typeOf(p) isnt common.STRING_TYPE
single = false
break
unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision)
for param in unpacked
r = {}
attrs = sandbox.deviceData.attributes.get(param, sandbox.revision)
for attrName, dec of v
if attrName of attrs
r[attrName] = attrs[attrName]
if single
res[k] = r
else
res[k] ?= {}
res[k][param.join('.')] = r
return res
})
Object.defineProperty(sandbox.context, 'clear', {value: (pattern, timestamp) ->
if sandbox.revision == sandbox.maxRevision
sandbox.clear ?= []
sandbox.clear.push([common.parsePath(pattern), timestamp])
return
})
run = (script, globals, timestamp, deviceData, extensionsCache, startRevision, maxRevision) ->
sandbox.timestamp = timestamp
sandbox.deviceData = deviceData
sandbox.extensionsCache = extensionsCache
sandbox.revision = startRevision
sandbox.maxRevision = maxRevision
sandbox.declarations = []
sandbox.extensions = null
sandbox.clear = null
for k, v of globals
sandbox.context[k] = v
try
ret = script.runInNewContext(sandbox.context, {displayErrors: false})
delete sandbox.context[k] for k of sandbox.context
return {done: true, declarations: sandbox.declarations, extensions: sandbox.extensions, clear: sandbox.clear, returnValue: ret}
catch err
delete sandbox.context[k] for k of sandbox.context
throw err if err not instanceof Exit
return {done: false, declarations: sandbox.declarations, extensions: sandbox.extensions, clear: sandbox.clear}
exports.run = run
+281
View File
@@ -0,0 +1,281 @@
/**
* Copyright 2013-2016 Zaid Abdulla
*
* GenieACS is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* GenieACS 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
const vm = require("vm");
const common = require("./common");
const device = require("./device");
const extensions = require("./extensions");
// Used for throwing to exit user script and commit
const COMMIT = Symbol();
// Used to execute extensions and restart
const EXT = Symbol();
const sandbox = {
timestamp: null,
deviceData: null,
revision: null,
maxRevision: null,
uncommitted: false,
declarations: null,
extensionsCache: null,
extensions: null,
context: vm.createContext()
}
class SandboxDate {
constructor(arg) {
if (arguments.length)
return new (Function.prototype.bind.apply(Date, arguments));
return new Date(sandbox.timestamp);
}
static now() {
return sandbox.timestamp;
}
};
class ParameterWrapper {
constructor(path, attributes, unpacked, unpackedRevision) {
for (let attrName of attributes) {
Object.defineProperty(this, attrName, {get: function() {
if (sandbox.uncommitted)
commit();
if (sandbox.revision !== unpackedRevision) {
unpackedRevision = sandbox.revision;
unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision + 1);
}
if (!unpacked.length)
return undefined;
let attr = sandbox.deviceData.attributes.get(unpacked[0], sandbox.revision + 1)[attrName];
if (!attr)
return undefined;
return attr[1];
}});
}
Object.defineProperty(this, "path", {get: function() {
if (sandbox.uncommitted)
commit();
if (sandbox.revision !== unpackedRevision) {
unpackedRevision = sandbox.revision;
unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision + 1);
}
if (!unpacked.length)
return undefined;
return unpacked[0].join('.');
}});
Object.defineProperty(this, "size", {get: function() {
if (sandbox.uncommitted)
commit();
if (sandbox.revision !== unpackedRevision) {
unpackedRevision = sandbox.revision;
unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision + 1);
}
if (!unpacked.length)
return undefined;
return unpacked.length;
}});
this[Symbol.iterator] = function*() {
if (sandbox.uncommitted)
commit();
if (sandbox.revision !== unpackedRevision) {
unpackedRevision = sandbox.revision;
unpacked = device.unpack(sandbox.deviceData, path, sandbox.revision + 1);
}
for (let p of unpacked)
yield new ParameterWrapper(p, attributes, [p], sandbox.revision);
}
}
}
function declare(path, timestamps, values) {
sandbox.uncommitted = true;
if (!timestamps)
timestamps = {};
let parsedPath = common.parsePath(path);
sandbox.declarations[path] = sandbox.declarations[path] || [parsedPath, 1, {}, null, {}];
let attrs = {};
for (let attrName in timestamps) {
if (attrName === "path") {
if (timestamps.path > sandbox.declarations[path][1])
sandbox.declarations[path][1] = timestamps.path;
}
else {
attrs[attrName] = 1;
if (!(timestamps[attrName] <= sandbox.declarations[path][2][attrName]))
sandbox.declarations[path][2][attrName] = timestamps[attrName];
}
}
if (values) {
for (let attrName in values) {
if (attrName === "path")
sandbox.declarations[path][3] = values.path;
else {
attrs[attrName] = 1;
if (attrName === 'value' && !Array.isArray(values[attrName]))
sandbox.declarations[path][4].value = [values[attrName]];
else
sandbox.declarations[path][4].value = values[attrName];
}
}
}
return new ParameterWrapper(parsedPath, Object.keys(attrs));
}
function clear(path, timestamp, attributes) {
sandbox.uncommitted = true;
if (sandbox.revision === sandbox.maxRevision) {
sandbox.clear = sandbox.clear || [];
sandbox.clear.push([common.parsePath(path), timestamp, attributes]);
}
}
function commit() {
++ sandbox.revision;
sandbox.uncommitted = false;
if (sandbox.revision === sandbox.maxRevision + 1)
throw COMMIT;
else if (sandbox.revision > sandbox.maxRevision + 1)
throw new Error("Declare function should not be called from within a try/catch block");
}
function ext() {
let extCall = Array.from(arguments).map(String);
let key = `${sandbox.revision}: ${JSON.stringify(extCall)}`;
if (key in sandbox.extensionsCache)
return sandbox.extensionsCache[key];
sandbox.extensions[key] = extCall;
throw EXT;
}
Object.defineProperty(sandbox.context, 'Date', {value: SandboxDate});
Object.defineProperty(sandbox.context, 'declare', {value: declare});
Object.defineProperty(sandbox.context, 'clear', {value: clear});
Object.defineProperty(sandbox.context, 'commit', {value: commit});
Object.defineProperty(sandbox.context, 'ext', {value: ext});
function run(script, globals, timestamp, deviceData, extensionsCache, startRevision, maxRevision, callback) {
sandbox.timestamp = timestamp;
sandbox.deviceData = deviceData;
sandbox.extensionsCache = extensionsCache;
sandbox.revision = startRevision;
sandbox.maxRevision = maxRevision;
sandbox.uncommitted = false;
sandbox.declarations = {};
sandbox.extensions = {};
sandbox.clear = null;
for (let n in sandbox.context)
delete sandbox.context[n];
for (let n in globals)
sandbox.context[n] = globals[n];
try {
let ret = script.runInContext(sandbox.context, {displayErrors: false});
let declarations = [];
for (let p in sandbox.declarations)
declarations.push(sandbox.declarations[p]);
let counter = 1;
for (let key in sandbox.extensions) {
++ counter;
extensions.run(sandbox.extensions[key], function(err, res) {
sandbox.extensionsCache[key] = res;
if (-- counter === 0)
return callback(null, sandbox.clear, declarations, true, ret);
});
}
if (-- counter === 0)
return callback(null, sandbox.clear, declarations, true, ret);
}
catch (err) {
if (err === COMMIT) {
let declarations = [];
for (let p in sandbox.declarations)
declarations.push(sandbox.declarations[p]);
let counter = 1;
for (let key in sandbox.extensions) {
++ counter;
extensions.run(sandbox.extensions[key], function(err, res) {
sandbox.extensionsCache[key] = res;
if (-- counter === 0)
return callback(null, sandbox.clear, declarations, false);
});
}
if (-- counter === 0)
return callback(null, sandbox.clear, declarations, false);
}
else if (err === EXT) {
let counter = 1;
for (let key in sandbox.extensions) {
++ counter;
extensions.run(sandbox.extensions[key], function(err, res) {
sandbox.extensionsCache[key] = res;
if (-- counter === 0)
return run(script, globals, timestamp, deviceData, extensionsCache, startRevision, maxRevision, callback);
});
}
if (-- counter === 0)
return run(script, globals, timestamp, deviceData, extensionsCache, startRevision, maxRevision, callback);
}
else
return callback(err);
}
}
exports.run = run;
+53 -54
View File
@@ -52,7 +52,7 @@ init = (deviceId, cwmpVersion, timeout, callback) ->
revisions: []
rpcCount: 0
iteration: 0
extensionsCache: []
extensionsCache: {}
declarations: []
}
@@ -180,7 +180,7 @@ addProvisions = (sessionData, channel, provisions) ->
v.collapse(1) for k, v of sessionData.deviceData.attributes
sessionData.rpcCount = 0
sessionData.revisions = [0]
sessionData.extensionsCache.length = 0
sessionData.extensionsCache = {}
clearProvisions = (sessionData) ->
@@ -195,7 +195,7 @@ clearProvisions = (sessionData) ->
sessionData.deviceData.attributes.collapse(1)
sessionData.rpcCount = 0
sessionData.revisions = [0]
sessionData.extensionsCache.length = 0
sessionData.extensionsCache = {}
runProvisions = (sessionData, provisions, startRevision, endRevision, callback) ->
@@ -203,6 +203,7 @@ runProvisions = (sessionData, provisions, startRevision, endRevision, callback)
_extensions = []
_clear = []
allDeclarations = []
counter = 1
for provision in provisions
if not sessionData.cache.provisions[provision[0]]?
switch provision[0]
@@ -245,21 +246,26 @@ runProvisions = (sessionData, provisions, startRevision, endRevision, callback)
allDeclarations.push([['FactoryReset'], 1, {value: 1}, null, {value: [sessionData.timestamp]}])
continue
ret = sandbox.run(sessionData.cache.provisions[provision[0]].script, {args: provision[1]}, sessionData.timestamp, sessionData.deviceData, sessionData.extensionsCache, startRevision, endRevision)
++ counter
sandbox.run(sessionData.cache.provisions[provision[0]].script,
{args: provision[1]}, sessionData.timestamp, sessionData.deviceData,
sessionData.extensionsCache, startRevision, endRevision,
(err, _clear, _declarations, _done) ->
-- counter
if err
if counter >= 0
counter = 0
return callback(err)
return
if ret.extensions?.length
_extensions = _extensions.concat(ret.extensions)
if ret.clear?.length
_clear = _clear.concat(ret.clear)
done &&= ret.done
allDeclarations = allDeclarations.concat(ret.declarations)
done &&= _done
allDeclarations = allDeclarations.concat(_declarations)
if _extensions.length
return runExtensions(sessionData, endRevision, _extensions, (err) ->
return callback(err) if err
return runProvisions(sessionData, provisions, startRevision, endRevision, callback)
if counter == 0
return callback(null, done, allDeclarations, _clear)
)
if -- counter == 0
return callback(null, done, allDeclarations, _clear)
@@ -269,27 +275,39 @@ runVirtualParameters = (sessionData, provisions, startRevision, endRevision, cal
virtualParameterUpdates = []
_extensions = []
_clear = []
counter = 1
for provision in provisions
++ counter
globals = {TIMESTAMPS: provision[1], VALUES: provision[2]}
ret = sandbox.run(sessionData.cache.virtualParameters[provision[0]].script, globals, sessionData.timestamp, sessionData.deviceData, sessionData.extensionsCache, startRevision, endRevision)
if ret.extensions?.length
_extensions = _extensions.concat(ret.extensions)
if ret.clear?.length
_clear = _clear.concat(ret.clear)
sandbox.run(sessionData.cache.virtualParameters[provision[0]].script, globals,
sessionData.timestamp, sessionData.deviceData,
sessionData.extensionsCache, startRevision, endRevision,
(err, _clear, _declarations, _done, _returnValue) ->
-- counter
if err
if counter >= 0
counter = 0
return callback(err)
return
done &&= ret.done
decs = decs.concat(ret.declarations)
done &&= _done
decs = decs.concat(_declarations)
if ret.done
virtualParameterUpdates.push(ret.returnValue)
if _done
virtualParameterUpdates.push(_returnValue)
if _extensions.length
return runExtensions(sessionData, endRevision, _extensions, (err) ->
return callback(err) if err
return runVirtualParameters(sessionData, provisions, startRevision, endRevision, callback)
if counter == 0
toClear = null
if virtualParameterUpdates.length == provisions.length
for vpu, i in virtualParameterUpdates
toClear = commitVirtualParameter(sessionData, provisions[i], vpu, toClear)
clear(sessionData, toClear, (err) ->
return callback(err, done, decs, _clear)
)
)
if -- counter == 0
toClear = null
if virtualParameterUpdates.length == provisions.length
for vpu, i in virtualParameterUpdates
@@ -388,8 +406,8 @@ runDeclarations = (sessionData, declarations) ->
if declaration[3]?
if Array.isArray(declaration[3])
minInstances = declaration[3].exist[0]
maxInstances = declaration[3].exist[1]
minInstances = declaration[3][0]
maxInstances = declaration[3][1]
else
minInstances = maxInstances = declaration[3]
@@ -557,13 +575,16 @@ rpcRequest = (sessionData, _declarations, callback) ->
delete sessionData.syncState
sessionData.deviceData.timestamps.collapse(1)
sessionData.deviceData.attributes.collapse(1)
sessionData.extensionsCache.splice(0)
sessionData.extensionsCache = {}
return callback(null, null, null, doneProvisions)
rev = sessionData.revisions[sessionData.revisions.length - 1]
sessionData.deviceData.timestamps.collapse(rev + 1)
sessionData.deviceData.attributes.collapse(rev + 1)
sessionData.extensionsCache.splice(rev)
for k of sessionData.extensionsCache
n = Number(k.split(':', 1)[0])
if n > rev
delete sessionData.extensionsCache[k]
sessionData.syncState.virtualParameterDeclarations.length = sessionData.declarations.length
@@ -1039,28 +1060,6 @@ commitVirtualParameter = (sessionData, provision, update, toClear) ->
return device.set(sessionData.deviceData, ['VirtualParameters', provision[0]], timestamp, attributes, toClear)
runExtensions = (sessionData, revision, _extensions, callback) ->
sessionData.extensionsCache[revision] ?= {}
obj = {}
for e in _extensions
obj[JSON.stringify(e)] = e
counter = 1
for k, v of obj
++ counter
do (k, v) ->
extensions.run(v, (err, res) ->
sessionData.extensionsCache[revision][k] = res
if -- counter == 0 or err
counter = 0
return callback(err)
)
if -- counter == 0
return callback()
rpcResponse = (sessionData, id, rpcRes, callback) ->
if id != generateRpcId(sessionData)
return callback(new Error('Request ID not recognized'))