diff --git a/lib/config.coffee b/lib/config.coffee
index d45a39a..5ba2b69 100644
--- a/lib/config.coffee
+++ b/lib/config.coffee
@@ -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
diff --git a/lib/custom-commands.coffee b/lib/custom-commands.coffee
deleted file mode 100644
index 711dea7..0000000
--- a/lib/custom-commands.coffee
+++ /dev/null
@@ -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 .
-###
-
-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
diff --git a/lib/db.coffee b/lib/db.coffee
index 1b4b239..1735783 100644
--- a/lib/db.coffee
+++ b/lib/db.coffee
@@ -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,15 +73,9 @@ connect = (callback) ->
exports.redisClient = redisClient = redis.createClient(config.get('REDIS_PORT'), config.get('REDIS_HOST'))
redisClient.select(config.get('REDIS_DB'), (err) ->
- if err
+ if --callbackCounter == 0 or err
callbackCounter = 0
return callback(err)
-
- flushRedis((err) ->
- if --callbackCounter == 0 or err
- callbackCounter = 0
- return callback(err)
- )
)
)
diff --git a/lib/mongo-query.coffee b/lib/mongo-query.coffee
deleted file mode 100644
index 71d91f1..0000000
--- a/lib/mongo-query.coffee
+++ /dev/null
@@ -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 .
-#
-# 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
diff --git a/lib/nbi.coffee b/lib/nbi.coffee
index 4aa1db0..a0eac4c 100644
--- a/lib/nbi.coffee
+++ b/lib/nbi.coffee
@@ -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]))
diff --git a/lib/presets.coffee b/lib/presets.coffee
deleted file mode 100644
index 8e3293e..0000000
--- a/lib/presets.coffee
+++ /dev/null
@@ -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 .
-#
-# 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
diff --git a/lib/tasks.coffee b/lib/tasks.coffee
deleted file mode 100644
index 568563a..0000000
--- a/lib/tasks.coffee
+++ /dev/null
@@ -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 .
-#
-# 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)
diff --git a/lib/update-device.coffee b/lib/update-device.coffee
deleted file mode 100644
index 3f5ed41..0000000
--- a/lib/update-device.coffee
+++ /dev/null
@@ -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 .
-###
-
-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