Remove obsolete code
This commit is contained in:
1 parent
19fea2ac72
commit
c3a956ad27
8 files changed
+5
-1183
No files matched your search
@@ -164,8 +164,6 @@ exports.PARAMETERS = require(path.resolve(allConfig.CONFIG_DIR, 'parameters'))
|
||||
# load authentication scripts
|
||||
exports.auth = require(path.resolve(allConfig.CONFIG_DIR, 'auth'))
|
||||
|
||||
exports.CUSTOM_COMMANDS = require(path.resolve(allConfig.CONFIG_DIR, 'custom_commands'))
|
||||
|
||||
exports.get = get
|
||||
|
||||
exports.allConfig = allConfig
|
||||
@@ -1,61 +0,0 @@
|
||||
###
|
||||
# Copyright 2013, 2014 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/>.
|
||||
###
|
||||
|
||||
path = require 'path'
|
||||
config = require './config'
|
||||
common = require './common'
|
||||
|
||||
|
||||
FILES = {}
|
||||
getFile = (filename) ->
|
||||
if filename not of FILES
|
||||
FILES[filename] = require(path.resolve(config.get('CONFIG_DIR'), "custom_commands/#{filename}"))
|
||||
|
||||
return FILES[filename]
|
||||
|
||||
|
||||
exports.execute = (deviceId, command, callback) ->
|
||||
args = command.split(/\s+/)
|
||||
f = args.shift()
|
||||
c = args.shift()
|
||||
|
||||
file = getFile(f)
|
||||
file[c](deviceId, args.join(' '), callback)
|
||||
|
||||
|
||||
exports.getFileCommands = (filename) ->
|
||||
commands = []
|
||||
f = getFile(filename)
|
||||
for k,v of f
|
||||
commands.push(k)
|
||||
return commands
|
||||
|
||||
|
||||
exports.getDeviceCustomCommands = (deviceId) ->
|
||||
commands = {}
|
||||
for k,v of config.CUSTOM_COMMANDS
|
||||
if eval(v).test(deviceId)
|
||||
commands[k] = exports.getFileCommands(k)
|
||||
return commands
|
||||
|
||||
|
||||
exports.getDeviceCustomCommandNames = (deviceId) ->
|
||||
commands = []
|
||||
for k,v of config.CUSTOM_COMMANDS
|
||||
if eval(v).test(deviceId)
|
||||
commands.push(k)
|
||||
return commands
|
||||
@@ -28,29 +28,6 @@ presetsCollection = null
|
||||
objectsCollection = null
|
||||
|
||||
|
||||
# Only flush redis DB if aliases or custom commands config has changed
|
||||
flushRedis = (callback) ->
|
||||
redisClient.get('config_hash', (err, res) ->
|
||||
return callback(err) if err
|
||||
|
||||
hash = require('crypto')
|
||||
.createHash('md5')
|
||||
.update(JSON.stringify(config.PARAMETERS))
|
||||
.update(JSON.stringify(config.CUSTOM_COMMANDS))
|
||||
.digest('hex')
|
||||
|
||||
if res == hash
|
||||
return callback()
|
||||
|
||||
redisClient.flushdb((err) ->
|
||||
return callback(err) if err
|
||||
redisClient.set('config_hash', hash, (err, res) ->
|
||||
return callback(err)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
connect = (callback) ->
|
||||
callbackCounter = 6
|
||||
mongodb.MongoClient.connect(config.get('MONGODB_CONNECTION_URL'), {db:{w:1},server:{autoReconnect:true}}, (err, db) ->
|
||||
@@ -96,17 +73,11 @@ connect = (callback) ->
|
||||
|
||||
exports.redisClient = redisClient = redis.createClient(config.get('REDIS_PORT'), config.get('REDIS_HOST'))
|
||||
redisClient.select(config.get('REDIS_DB'), (err) ->
|
||||
if err
|
||||
callbackCounter = 0
|
||||
return callback(err)
|
||||
|
||||
flushRedis((err) ->
|
||||
if --callbackCounter == 0 or err
|
||||
callbackCounter = 0
|
||||
return callback(err)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
disconnect = () ->
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
###
|
||||
# Copyright 2013, 2014 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/>.
|
||||
#
|
||||
# This file incorporates work covered by the following copyright and
|
||||
# permission notice:
|
||||
#
|
||||
# Copyright 2013 Fanoos Telecom
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
###
|
||||
|
||||
common = require './common'
|
||||
|
||||
|
||||
test = (obj, query) ->
|
||||
for k,v of query
|
||||
if k.charAt(0) == '$' # this is a logical operator
|
||||
res = testExpressions(obj, v, k)
|
||||
else
|
||||
value = common.getParamValueFromPath(obj, k)
|
||||
|
||||
if common.typeOf(v) isnt common.OBJECT_TYPE
|
||||
# TODO comparing array to regex, array to array, and object to object
|
||||
if common.typeOf(value) is common.ARRAY_TYPE
|
||||
res = value.indexOf(v) != -1
|
||||
else
|
||||
if common.typeOf(v) is common.REGEXP_TYPE
|
||||
res = v.test(value)
|
||||
else
|
||||
res = v == value
|
||||
else
|
||||
for k2,v2 of v
|
||||
switch k2
|
||||
when '$ne'
|
||||
if common.typeOf(value) is common.ARRAY_TYPE
|
||||
res = value.indexOf(v2) == -1
|
||||
else
|
||||
res = value != v2
|
||||
when '$lt'
|
||||
res = value < v2
|
||||
when '$lte'
|
||||
res = value <= v2
|
||||
when '$gt'
|
||||
res = value > v2
|
||||
when '$gte'
|
||||
res = value >= v2
|
||||
when '$regex'
|
||||
res = v2.test(value)
|
||||
when '$in'
|
||||
throw new Error('Operator not supported')
|
||||
when '$nin'
|
||||
throw new Error('Operator not supported')
|
||||
when '$all'
|
||||
throw new Error('Operator not supported')
|
||||
when '$exists'
|
||||
throw new Error('Operator not supported')
|
||||
else
|
||||
throw new Error('Operator not supported')
|
||||
|
||||
if not res
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
|
||||
testExpressions = (obj, expressions, lop) ->
|
||||
for f in expressions
|
||||
res = test(obj, f)
|
||||
|
||||
switch lop
|
||||
when '$and'
|
||||
return false if not res
|
||||
when '$or'
|
||||
return true if res
|
||||
when '$nor'
|
||||
return false if res
|
||||
|
||||
return switch lop
|
||||
when '$and' then true
|
||||
when '$or' then false
|
||||
when '$nor' then true
|
||||
else throw new Error('Unknown logical operator')
|
||||
|
||||
|
||||
# generate field projection from a query
|
||||
projection = (query, proj) ->
|
||||
for k,v of query
|
||||
if k.charAt(0) == '$' # this is a logical operator
|
||||
for q in v
|
||||
projection(q, proj)
|
||||
else
|
||||
proj[k] = 1
|
||||
|
||||
# optimize projection by removing overlaps
|
||||
optimizeProjection = (obj) ->
|
||||
keys = Object.keys(obj).sort()
|
||||
return if keys.length <= 1
|
||||
i = 1
|
||||
while i < keys.length
|
||||
a = keys[i-1]
|
||||
b = keys[i]
|
||||
if common.startsWith(b, a)
|
||||
if b.charAt(a.length) == '.' or b.charAt(a.length - 1) == '.'
|
||||
delete obj[b]
|
||||
keys.splice(i--, 1)
|
||||
++ i
|
||||
return
|
||||
|
||||
|
||||
exports.test = test
|
||||
exports.projection = projection
|
||||
exports.optimizeProjection = optimizeProjection
|
||||
+5
-18
@@ -1,5 +1,5 @@
|
||||
###
|
||||
# Copyright 2013, 2014 Zaid Abdulla
|
||||
# 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
|
||||
@@ -38,15 +38,15 @@
|
||||
# THE SOFTWARE.
|
||||
###
|
||||
|
||||
config = require './config'
|
||||
url = require 'url'
|
||||
common = require './common'
|
||||
db = require './db'
|
||||
mongodb = require 'mongodb'
|
||||
querystring = require 'querystring'
|
||||
|
||||
config = require './config'
|
||||
common = require './common'
|
||||
db = require './db'
|
||||
query = require './query'
|
||||
apiFunctions = require './api-functions'
|
||||
presets = require './presets'
|
||||
|
||||
# regular expression objects
|
||||
DEVICE_TASKS_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/tasks\/?$/
|
||||
@@ -57,7 +57,6 @@ OBJECTS_REGEX = /^\/objects\/([a-zA-Z0-9\-\_\%]+)\/?$/
|
||||
FILES_REGEX = /^\/files\/([a-zA-Z0-9\%\!\*\'\(\)\;\:\@\&\=\+\$\,\?\#\[\]\-\_\.\~]+)\/?$/
|
||||
PING_REGEX = /^\/ping\/([a-zA-Z0-9\-\_\.]+)\/?$/
|
||||
QUERY_REGEX = /^\/([a-zA-Z0-9_]+s)\/?$/
|
||||
DEVICE_PRESET_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/preset\/?$/
|
||||
DELETE_DEVICE_REGEX = /^\/devices\/([a-zA-Z0-9\-\_\%]+)\/?$/
|
||||
|
||||
|
||||
@@ -248,18 +247,6 @@ listener = (request, response) ->
|
||||
else
|
||||
response.writeHead 405, {'Allow': 'POST'}
|
||||
response.end('405 Method Not Allowed')
|
||||
else if DEVICE_PRESET_REGEX.test(urlParts.pathname)
|
||||
deviceId = querystring.unescape(DEVICE_PRESET_REGEX.exec(urlParts.pathname)[1])
|
||||
if request.method is 'GET'
|
||||
db.getPresetsObjectsAliases((allPresets, allObjects, allAliases) ->
|
||||
presets.getDevicePreset(deviceId, allPresets, allObjects, allAliases, (devicePreset) ->
|
||||
response.writeHead(200, {'Content-Type' : 'application/json'})
|
||||
response.end(JSON.stringify(devicePreset))
|
||||
)
|
||||
)
|
||||
else
|
||||
response.writeHead 405, {'Allow': 'GET'}
|
||||
response.end('405 Method Not Allowed')
|
||||
else if TASKS_REGEX.test(urlParts.pathname)
|
||||
r = TASKS_REGEX.exec(urlParts.pathname)
|
||||
taskId = mongodb.ObjectID(querystring.unescape(r[1]))
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
###
|
||||
# Copyright 2013, 2014 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/>.
|
||||
#
|
||||
# This file incorporates work covered by the following copyright and
|
||||
# permission notice:
|
||||
#
|
||||
# Copyright 2013 Fanoos Telecom
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
###
|
||||
|
||||
config = require './config'
|
||||
common = require './common'
|
||||
db = require './db'
|
||||
mongoQuery = require './mongo-query'
|
||||
query = require './query'
|
||||
log = require('util').log
|
||||
customCommands = require './custom-commands'
|
||||
|
||||
|
||||
exports.calculatePresetsHash = (presets, objects) ->
|
||||
crypto = require('crypto')
|
||||
hash = crypto.createHash('md5').update(JSON.stringify(presets) + JSON.stringify(objects)).digest('hex')
|
||||
return hash
|
||||
|
||||
|
||||
matchObject = (object, param) ->
|
||||
flatObject = common.flattenObject(object)
|
||||
if object._keys?.length
|
||||
keys = object._keys
|
||||
else
|
||||
# Consider all parameters as keys if none are defined
|
||||
keys = (k for k in Object.keys(flatObject) when k[0] != '_')
|
||||
|
||||
for k in keys
|
||||
p = common.getParamValueFromPath(param, k)
|
||||
return false if not p?
|
||||
v = common.matchType(p._value, flatObject[k])
|
||||
|
||||
if p._value != common.matchType(p._value, flatObject[k])
|
||||
return false
|
||||
|
||||
return true
|
||||
|
||||
|
||||
exports.getDevicePreset = (deviceId, presets, objects, aliases, callback) ->
|
||||
deviceCustomCommands = customCommands.getDeviceCustomCommands(deviceId)
|
||||
# only fetch relevant params
|
||||
projection = {_id : 1}
|
||||
for p in presets
|
||||
if common.typeOf(p.precondition) is common.STRING_TYPE
|
||||
p.precondition = query.expand(JSON.parse(p.precondition), aliases)
|
||||
else
|
||||
# Accept an object for backward compatiblity
|
||||
p.precondition = query.expand(p.precondition ? {}, aliases)
|
||||
mongoQuery.projection(p.precondition, projection)
|
||||
|
||||
for c in p.configurations
|
||||
switch c.type
|
||||
when 'value', 'age'
|
||||
projection[c.name] = 1
|
||||
when 'custom_command_value', 'custom_command_age'
|
||||
commandfile = c.command.split(' ', 1)
|
||||
if commandfile of deviceCustomCommands
|
||||
projection["_customCommands.#{c.command.split(' ', 1)}"] = 1
|
||||
when 'software_version'
|
||||
projection['Device.DeviceInfo.SoftwareVersion'] = 1
|
||||
projection['InternetGatewayDevice.DeviceInfo.SoftwareVersion'] = 1
|
||||
when 'add_tag', 'delete_tag'
|
||||
projection['_tags'] = 1
|
||||
when 'add_object', 'delete_object'
|
||||
projection[c.name] = 1
|
||||
else
|
||||
throw new Error('Unknown configuration type')
|
||||
|
||||
mongoQuery.optimizeProjection(projection)
|
||||
|
||||
db.devicesCollection.findOne({'_id' : deviceId}, projection, (err, device) ->
|
||||
throw err if err
|
||||
devicePresets = []
|
||||
for p in presets
|
||||
if mongoQuery.test(device, p.precondition)
|
||||
devicePresets.push(p)
|
||||
|
||||
configurations = accumulateConfigurations(devicePresets, objects)
|
||||
|
||||
getParamDetails = (device, path) ->
|
||||
p = common.getParamValueFromPath(device, path)
|
||||
return undefined if not p?
|
||||
details = {}
|
||||
for k,v of p
|
||||
details[k] = v if k[0] == '_'
|
||||
return details
|
||||
|
||||
devicePreset = {}
|
||||
for c in configurations
|
||||
switch c.type
|
||||
when 'value'
|
||||
devicePreset.parameters ?= {}
|
||||
devicePreset.parameters[c.name] ?= {}
|
||||
devicePreset.parameters[c.name].preset ?= {}
|
||||
devicePreset.parameters[c.name].preset.value = c.value
|
||||
devicePreset.parameters[c.name].current = getParamDetails(device, c.name)
|
||||
|
||||
when 'age'
|
||||
devicePreset.parameters ?= {}
|
||||
devicePreset.parameters[c.name] ?= {}
|
||||
devicePreset.parameters[c.name].preset ?= {}
|
||||
devicePreset.parameters[c.name].preset.expiry = parseInt(c.age)
|
||||
devicePreset.parameters[c.name].current = getParamDetails(device, c.name)
|
||||
|
||||
when 'add_tag'
|
||||
devicePreset.tags ?= {}
|
||||
devicePreset.tags[c.tag] = 2 | c.tag in (device._tags ? [])
|
||||
|
||||
when 'delete_tag'
|
||||
devicePreset.tags ?= {}
|
||||
devicePreset.tags[c.tag] = 0 | c.tag in (device._tags ? [])
|
||||
|
||||
when 'add_object'
|
||||
devicePreset.objects ?= {}
|
||||
devicePreset.objects[c.name] ?= {}
|
||||
devicePreset.objects[c.name][c.object] ?= {}
|
||||
devicePreset.objects[c.name][c.object].preset = objects[c.object]
|
||||
continue if not param = common.getParamValueFromPath(device, c.name)
|
||||
devicePreset.objects[c.name][c.object].current ?= {}
|
||||
for k,p of param
|
||||
continue if k[0] == '_'
|
||||
if matchObject(objects[c.object], p)
|
||||
devicePreset.objects[c.name][c.object].current[k] = p
|
||||
|
||||
when 'delete_object'
|
||||
devicePreset.objects ?= {}
|
||||
devicePreset.objects[c.name] ?= {}
|
||||
devicePreset.objects[c.name][c.object] ?= {}
|
||||
continue if not param = common.getParamValueFromPath(device, c.name)
|
||||
devicePreset.objects[c.name][c.object].current ?= {}
|
||||
for k,p of param
|
||||
continue if k[0] == '_'
|
||||
if matchObject(objects[c.object], p)
|
||||
devicePreset.objects[c.name][c.object].current[k] = p
|
||||
|
||||
when 'custom_command_value'
|
||||
[filename, commandName] = c.command.split(' ', 2)
|
||||
devicePreset.customCommands ?= {}
|
||||
devicePreset.customCommands[filename] ?= {}
|
||||
devicePreset.customCommands[filename].preset ?= {}
|
||||
devicePreset.customCommands[filename].preset.value = c.value
|
||||
devicePreset.customCommands[filename].preset.valueCommand = c.command
|
||||
continue if not deviceCustomCommands[filename]? or commandName not in deviceCustomCommands[filename]
|
||||
if cmd = common.getParamValueFromPath(device, "_customCommands.#{filename}")
|
||||
devicePreset.customCommands[filename].current = cmd
|
||||
else
|
||||
devicePreset.customCommands[filename].current ?= {}
|
||||
|
||||
when 'custom_command_age'
|
||||
[filename, commandName] = c.command.split(' ', 2)
|
||||
devicePreset.customCommands ?= {}
|
||||
devicePreset.customCommands[filename] ?= {}
|
||||
devicePreset.customCommands[filename].preset ?= {}
|
||||
devicePreset.customCommands[filename].preset.expiry = parseInt(c.age)
|
||||
devicePreset.customCommands[filename].preset.expiryCommand = c.command
|
||||
continue if not deviceCustomCommands[filename]? or commandName not in deviceCustomCommands[filename]
|
||||
if cmd = common.getParamValueFromPath(device, "_customCommands.#{filename}")
|
||||
devicePreset.customCommands[filename].current = cmd
|
||||
else
|
||||
devicePreset.customCommands[filename].current ?= {}
|
||||
|
||||
when 'software_version'
|
||||
devicePreset.softwareVersion ?= {}
|
||||
devicePreset.softwareVersion.preset = c.software_version
|
||||
if device?.Device? # TR-181 data model
|
||||
devicePreset.softwareVersion.current = common.getParamValueFromPath(device, 'Device.DeviceInfo.SoftwareVersion')
|
||||
else # TR-098 data model
|
||||
devicePreset.softwareVersion.current = common.getParamValueFromPath(device, 'InternetGatewayDevice.DeviceInfo.SoftwareVersion')
|
||||
|
||||
else
|
||||
throw new Error('Unknown configuration type')
|
||||
|
||||
callback(devicePreset)
|
||||
)
|
||||
|
||||
|
||||
exports.processDevicePreset = (deviceId, devicePreset, callback) ->
|
||||
PRESETS_TIME_PADDING = config.get('PRESETS_TIME_PADDING')
|
||||
now = new Date()
|
||||
expiry = config.get('PRESETS_CACHE_DURATION', deviceId)
|
||||
getParameterValues = []
|
||||
setParameterValues = []
|
||||
addTags = []
|
||||
deleteTags = []
|
||||
taskList = []
|
||||
|
||||
# Parameters
|
||||
for parameterPath, parameterDetails of devicePreset.parameters ? {}
|
||||
continue if not parameterDetails.current?
|
||||
expiry = Math.min(expiry, parameterDetails.preset.expiry) if parameterDetails.preset.expiry?
|
||||
presetValue = parameterDetails.preset.value
|
||||
currentValue = parameterDetails.current._value
|
||||
if presetValue? and currentValue != (presetValue = common.matchType(currentValue, presetValue))
|
||||
setParameterValues.push([parameterPath, presetValue, parameterDetails.current._type])
|
||||
else if parameterDetails.preset.expiry?
|
||||
diff = parameterDetails.preset.expiry - (now - (parameterDetails.current._timestamp ? 0)) / 1000
|
||||
if diff <= PRESETS_TIME_PADDING
|
||||
if parameterDetails.current._value?
|
||||
getParameterValues.push(parameterPath)
|
||||
else
|
||||
taskList.push({device : deviceId, name : 'refreshObject', objectName : parameterPath})
|
||||
else
|
||||
expiry = Math.min(expiry, diff)
|
||||
|
||||
# Objects
|
||||
for parameterPath, object of devicePreset.objects ? {}
|
||||
for objectName, objectDetails of object
|
||||
continue if not objectDetails.current?
|
||||
if objectDetails.preset?
|
||||
flatObject = common.flattenObject(objectDetails.preset)
|
||||
if Object.keys(objectDetails.current).length > 0
|
||||
for i, obj of objectDetails.current
|
||||
for paramName, paramValue of flatObject
|
||||
continue if paramName[0] == '_'
|
||||
currentValue = common.getParamValueFromPath(obj, paramName)
|
||||
if currentValue?
|
||||
presetValue = common.matchType(currentValue._value, flatObject[paramName])
|
||||
if currentValue._value != presetValue
|
||||
setParameterValues.push(["#{parameterPath}.#{i}.#{paramName}", presetValue, currentValue._type])
|
||||
else
|
||||
vals = []
|
||||
for k,v of flatObject
|
||||
vals.push([k, v]) if k[0] != '_'
|
||||
taskList.push({device : deviceId, name : 'addObject', objectName : parameterPath, parameterValues : vals})
|
||||
else if Object.keys(objectDetails.current).length > 0
|
||||
for i, obj of objectDetails.current
|
||||
taskList.push({device : deviceId, name : 'deleteObject', objectName : "#{parameterPath}.#{i}"})
|
||||
|
||||
# Tags
|
||||
for tag, v of devicePreset.tags ? {}
|
||||
if v == 1
|
||||
deleteTags.push(tag)
|
||||
else if v == 2
|
||||
addTags.push(tag)
|
||||
|
||||
# Custom commands
|
||||
for commandName, commandDetails of devicePreset.customCommands ? {}
|
||||
continue if not commandDetails.current?
|
||||
expiry = Math.min(expiry, commandDetails.preset.expiry) if commandDetails.preset.expiry?
|
||||
currentValue = commandDetails.current._value
|
||||
presetValue = common.matchType(currentValue, commandDetails.preset.value)
|
||||
if commandDetails.preset.value? and currentValue != presetValue
|
||||
taskList.push({device : deviceId, name : 'customCommand', command : commandDetails.preset.valueCommand})
|
||||
else if commandDetails.preset.expiry?
|
||||
diff = commandDetails.preset.expiry - (now - (commandDetails.current._timestamp ? 0)) / 1000
|
||||
if diff <= PRESETS_TIME_PADDING
|
||||
taskList.push({device : deviceId, name : 'customCommand', command : commandDetails.preset.expiryCommand})
|
||||
else
|
||||
expiry = Math.min(expiry, diff)
|
||||
|
||||
if setParameterValues.length
|
||||
taskList.push {device : deviceId, name : 'setParameterValues', parameterValues: setParameterValues, timestamp : now}
|
||||
|
||||
if getParameterValues.length
|
||||
taskList.push {device : deviceId, name : 'getParameterValues', parameterNames: getParameterValues, timestamp : now}
|
||||
|
||||
callback(taskList, addTags, deleteTags, expiry)
|
||||
|
||||
|
||||
getObjectHash = (object) ->
|
||||
if object._keys?.length
|
||||
keys = object._keys
|
||||
else
|
||||
keys = (k for k in Object.keys(object) when k[0] != '_')
|
||||
|
||||
keys.sort()
|
||||
|
||||
hash = []
|
||||
for k in keys
|
||||
hash.push(k)
|
||||
hash.push(common.getParamValueFromPath(object, k))
|
||||
|
||||
return JSON.stringify(hash)
|
||||
|
||||
|
||||
accumulateConfigurations = (presets, objects) ->
|
||||
maxWeights = {}
|
||||
configurations = {}
|
||||
for p in presets
|
||||
for c in p.configurations
|
||||
configurationHash = switch c.type
|
||||
when 'add_tag', 'delete_tag'
|
||||
"tag_#{c.tag}"
|
||||
when 'add_object', 'delete_object'
|
||||
objectHash = getObjectHash(objects[c.object])
|
||||
"object_#{c.name}_#{objectHash}"
|
||||
when 'software_version'
|
||||
'software_version'
|
||||
when 'custom_command_age', 'custom_command_value'
|
||||
[filename, commandName] = c.command.split(' ', 2)
|
||||
"#{c.type}_#{filename}_#{commandName}"
|
||||
else
|
||||
"#{c.type}_#{c.name}"
|
||||
|
||||
if not maxWeights[configurationHash]? or p.weight > maxWeights[configurationHash]
|
||||
configurations[configurationHash] = c
|
||||
maxWeights[configurationHash] = p.weight
|
||||
|
||||
configurationsList = (configurations[c] for c of configurations)
|
||||
return configurationsList
|
||||
@@ -1,379 +0,0 @@
|
||||
###
|
||||
# Copyright 2013, 2014 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/>.
|
||||
#
|
||||
# This file incorporates work covered by the following copyright and
|
||||
# permission notice:
|
||||
#
|
||||
# Copyright 2013 Fanoos Telecom
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
###
|
||||
|
||||
config = require './config'
|
||||
common = require './common'
|
||||
db = require './db'
|
||||
mongodb = require 'mongodb'
|
||||
customCommands = require './custom-commands'
|
||||
url = require 'url'
|
||||
|
||||
exports.STATUS_OK = STATUS_OK = 1
|
||||
exports.STATUS_FAULT = STATUS_FAULT = 2
|
||||
exports.STATUS_COMPLETED = STATUS_COMPLETED = 4
|
||||
exports.STATUS_SAVE = STATUS_SAVE = 128 # not mutually exclusive with the rest
|
||||
|
||||
|
||||
this.refreshObject = (task, methodResponse, callback) ->
|
||||
task.session = {} if not task.session?
|
||||
|
||||
if not task.session.subtask?
|
||||
path = task.objectName
|
||||
path += '.' if path != '' and not common.endsWith(path, '.')
|
||||
task.session.subtask = {device : task.device, name : 'getParameterNames', parameterPath : path}
|
||||
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
if task.session.subtask.name is 'getParameterNames'
|
||||
this.getParameterNames(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) =>
|
||||
task.session.parameterNames ?= []
|
||||
if deviceUpdates?.parameterNames?
|
||||
for p in deviceUpdates.parameterNames
|
||||
if not common.endsWith(p[0], '.')
|
||||
task.session.parameterNames.push(p[0])
|
||||
|
||||
if status & STATUS_COMPLETED
|
||||
task.session.subtask = {device : task.device, name : 'getParameterValues', parameterNames : task.session.parameterNames}
|
||||
delete task.session.parameterNames
|
||||
|
||||
this.getParameterValues(task.session.subtask, {}, (err, status, methodRequest) ->
|
||||
# ignore deviceUpdates returned by first call to getParameterValues
|
||||
callback(err, status, methodRequest, deviceUpdates)
|
||||
)
|
||||
else if status & STATUS_OK
|
||||
callback(err, STATUS_OK, methodRequest, deviceUpdates)
|
||||
else
|
||||
throw new Error('Unexpected subtask status')
|
||||
)
|
||||
else if task.session.subtask.name is 'getParameterValues'
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
return callback(null, STATUS_FAULT)
|
||||
|
||||
this.getParameterValues(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) ->
|
||||
callback(err, status, methodRequest, deviceUpdates)
|
||||
)
|
||||
else
|
||||
throw Error('Unexpected subtask name')
|
||||
|
||||
|
||||
this.getParameterNames = (task, methodResponse, callback) ->
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
GET_PARAMETER_NAMES_DEPTH_THRESHOLD = config.get('GET_PARAMETER_NAMES_DEPTH_THRESHOLD', task.device)
|
||||
|
||||
getParameterDepth = (param) ->
|
||||
return 0 if !param
|
||||
return (param[...-1] + '.').split('.').length - 1
|
||||
|
||||
if methodResponse.type is 'GetParameterNamesResponse'
|
||||
path = task.session.queue.pop()
|
||||
|
||||
# If parameter depth higher than the threshold, nextLevel was set to false
|
||||
if not task.nextLevel? and getParameterDepth(path) < GET_PARAMETER_NAMES_DEPTH_THRESHOLD
|
||||
for p in methodResponse.parameterList
|
||||
task.session.queue.push(p[0]) if p[0][-1..] == '.'
|
||||
|
||||
deviceUpdates = {parameterNames : methodResponse.parameterList}
|
||||
|
||||
# Make sure that for each parameter, all its parents are explicitly included
|
||||
found = {}
|
||||
found[path] = 0 if !!path
|
||||
for p in deviceUpdates.parameterNames
|
||||
param = p[0]
|
||||
i = param.length
|
||||
while (i = param.lastIndexOf('.', i-1)) > path.length
|
||||
pp = param.slice(0, i + 1)
|
||||
break if found[pp]?
|
||||
found[pp] = 0
|
||||
found[p[0]] = 1
|
||||
|
||||
for k, v of found
|
||||
deviceUpdates.parameterNames.push([k]) if v == 0
|
||||
else
|
||||
task.session = {queue : [task.parameterPath]}
|
||||
|
||||
if task.session.queue.length > 0
|
||||
path = task.session.queue[-1..][0]
|
||||
methodRequest = {
|
||||
type : 'GetParameterNames',
|
||||
parameterPath : path,
|
||||
nextLevel : task.nextLevel ? getParameterDepth(path) < GET_PARAMETER_NAMES_DEPTH_THRESHOLD
|
||||
}
|
||||
return callback(null, STATUS_OK, methodRequest, deviceUpdates)
|
||||
else
|
||||
return callback(null, STATUS_COMPLETED, null, deviceUpdates)
|
||||
|
||||
|
||||
this.getParameterValues = (task, methodResponse, callback) ->
|
||||
task.session = {} if not task.session?
|
||||
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
if not task.session.currentIndex?
|
||||
task.session.currentIndex = 0
|
||||
else if methodResponse.parameterList?
|
||||
task.session.currentIndex = task.session.nextIndex
|
||||
|
||||
task.session.nextIndex = Math.min(task.session.currentIndex + config.get('TASK_PARAMETERS_BATCH_SIZE', task.device), task.parameterNames.length)
|
||||
names = task.parameterNames.slice(task.session.currentIndex, task.session.nextIndex)
|
||||
|
||||
if methodResponse.type is 'GetParameterValuesResponse'
|
||||
deviceUpdates = {parameterValues : methodResponse.parameterList}
|
||||
|
||||
if names.length == 0
|
||||
callback(null, STATUS_COMPLETED, null, deviceUpdates)
|
||||
else
|
||||
methodRequest = {
|
||||
type : 'GetParameterValues',
|
||||
parameterNames : names
|
||||
}
|
||||
callback(null, STATUS_OK, methodRequest, deviceUpdates)
|
||||
|
||||
|
||||
this.setParameterValues = (task, methodResponse, callback) ->
|
||||
task.session = {} if not task.session?
|
||||
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
if not task.session.currentIndex?
|
||||
task.session.currentIndex = 0
|
||||
else if methodResponse.type is 'SetParameterValuesResponse'
|
||||
prevValues = task.parameterValues.slice(task.session.currentIndex, task.session.nextIndex)
|
||||
task.session.currentIndex = task.session.nextIndex
|
||||
|
||||
task.session.nextIndex = Math.min(task.session.currentIndex + config.get('TASK_PARAMETERS_BATCH_SIZE', task.device), task.parameterValues.length)
|
||||
values = task.parameterValues.slice(task.session.currentIndex, task.session.nextIndex)
|
||||
|
||||
if prevValues?
|
||||
deviceUpdates = {parameterValues : prevValues}
|
||||
|
||||
if values.length == 0
|
||||
callback(null, STATUS_COMPLETED, null, deviceUpdates)
|
||||
else
|
||||
callback(null, STATUS_OK, {type : 'SetParameterValues', parameterList : values}, deviceUpdates)
|
||||
|
||||
|
||||
this.addObject = (task, methodResponse, callback) ->
|
||||
task.session = {} if not task.session?
|
||||
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
allDeviceUpdates = {}
|
||||
|
||||
if not task.session.instanceNumber?
|
||||
if methodResponse.type is 'AddObjectResponse'
|
||||
status_save = STATUS_SAVE
|
||||
task.session.instanceNumber = methodResponse.instanceNumber
|
||||
# TODO Don't specify nextLevel explicity. Also consider using refreshObject instead.
|
||||
task.session.subtask = {device : task.device, name : 'getParameterNames', parameterPath : "#{task.objectName}.#{task.session.instanceNumber}.", nextLevel : false}
|
||||
task.session.appliedParameterValues = []
|
||||
task.session.parameterNames = []
|
||||
else
|
||||
callback(null, STATUS_OK, {type : 'AddObject', objectName : "#{task.objectName}."})
|
||||
return
|
||||
|
||||
subtask = () =>
|
||||
switch task.session.subtask.name
|
||||
when 'getParameterNames'
|
||||
this.getParameterNames(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) =>
|
||||
common.extend(allDeviceUpdates, deviceUpdates)
|
||||
if deviceUpdates and deviceUpdates.parameterNames
|
||||
for p in deviceUpdates.parameterNames
|
||||
task.session.parameterNames.push(p[0]) if not common.endsWith(p[0], '.')
|
||||
|
||||
if status & STATUS_COMPLETED
|
||||
task.session.subtask = {device : task.device, name : 'getParameterValues', parameterNames : task.session.parameterNames}
|
||||
subtask()
|
||||
else if status & STATUS_OK
|
||||
# Use STATUS_SAVE to avoid adding duplicate object in case of error
|
||||
callback(err, STATUS_OK | status_save, methodRequest, allDeviceUpdates)
|
||||
else
|
||||
throw Error('Unexpected subtask status')
|
||||
)
|
||||
when 'getParameterValues'
|
||||
this.getParameterValues(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) =>
|
||||
common.extend(allDeviceUpdates, deviceUpdates)
|
||||
# if values are given, compare with default values
|
||||
if task.parameterValues?
|
||||
if deviceUpdates and deviceUpdates.parameterValues?
|
||||
for p1 in deviceUpdates.parameterValues
|
||||
for p2 in task.parameterValues
|
||||
if p1[0] == "#{task.objectName}.#{task.session.instanceNumber}.#{p2[0]}"
|
||||
t = if p2[2] then p2[2] else p1[2]
|
||||
v = common.matchType(p1[1], p2[1])
|
||||
# TODO only include if writable
|
||||
task.session.appliedParameterValues.push([p1[0], v, t])
|
||||
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
return callback(null, STATUS_FAULT)
|
||||
|
||||
if status & STATUS_COMPLETED and task.session.appliedParameterValues.length > 0
|
||||
task.session.subtask = {device : task.device, name : 'setParameterValues', parameterValues : task.session.appliedParameterValues}
|
||||
subtask()
|
||||
else
|
||||
callback(err, status, methodRequest, allDeviceUpdates)
|
||||
)
|
||||
when 'setParameterValues'
|
||||
this.setParameterValues(task.session.subtask, methodResponse, (err, status, methodRequest, deviceUpdates) =>
|
||||
common.extend(allDeviceUpdates, deviceUpdates)
|
||||
callback(err, status, methodRequest, allDeviceUpdates)
|
||||
)
|
||||
subtask()
|
||||
|
||||
|
||||
this.deleteObject = (task, methodResponse, callback) ->
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
if methodResponse.type is 'DeleteObjectResponse'
|
||||
callback(null, STATUS_COMPLETED, null, {deletedObjects : [task.objectName]})
|
||||
else
|
||||
methodRequest = {
|
||||
type : 'DeleteObject',
|
||||
objectName : "#{task.objectName}."
|
||||
}
|
||||
callback(null, STATUS_OK, methodRequest)
|
||||
|
||||
|
||||
this.reboot = (task, methodResponse, callback) ->
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
if methodResponse.type isnt 'RebootResponse'
|
||||
callback(null, STATUS_OK, {type : 'Reboot'})
|
||||
else
|
||||
callback(null, STATUS_COMPLETED)
|
||||
|
||||
|
||||
this.factoryReset = (task, methodResponse, callback) ->
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
if methodResponse.type isnt 'FactoryResetResponse'
|
||||
callback(null, STATUS_OK, {type : 'FactoryReset'})
|
||||
else
|
||||
callback(null, STATUS_COMPLETED)
|
||||
|
||||
|
||||
this.download = (task, methodResponse, callback) ->
|
||||
if methodResponse.faultcode?
|
||||
task.fault = methodResponse
|
||||
callback(null, STATUS_FAULT)
|
||||
return
|
||||
|
||||
if methodResponse.type isnt 'DownloadResponse'
|
||||
if task.url?
|
||||
methodRequest = {
|
||||
type : 'Download',
|
||||
fileType : task.fileType or '1 Firmware Upgrade Image',
|
||||
url : task.url,
|
||||
username: task.username,
|
||||
password: task.password,
|
||||
successUrl : task.successUrl,
|
||||
failureUrl : task.failureUrl
|
||||
}
|
||||
callback(null, STATUS_OK, methodRequest)
|
||||
else
|
||||
db.filesCollection.findOne({_id : mongodb.ObjectID(String(task.file))}, (err, file) ->
|
||||
if not file?
|
||||
callback('File not found')
|
||||
return
|
||||
else if err?
|
||||
callback(err)
|
||||
return
|
||||
|
||||
l = {
|
||||
protocol : if config.get('FS_SSL') then 'https' else 'http',
|
||||
hostname : config.get('FS_IP'),
|
||||
port : config.get('FS_PORT'),
|
||||
pathname : encodeURIComponent(file.filename)
|
||||
}
|
||||
|
||||
methodRequest = {
|
||||
type : 'Download',
|
||||
fileType : file.metadata.fileType,
|
||||
fileSize : file.length,
|
||||
url : url.format(l),
|
||||
successUrl : task.successUrl,
|
||||
failureUrl : task.failureUrl
|
||||
}
|
||||
callback(null, STATUS_OK, methodRequest)
|
||||
)
|
||||
else
|
||||
callback(null, STATUS_COMPLETED)
|
||||
|
||||
|
||||
this.customCommand = (task, methodResponse, callback) ->
|
||||
# TODO implement timeout
|
||||
customCommands.execute(task.device, task.command, (err, value) ->
|
||||
if err
|
||||
task.fault = {name : err.name, message : err.message}
|
||||
callback(null, STATUS_FAULT)
|
||||
else
|
||||
commandName = task.command.split(' ', 2)[0]
|
||||
callback(null, STATUS_COMPLETED, null, {customCommands : [[commandName, value]]})
|
||||
)
|
||||
|
||||
|
||||
exports.task = (task, methodResponse, callback) ->
|
||||
this[task.name](task, methodResponse, callback)
|
||||
@@ -1,217 +0,0 @@
|
||||
###
|
||||
# Copyright 2013, 2014 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/>.
|
||||
###
|
||||
|
||||
common = require './common'
|
||||
db = require './db'
|
||||
normalize = require('./normalize').normalize
|
||||
mongoQuery = require './mongo-query'
|
||||
parameters = require './parameters'
|
||||
|
||||
OBJECT_REGEX = /\.$/
|
||||
INSTANCE_REGEX = /\.[\d]+\.$/
|
||||
|
||||
|
||||
queueUpdates = (deviceId, deviceUpdates, callback) ->
|
||||
return callback() if not deviceUpdates? or Object.keys(deviceUpdates).length == 0
|
||||
# TODO consider setting expiry
|
||||
db.redisClient.rpush("#{deviceId}_updates", JSON.stringify(deviceUpdates), (err, res) ->
|
||||
return callback(err)
|
||||
)
|
||||
|
||||
|
||||
clearUpdatesQueue = (deviceId, callback) ->
|
||||
db.redisClient.del("#{deviceId}_updates", (err, res) ->
|
||||
return callback(err)
|
||||
)
|
||||
|
||||
|
||||
commitUpdates = (deviceId, deviceUpdates, commitQueue, callback) ->
|
||||
now = new Date(Date.now())
|
||||
update = {'$set' : {}, '$unset' : {}}
|
||||
|
||||
# Used to find parameters that no longer exist in device
|
||||
presentPaths = null
|
||||
projection = null
|
||||
|
||||
# Keep track of alias changes to clear cache if needed
|
||||
newAliases = null
|
||||
|
||||
processBatch = (updatesBatch, cb) ->
|
||||
if updatesBatch.informEvents?
|
||||
update['$set']['_lastInform'] = now
|
||||
update['$set']['_lastBoot'] = now if '1 BOOT' in updatesBatch.informEvents
|
||||
update['$set']['_lastBootstrap'] = now if '0 BOOTSTRAP' in updatesBatch.informEvents
|
||||
|
||||
if updatesBatch.parameterValues?
|
||||
for p in updatesBatch.parameterValues
|
||||
origValue = p[1]
|
||||
v = normalize(p[0], origValue)
|
||||
|
||||
path = if common.endsWith(p[0], '.') then p[0] else "#{p[0]}."
|
||||
if v == origValue
|
||||
update['$unset']["#{path}_orig"] = 1
|
||||
else
|
||||
update['$set']["#{path}_orig"] = origValue
|
||||
update['$set']["#{path}_value"] = v
|
||||
update['$set']["#{path}_timestamp"] = now
|
||||
if p[2]?
|
||||
update['$set']["#{path}_type"] = p[2]
|
||||
|
||||
if updatesBatch.deletedObjects?
|
||||
for p in updatesBatch.deletedObjects
|
||||
update['$unset'][p] = 1
|
||||
|
||||
if updatesBatch.parameterNames?
|
||||
presentPaths ?= {}
|
||||
projection ?= {}
|
||||
|
||||
for p in updatesBatch.parameterNames
|
||||
if OBJECT_REGEX.test(p[0])
|
||||
path = p[0].slice(0, -1)
|
||||
if INSTANCE_REGEX.test(p[0])
|
||||
update['$set']["#{path}._instance"] = true
|
||||
else
|
||||
update['$set']["#{path}._object"] = true
|
||||
else
|
||||
path = p[0]
|
||||
update['$set']["#{path}._writable"] = p[1] if p[1]?
|
||||
update['$set']["#{path}._timestamp"] = now
|
||||
|
||||
# Add parameter (sans any dot suffix) if has an alias
|
||||
if (a = parameters.getAlias(path))?
|
||||
newAliases ?= {}
|
||||
newAliases[path] = a
|
||||
|
||||
projection[path] = 1
|
||||
|
||||
# Store this param path and all its parents for later look up
|
||||
presentPaths[path] = true
|
||||
i = p[0].indexOf('.')
|
||||
while i != -1
|
||||
presentPaths[p[0][0...i]] = true
|
||||
i = p[0].indexOf('.', i + 1)
|
||||
|
||||
if updatesBatch.customCommands?
|
||||
for p in updatesBatch.customCommands
|
||||
commandName = p[0]
|
||||
update['$set']["_customCommands.#{commandName}._value"] = p[1]
|
||||
update['$set']["_customCommands.#{commandName}._timestamp"] = now
|
||||
|
||||
processBatches = (cb) ->
|
||||
if not commitQueue
|
||||
processBatch(deviceUpdates) if deviceUpdates?
|
||||
return cb()
|
||||
|
||||
db.redisClient.lpop("#{deviceId}_updates", (err, res) ->
|
||||
callback(err) if err
|
||||
|
||||
if not res?
|
||||
processBatch(deviceUpdates) if deviceUpdates?
|
||||
return cb()
|
||||
|
||||
processBatch(JSON.parse(res))
|
||||
return processBatches(cb)
|
||||
)
|
||||
|
||||
commit = () ->
|
||||
for k of update
|
||||
delete update[k] if Object.keys(update[k]).length == 0
|
||||
|
||||
if Object.keys(update).length
|
||||
db.devicesCollection.update({'_id' : deviceId}, update, {}, (err, count) ->
|
||||
return callback(err) if err
|
||||
|
||||
# Clear aliases cache if there's a new aliased parameter that has not been included in aliases cache
|
||||
if newAliases?
|
||||
newAliasFound = false
|
||||
db.getAliases((aliases) ->
|
||||
for p, a of newAliases
|
||||
if not aliases[a]? or p not in aliases[a]
|
||||
newAliasFound = true
|
||||
break
|
||||
|
||||
return callback() if not newAliasFound
|
||||
db.redisClient.del('aliases', (err, res) ->
|
||||
return callback(err)
|
||||
)
|
||||
)
|
||||
else
|
||||
return callback()
|
||||
)
|
||||
else
|
||||
return callback()
|
||||
|
||||
processBatches(() ->
|
||||
if projection?
|
||||
mongoQuery.optimizeProjection(projection)
|
||||
|
||||
# Fetch device from DB to discover and delete parameters that no longer exist
|
||||
# While we're fetching the object, we might as well use the information
|
||||
# to optimize our update query
|
||||
db.devicesCollection.findOne({'_id' : deviceId}, projection, (err, device) ->
|
||||
return callback(err) if err
|
||||
|
||||
recursive = (obj, prefix) ->
|
||||
for k, v of obj
|
||||
p = prefix + k
|
||||
|
||||
# Mark unset operations we want to keep
|
||||
if update['$unset'][p]?
|
||||
update['$unset'][p] = 2
|
||||
continue
|
||||
|
||||
if k[0] == '_'
|
||||
# Remove set operations for unmodified values for optimization
|
||||
if update['$set'][p] == v
|
||||
delete update['$set'][p]
|
||||
continue
|
||||
|
||||
if presentPaths[p]
|
||||
recursive(v, "#{p}.")
|
||||
else
|
||||
update['$unset'][p] = 2
|
||||
|
||||
# Scan the DB object and remove parameters that no longer exists in device
|
||||
for path of projection
|
||||
root = device
|
||||
rootPath = ''
|
||||
for p in path.split('.')
|
||||
rootPath += "#{p}."
|
||||
root = root[p]
|
||||
break if not root?
|
||||
|
||||
if root
|
||||
recursive(root, rootPath)
|
||||
else
|
||||
if not presentPaths[rootPath.slice(0, -1)]?
|
||||
update['$unset'][rootPath.slice(0, -1)] = 2
|
||||
|
||||
# Remove unnecessary unset updates
|
||||
for k, v of update['$unset']
|
||||
if common.startsWith(k, path + '.') and v != 2
|
||||
delete update['$unset'][k]
|
||||
|
||||
commit()
|
||||
)
|
||||
else
|
||||
commit()
|
||||
)
|
||||
|
||||
|
||||
exports.queueUpdates = queueUpdates
|
||||
exports.clearUpdatesQueue = clearUpdatesQueue
|
||||
exports.commitUpdates = commitUpdates
|
||||
Reference in new issue
Block a user