From 89e323ae7c6499b71d76a54f132104a2cd3f97b2 Mon Sep 17 00:00:00 2001 From: Zaid Abdulla Date: Sun, 6 Mar 2016 02:57:23 +0300 Subject: [PATCH] Stand-alone functions to fetch/save parameters --- lib/db.coffee | 268 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 1 deletion(-) diff --git a/lib/db.coffee b/lib/db.coffee index 94092c8..ab2ecc4 100644 --- a/lib/db.coffee +++ b/lib/db.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 @@ -19,6 +19,7 @@ config = require './config' mongodb = require 'mongodb' redis = require('redis') parameters = require './parameters' +common = require './common' redisClient = null tasksCollection = null @@ -223,8 +224,273 @@ getPresetsObjectsAliases = (callback) -> ) +# Optimize projection by removing overlaps +# This modifies given object and returns it +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 obj + + +fetchDevice = (id, timestamp, patterns, callback) -> + res = [] + + # Build projection + projection = {_id: 1} + for pattern in patterns + if pattern.length == 0 + projection['_timestamp'] = 1 + else if not pattern[0]? + projection[''] = 1 + else if pattern[0] == 'Events' + if pattern.length == 1 + res.push([['Events'], timestamp, 1, timestamp, 1, timestamp, 0]) + else if pattern.length == 2 + if not pattern[1]? + projection['_registered'] = 1 + projection['_lastInform'] = 1 + projection['_lastBootstrap'] = 1 + projection['_lastBoot'] = 1 + res.push([['Events', null], timestamp]) + else if pattern[1] == 'Registered' + projection['_registered'] = 1 + else if pattern[1] == 'Inform' + projection['_lastInform'] = 1 + else if pattern[1] == '0_BOOTSTRAP' + projection['_lastBootstrap'] = 1 + else if pattern[1] == '1_BOOT' + projection['_lastBoot'] = 1 + else if pattern[0] == 'DeviceID' + if pattern.length == 1 + res.push([['DeviceID'], timestamp, 1, timestamp, 1, timestamp, 0]) + else if pattern.length == 2 + if not pattern[1]? + projection['_id'] = 1 + projection['_deviceId._Manufacturer'] = 1 + projection['_deviceId._ProductClass'] = 1 + projection['_deviceId._SerialNumber'] = 1 + projection['_deviceId._OUI'] = 1 + res.push([['DeviceID', null], timestamp]) + else if pattern[1] == 'ID' + projection['_id'] = 1 + else if pattern[1] == 'Manufacturer' + projection['_deviceId._Manufacturer'] = 1 + else if pattern[1] == 'ProductClass' + projection['_deviceId._ProductClass'] = 1 + else if pattern[1] == 'OUI' + projection['_deviceId._OUI'] = 1 + else if pattern[1] == 'SerialNumber' + projection['_deviceId._SerialNumber'] = 1 + else if pattern[0] == 'Tags' + if pattern.length == 1 + res.push([['Tags'], timestamp, 1, timestamp, 1, timestamp, 0]) + else if pattern.length == 2 + res.push([['Tags', null], timestamp]) + projection['_tags'] = 1 + else + for i in [0...pattern.length] by 1 + break if not pattern[i] + + if i == pattern.length + s = pattern.join('.') + projection["#{s}._value"] = 1 + projection["#{s}._timestamp"] = 1 + projection["#{s}._type"] = 1 + projection["#{s}._writable"] = 1 + projection["#{s}._object"] = 1 + projection["#{s}._orig"] = 1 + + # Timestamp from parent is needed for writable timestamp + if pattern.length <= 1 + projection['_timestamp'] = 1 + else + projection["#{pattern.slice(0, -1).join('.')}._timestamp"] = 1 + else + s = pattern.slice(0, i).join('.') + projection[s] = 1 + + if projection[''] + projection = {} + + if projection? + optimizeProjection(projection) + + devicesCollection.findOne({'_id' : id}, projection, (err, device) -> + return callback(err) if err or not device? + + storeParams = (obj, path, timestamp, descendantsFetched) -> + if not descendantsFetched and not obj['_value']? and projection[path.join('.')] + descendantsFetched = true + + if obj['_timestamp']? + obj['_timestamp'] = +obj['_timestamp'] + + v = [timestamp, 1] + + if obj['_value']? + v[6] = obj['_timestamp'] ? timestamp + v[7] = [obj['_value'], obj['_type']] + obj['_object'] = false + + if obj['_writable']? + v[4] = timestamp + v[5] = if obj['_writable'] then 1 else 0 + + if obj['_object']? + v[2] = timestamp + v[3] = if obj['_object'] then 1 else 0 + + if v.length + v[0] ?= timestamp ? 0 + v[1] ?= 1 + res.push([path].concat(v)) + + for k, v of obj + if k[0] != '_' + obj['_object'] = true + storeParams(v, path.concat(k), obj['_timestamp'] ? 0, descendantsFetched) + + if obj['_object'] and descendantsFetched + res.push([path.concat(null), obj['_timestamp'] ? 0]) + + for k, v of device + if k == '_timestamp' + res.push([[null], v]) if not Object.keys(projection).length + else if k == '_lastInform' + res.push([['Events', 'Inform'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']]) + else if k == '_lastBoot' + res.push([['Events', '1_BOOT'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']]) + else if k == '_lastBootstrap' + res.push([['Events', '0_BOOTSTRAP'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']]) + else if k == '_registered' + res.push([['Events', 'Registered'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:dateTime']]) + else if k == '_id' + res.push([['DeviceID', 'ID'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [v, 'xsd:string']]) + else if k == '_tags' + for t in v + res.push([['Tags', t], timestamp, 1, timestamp, 0, timestamp, 1, timestamp, [true, 'xsd:boolean']]) + else if k == '_deviceId' + for kk, vv of v + if kk == '_Manufacturer' + res.push([['DeviceID', 'Manufacturer'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']]) + else if kk == '_OUI' + res.push([['DeviceID', 'OUI'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']]) + if kk == '_ProductClass' + res.push([['DeviceID', 'ProductClass'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']]) + if kk == '_SerialNumber' + res.push([['DeviceID', 'SerialNumber'], timestamp, 1, timestamp, 0, timestamp, 0, timestamp, [vv, 'xsd:string']]) + else if common.typeOf(v) is common.OBJECT_TYPE + storeParams(v, [k], 0, projection == null) + + return callback(null, res) + ) + + +saveDevice = (deviceId, diff, isNew, callback) -> + update = {'$set' : {}, '$unset' : {}, '$addToSet' : {}, '$pull' : {}} + for p in diff + base = p[2] + current = p[3] + if p[0][0] == 'Events' + if p[0].length == 2 + if p[0][1] == 'Inform' + update['$set']['_lastInform'] = new Date(current[7][0]) + else if p[0][1] == '0_BOOTSTRAP' + update['$set']['_lastBootstrap'] = new Date(current[7][0]) + else if p[0][1] == '1_BOOT' + update['$set']['_lastBoot'] = new Date(current[7][0]) + else if p[0][1] == 'Registered' + update['$set']['_registered'] = new Date(current[7][0]) + else if p[0][0] == 'Tags' + if p[0].length == 2 and p[0][1]? + if current[7][0] and not base[7]?[0] + update['$addToSet']['_tags'] ?= {'$each' : []} + update['$addToSet']['_tags']['$each'].push(p[0][1]) + else if not current[7]?[0] and base[7]?[0] + update['$pull']['_tags'] ?= {'$in' : []} + update['$pull']['_tags']['$in'].push(p[0][1]) + else if p[0][0] == 'DeviceID' + if p[0].length == 2 and current[7]? + if p[0][1] == 'ID' + update['$set']['_id'] = current[7][0] + else if p[0][1] == 'Manufacturer' + update['$set']['_deviceId._Manufacturer'] = current[7][0] + else if p[0][1] == 'OUI' + update['$set']['_deviceId._OUI'] = current[7][0] + else if p[0][1] == 'ProductClass' + update['$set']['_deviceId._ProductClass'] = current[7][0] + else if p[0][1] == 'SerialNumber' + update['$set']['_deviceId._SerialNumber'] = current[7][0] + else if p[1] == p[0].length + param = p[0].join('.') + if current[2]? + if current[3] != base[3] + if current[3] + update['$set']["#{param}._object"] = true + else if base[3]? + update['$unset']["#{param}._object"] = 1 + else if base[2]? + update['$unset']["#{param}._object"] = 1 + + if current[4]? + if current[5] != base[5] + update['$set']["#{param}._writable"] = !!current[5] + else if base[4]? + update['$unset']["#{param}._writable"] = 1 + + if current[6]? and current[7]? + if current[6] != base[6] + update['$set']["#{param}._timestamp"] = new Date(current[6]) + + if current[7][0] != base[7]?[0] or current[7][1] != base[7]?[1] + if current[7][1] == 'xsd:dateTime' + update['$set']["#{param}._value"] = new Date(current[7][0]) + else + update['$set']["#{param}._value"] = current[7][0] + update['$set']["#{param}._type"] = current[7][1] + else if base[7]? + update['$unset']["#{param}._value"] = 1 + update['$unset']["#{param}._type"] = 1 + else if p[1] == p[0].length - 1 and not p[0][p[1]]? + param = p[0].slice(0, p[1]).concat('_timestamp').join('.') + if current? + update['$set'][param] = new Date(current) + else if base? + update['$unset'][param] = 1 + + # Remove empty keys + for k of update + if k == '$addToSet' + for kk of update[k] + delete update[k][kk] if update[k][kk]['$each'].length == 0 + else if k == '$pull' + for kk of update[k] + delete update[k][kk] if update[k][kk]['$in'].length == 0 + delete update[k] if Object.keys(update[k]).length == 0 + + return callback() if Object.keys(update).length == 0 + + devicesCollection.update({'_id' : deviceId}, update, {upsert: isNew}, (err, count) => + if not err and count != 1 + return callback(new Error("Device #{deviceId} not found in database")) + + return callback(err) + ) + + exports.getTask = getTask exports.getPresetsObjectsAliases = getPresetsObjectsAliases exports.getAliases = getAliases exports.connect = connect exports.disconnect = disconnect +exports.fetchDevice = fetchDevice +exports.saveDevice = saveDevice