Device data model functions
A set of functions for efficiently querying and manipulating a data structure representing a device. The data structure also holds declarations which are used to indicate what needs to be synced from and to the device.
This commit is contained in:
1 parent
53baa8dc78
commit
2012004299
2 files changed
+411
No files matched your search
@@ -1,4 +1,22 @@
|
||||
###
|
||||
# Copyright 2013-2016 Zaid Abdulla
|
||||
#
|
||||
# GenieACS is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# GenieACS is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# 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
|
||||
@@ -122,6 +140,28 @@ exports.matchType = (src, dst) ->
|
||||
dst
|
||||
|
||||
|
||||
pathOverlap = (a, b, start) ->
|
||||
if a.length == b.length
|
||||
res = 3
|
||||
else if a.length > b.length
|
||||
res = 2
|
||||
else
|
||||
res = 1
|
||||
|
||||
for i in [(start ? 0)...Math.min(a.length, b.length)] by 1
|
||||
if a[i]?
|
||||
if not b[i]?
|
||||
res &= 2
|
||||
else if a[i] != b[i]
|
||||
return 0
|
||||
else if b[i]?
|
||||
res &= 1
|
||||
|
||||
return 0 if not res
|
||||
|
||||
return res
|
||||
|
||||
|
||||
exports.UNDEFINED_TYPE = UNDEFINED_TYPE
|
||||
exports.NULL_TYPE = NULL_TYPE
|
||||
exports.NUMBER_TYPE = NUMBER_TYPE
|
||||
@@ -131,3 +171,4 @@ exports.OBJECT_TYPE = OBJECT_TYPE
|
||||
exports.REGEXP_TYPE = REGEXP_TYPE
|
||||
|
||||
exports.typeOf = typeOf
|
||||
exports.pathOverlap = pathOverlap
|
||||
@@ -0,0 +1,370 @@
|
||||
###
|
||||
# Copyright 2013-2016 Zaid Abdulla
|
||||
#
|
||||
# GenieACS is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# GenieACS is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
###
|
||||
|
||||
common = require './common'
|
||||
|
||||
|
||||
sanitizeDeclaration = (declaration, wildcard, now) ->
|
||||
if typeof declaration is 'number'
|
||||
declaration = [declaration]
|
||||
|
||||
if declaration[7]?
|
||||
declaration[7] = sanitizeParameterValue(declaration[7])
|
||||
|
||||
for i in [0...declaration.length] by 2 when declaration[i]?
|
||||
if declaration[i] <= 0
|
||||
declaration[i] = now + declaration[i]
|
||||
else
|
||||
declaration[i] = Math.min(now, declaration[i])
|
||||
|
||||
return declaration
|
||||
|
||||
|
||||
sanitizeProperties = (properties, wildcard) ->
|
||||
if not wildcard
|
||||
if typeof properties is 'number'
|
||||
properties = [properties]
|
||||
else if properties[1]
|
||||
properties[1] = 1
|
||||
|
||||
for i in [2...properties.length] by 2 \
|
||||
when properties[i]? and properties[i + 1]?
|
||||
properties[0] = Math.max(properties[0] ? 0, properties[i])
|
||||
|
||||
if properties[6]? and properties[7]? and not (properties[6] < properties[2])
|
||||
properties[2] = properties[6]
|
||||
properties[3] = 0
|
||||
else if properties[2]? and properties[3] and not (properties[2] < properties[6])
|
||||
properties[6] = properties[2]
|
||||
properties[7] = null
|
||||
|
||||
else if typeof properties isnt 'number'
|
||||
properties = properties[0]
|
||||
|
||||
return properties
|
||||
|
||||
|
||||
sanitizeParameterValue = (parameterValue) ->
|
||||
if parameterValue[0]?
|
||||
switch parameterValue[1]
|
||||
when 'xsd:boolean'
|
||||
if typeof parameterValue[0] isnt 'boolean'
|
||||
parameterValue = parameterValue.slice()
|
||||
parameterValue[0] = !!JSON.parse(parameterValue[0])
|
||||
when 'xsd:string'
|
||||
if typeof parameterValue[0] isnt 'string'
|
||||
parameterValue = parameterValue.slice()
|
||||
parameterValue[0] = '' + parameterValue[0]
|
||||
when 'xsd:int', 'xsd:unsignedInt'
|
||||
if typeof parameterValue[0] isnt 'number'
|
||||
parameterValue = parameterValue.slice()
|
||||
parameterValue[0] = +parameterValue[0]
|
||||
when 'xsd:dateTime'
|
||||
if typeof parameterValue[0] isnt 'number'
|
||||
parameterValue = parameterValue.slice()
|
||||
if parameterValue[0].getTime?
|
||||
parameterValue[0] = parameterValue[0].getTime()
|
||||
else if isNaN(parameterValue[0])
|
||||
parameterValue[0] = Date.parse(parameterValue[0])
|
||||
else
|
||||
parameterValue[0] = +parameterValue[0]
|
||||
else
|
||||
if parameterValue[1]?
|
||||
throw new Error("Parameter value type \"#{parameterValue[1]}\" not recognized.")
|
||||
|
||||
# Ensure consistency after serialization
|
||||
parameterValue[0] = JSON.parse(JSON.stringify(parameter[0]))
|
||||
|
||||
return parameterValue
|
||||
|
||||
|
||||
init = () ->
|
||||
return [[[null, [0, 1]]]]
|
||||
|
||||
|
||||
declare = (deviceData, path, declaration, now) ->
|
||||
wildcard = false
|
||||
for i in [0...path.length] when not path[i]?
|
||||
wildcard = true
|
||||
break
|
||||
|
||||
declaration = sanitizeDeclaration(declaration, wildcard, now)
|
||||
|
||||
ref = deviceData
|
||||
depth = 0
|
||||
for i in [0...path.length] by 1
|
||||
if not (p = path[i])?
|
||||
++ depth
|
||||
continue
|
||||
|
||||
ref[1 + depth] ?= {}
|
||||
ref[1 + depth][p] ?= []
|
||||
ref = ref[1 + depth][p]
|
||||
depth = 0
|
||||
|
||||
ref[0] ?= []
|
||||
ref[0][depth] ?= []
|
||||
|
||||
if wildcard
|
||||
ref[0][depth][0] = Math.max(ref[0][depth][0] ? 0, declaration[0])
|
||||
else if not ref[0][depth][0]?
|
||||
ref[0][depth][0] = declaration
|
||||
else
|
||||
for i in [0...declaration.length] by 2
|
||||
if declaration[i]?
|
||||
ref[0][depth][0][i] = Math.max(ref[0][depth][0][i] ? 0, declaration[i])
|
||||
|
||||
if declaration[i + 1]?
|
||||
ref[0][depth][0][i + 1] = declaration[i + 1]
|
||||
|
||||
return
|
||||
|
||||
|
||||
set = (deviceData, path, revision, properties) ->
|
||||
++ revision # revisions start at layer 1
|
||||
wildcard = false
|
||||
for i in [0...path.length] when not path[i]?
|
||||
wildcard = true
|
||||
break
|
||||
|
||||
properties = sanitizeProperties(properties, wildcard)
|
||||
|
||||
ref = deviceData
|
||||
depth = 0
|
||||
for i in [0...path.length] by 1
|
||||
if not (p = path[i])?
|
||||
++ depth
|
||||
continue
|
||||
|
||||
if not wildcard and properties[1]
|
||||
# Update existing timestamp for ancestors
|
||||
ref[0] ?= []
|
||||
ref[0][0] ?= []
|
||||
ref[0][0][revision] ?= []
|
||||
if not (ref[0][0][revision][0] >= properties[0])
|
||||
ref[0][0][revision][0] = properties[0]
|
||||
ref[0][0][revision][1] = 1
|
||||
|
||||
if not (ref[0][0][revision][2] >= properties[0])
|
||||
ref[0][0][revision][2] = properties[0]
|
||||
ref[0][0][revision][3] = 1
|
||||
|
||||
ref[1 + depth] ?= {}
|
||||
ref[1 + depth][p] ?= []
|
||||
ref = ref[1 + depth][p]
|
||||
depth = 0
|
||||
|
||||
ref[0] ?= []
|
||||
ref[0][depth] ?= []
|
||||
|
||||
if wildcard
|
||||
ref[0][depth][revision] = Math.max(ref[0][depth][revision] ? 0, properties)
|
||||
else if not ref[0][0][revision]?
|
||||
ref[0][0][revision] = properties
|
||||
else
|
||||
for i in [0...properties.length] by 2 \
|
||||
when properties[i]?# and properties[i + 1]?
|
||||
if not (properties[i] < ref[0][0][revision][i])
|
||||
ref[0][0][revision][i] = properties[i]
|
||||
ref[0][0][revision][i + 1] = properties[i + 1]
|
||||
|
||||
if properties[7]?
|
||||
ref[0][0][revision][7] = sanitizeParameterValue(ref[0][0][revision][7])
|
||||
|
||||
return
|
||||
|
||||
|
||||
traverse = (deviceData, pattern, revision, callback) ->
|
||||
if not pattern?
|
||||
pattern = []
|
||||
pattern.length = 99
|
||||
|
||||
if not revision?
|
||||
revision = 99
|
||||
|
||||
++ revision # First layer is for declarations
|
||||
|
||||
collectTimestamps = (ref, path, root, timestamps, wildcard) ->
|
||||
# Skip properties of parameter we're currently at
|
||||
skip = if root == path.length then 1 else 0
|
||||
|
||||
if ref[0]?
|
||||
for i in [skip...ref[0].length] by 1
|
||||
if ref[0][i]? and i <= pattern.length - path.length
|
||||
p = path.slice()
|
||||
p.length += i
|
||||
if wildcard or i > 0
|
||||
revisions = ref[0][i]
|
||||
else
|
||||
# Extract only timestamp from properties array
|
||||
revisions = (j?[0] for j in ref[0][i])
|
||||
|
||||
current = -1
|
||||
for j in [Math.min(revision, revisions.length - 1)..1] by -1 when revisions[j]?
|
||||
current = revisions[j]; break
|
||||
|
||||
timestamps.push([p, root, revisions, current, current])
|
||||
|
||||
for i in [skip...ref.length - 1] by 1
|
||||
if ref[1 + i]? and i < pattern.length - path.length
|
||||
p = path.slice()
|
||||
p.length += i
|
||||
for k, v of ref[1+i]
|
||||
if not pattern[path.length + i]? or pattern[path.length + i] == k
|
||||
collectTimestamps(v, p.concat(k), root, timestamps, wildcard or i > 0)
|
||||
|
||||
return
|
||||
|
||||
processOverlaps = (timestamps, root, skip) ->
|
||||
skip = 1 if not skip
|
||||
for i in [skip...timestamps.length] by 1
|
||||
for j in [0...i] by 1
|
||||
if timestamps[i][0].length > timestamps[j][0].length
|
||||
t = timestamps[j]
|
||||
tt = timestamps[i]
|
||||
else
|
||||
t = timestamps[i]
|
||||
tt = timestamps[j]
|
||||
|
||||
overlap = common.pathOverlap(t[0], tt[0], root)
|
||||
|
||||
if overlap & 1
|
||||
if t[0].length == tt[0].length
|
||||
tt[3 + (root - tt[1]) * 2] = Math.max(tt[3 + (root - tt[1]) * 2], t[3 + (root - t[1]) * 2])
|
||||
else
|
||||
tt[4 + (root - tt[1]) * 2] = Math.max(tt[4 + (root - tt[1]) * 2], t[4 + (root - t[1]) * 2])
|
||||
|
||||
if overlap & 2
|
||||
t[3 + (root - t[1]) * 2] = Math.max(t[3 + (root - t[1]) * 2], tt[3 + (root - t[1]) * 2])
|
||||
|
||||
return
|
||||
|
||||
recursive = (ref, path, timestamps, timestampThreshold, revisionThreshold) ->
|
||||
declaration = ref[0]?[0]?[0]?.slice() or []
|
||||
base = ref[0]?[0]?[1]?.slice() or []
|
||||
current = []
|
||||
|
||||
# Find timestamp and revision in which this parameter was most recently
|
||||
# created in order to ignore older properties
|
||||
for t in timestamps
|
||||
for j in [Math.min(revision, t[2].length - 1)..1] by -1 when t[2][j]?
|
||||
if not (t[2][j] <= ref[0]?[0]?[j]?[0]) and t[2][j] >= timestampThreshold
|
||||
timestampThreshold = Math.max(timestampThreshold, t[2][j])
|
||||
revisionThreshold = Math.max(revisionThreshold, j)
|
||||
current[0] = timestampThreshold
|
||||
break
|
||||
|
||||
# Collect current properties
|
||||
if ref[0]?[0]?
|
||||
for i in [revisionThreshold...Math.min(revision, ref[0][0].length)] by 1 when (rev = ref[0][0][i])?
|
||||
for j in [0...rev.length] by 2 when rev[j]?
|
||||
if rev[j] >= timestampThreshold
|
||||
current[j] = rev[j]
|
||||
current[j + 1] = rev[j + 1]
|
||||
else
|
||||
current[j] ?= 0
|
||||
|
||||
# Return if parameter no longer exists
|
||||
if not (declaration[1] or base[1] or current[1])
|
||||
l = timestamps.length
|
||||
collectTimestamps(ref, path, path.length - 1, timestamps)
|
||||
processOverlaps(timestamps, path.length - 1, l)
|
||||
return
|
||||
|
||||
tentativeTimestamp = -1
|
||||
if current[2]? and not current[3]
|
||||
tentativeTimestamp = current[2]
|
||||
|
||||
filteredTimestamps = timestamps.filter((t) ->
|
||||
if t[0][path.length - 1]?
|
||||
if t[0][path.length - 1]? and t[0][path.length - 1] == path[path.length - 1]
|
||||
t[2 + (path.length - t[1]) * 2] = Math.max(t[2 + (path.length - t[1]) * 2], current[0])
|
||||
else
|
||||
return false
|
||||
|
||||
if t[0].length > path.length
|
||||
t[3 + (path.length - t[1]) * 2] = t[1 + (path.length - t[1]) * 2]
|
||||
t[4 + (path.length - t[1]) * 2] = tentativeTimestamp
|
||||
return true
|
||||
|
||||
if not declaration[0] or t[2][0] > declaration[0]
|
||||
declaration[0] = t[2][0]
|
||||
|
||||
return false
|
||||
)
|
||||
|
||||
collectTimestamps(ref, path, path.length, filteredTimestamps)
|
||||
|
||||
processOverlaps(filteredTimestamps, path.length, 0)
|
||||
|
||||
if path.length < pattern.length
|
||||
children = {}
|
||||
for k, v of ref[1]
|
||||
if not pattern[path.length]? or k == pattern[path.length]
|
||||
r = recursive(v, path.concat(k), filteredTimestamps, timestampThreshold, revisionThreshold)
|
||||
children[k] = r if r?
|
||||
|
||||
descendantTimestamps = filteredTimestamps.map((t) ->
|
||||
res = [t[0], t[1]]
|
||||
res[2] = t[2][0] if t[2][0]?
|
||||
res[3] = t[2][1] if t[2][1]?
|
||||
|
||||
cur = Math.max(t[3 + (path.length - t[1]) * 2], t[4 + (path.length - t[1]) * 2])
|
||||
|
||||
if path.length > t[1]
|
||||
t[2 + (path.length - t[1]) * 2] = Math.min(t[2 + (path.length - t[1]) * 2], cur)
|
||||
|
||||
if cur >= 0
|
||||
res[4] = cur
|
||||
|
||||
return res
|
||||
).filter((t) -> t.length > 1)
|
||||
|
||||
# Match value type of current if no type is specified
|
||||
if declaration[7]? and not declaration[7][1]?
|
||||
declaration[7] = sanitizeParameterValue([declaration[7][0], current[7]?[1]])
|
||||
|
||||
return callback(path, declaration, base, current, descendantTimestamps, children)
|
||||
|
||||
return recursive(deviceData, [], [], 0, 1)
|
||||
|
||||
|
||||
getPrerequisiteDeclarations = (declarations) ->
|
||||
dec = [declarations[0]]
|
||||
for i in [1...declarations.length] by 2
|
||||
if declarations[i]?
|
||||
dec[i] = declarations[i]
|
||||
else if declarations[i + 1]?
|
||||
dec[i] = 0
|
||||
return [dec]
|
||||
|
||||
|
||||
getAll = (deviceData, pattern, revision) ->
|
||||
allParameters = []
|
||||
traverse(deviceData, pattern, revision, (path, declaration, base, current, descendantTimestamps, children) ->
|
||||
if path.length == pattern.length and current[1]
|
||||
allParameters.push([path].concat(current))
|
||||
)
|
||||
return allParameters
|
||||
|
||||
|
||||
exports.getPrerequisiteDeclarations = getPrerequisiteDeclarations
|
||||
exports.init = init
|
||||
exports.getAll = getAll
|
||||
exports.declare = declare
|
||||
exports.set = set
|
||||
exports.traverse = traverse
|
||||
Reference in new issue
Block a user