Completed
Push — master ( 415375...56f28d )
by Barry
29s
created

fullsignalk.js ➔ fillIdentity   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 0
loc 14
rs 9.2
c 0
b 0
f 0
1
/*
2
 * Copyright 2016, Teppo Kurki
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 *
16
 */
17
18
var _ = require('lodash')
19
var signalkSchema = require('./') // TODO should not be needed
20
var getId
21
var debug = require('debug')('signalk:fullsignalk')
22
23
function FullSignalK (id, type, defaults) {
24
  // hack, apparently not available initially, so need to set lazily
25
  getId = signalkSchema.getSourceId
26
27
  if (!type || ((type !== 'aton') && (type !== 'aircraft') && (type !== 'sar'))) { type = 'vessels' } // vessels is the default if the passed value is anything invalid
28
29
  this.root = {}
30
  this.root.version = '1.0.2'  // Should we read this from the package.json file?
31
  this.root.self = ''
32
  this.root[type] = {}
33
34
  if (id) {
35
    this.root[type][id] = defaults && defaults.vessels && defaults.vessels.self ? defaults.vessels.self : {}
36
    this.self = this.root[type][id]
37
    fillIdentity(this.root)
38
    this.root.self = type + '.' + id
39
  }
40
41
  this.sources = {}
42
  this.root.sources = this.sources
43
  this.lastModifieds = {}
44
}
45
46
function fillIdentity (full) {
47
  // looks at all members of vessels, aton, aircraft and sar, takes their property name and
48
  // ensures there is one of mmsi, uuid or url within that branch as appropriate
49
  const groups = ['vessels', 'aton', 'aircraft', 'sar']
50
  for (var g = 0; g < groups.length; g++) {
51
    if (full.hasOwnProperty(groups[g])) {
52
      for (var identity in full[groups[g]]) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
53
        fillIdentityField(full[groups[g]][identity], identity)
54
        // fill arbitrarily the last id as self, used in tests
55
        full.self = identity // TODO this shouldn't be here it would unexpectedly change the self
56
      }
57
    }
58
  }
59
}
60
61
function fillIdentityField (vesselData, identity) {
62
   // accepts a vessel/aton/sar/aircraft branch and the id of that branch and makes sure there is one of
63
   // mmsi, uuid or url within it
64
   // eg. fillIdentityField({}, 'urn:mrn:imo:mmsi:230099999') returns {"mmsi": "230099999"}
65
  const mmsiPrefix = 'urn:mrn:imo:mmsi:'
66
  if (identity.indexOf(mmsiPrefix) === 0) {
67
    vesselData.mmsi = identity.substring(mmsiPrefix.length, identity.length)
68
  } else if (identity.indexOf('urn:mrn:signalk') === 0) {
69
    vesselData.uuid = identity
70
  } else {
71
    vesselData.url = identity
72
  }
73
}
74
75
require('util').inherits(FullSignalK, require('events').EventEmitter)
76
77
FullSignalK.prototype.retrieve = function () {
78
  return this.root
79
}
80
81
FullSignalK.prototype.addDelta = function (delta) {
82
  this.emit('delta', delta)
83
  var context = findContext(this.root, delta.context)
84
  this.addUpdates(context, delta.context, delta.updates)
85
  this.updateLastModified(delta.context)
86
}
87
88
FullSignalK.prototype.updateLastModified = function (contextKey) {
89
  this.lastModifieds[contextKey] = new Date().getTime()
90
}
91
92
FullSignalK.prototype.pruneContexts = function (seconds) {
93
  var threshold = new Date().getTime() - seconds * 1000
94
  for (let contextKey in this.lastModifieds) {
95
    if (this.lastModifieds[contextKey] < threshold) {
96
      this.deleteContext(contextKey)
97
      delete this.lastModifieds[contextKey]
98
    }
99
  }
100
}
101
102
FullSignalK.prototype.deleteContext = function (contextKey) {
103
  debug('Deleting context ' + contextKey)
104
  var pathParts = contextKey.split('.')
105
  if (pathParts.length === 2) {
106
    delete this.root[pathParts[0]][pathParts[1]]
107
  }
108
}
109
110
function findContext (root, contextPath) {
111
  var context = _.get(root, contextPath)
112
  if (!context) {
113
    context = {}
114
    _.set(root, contextPath, context)
115
  }
116
  var identity = contextPath.split('.')[1]
117
  if (!identity) {
118
    return undefined
119
  }
120
  fillIdentityField(context, identity)
121
  return context
122
}
123
124
FullSignalK.prototype.addUpdates = function (context, contextPath, updates) {
125
  var len = updates.length
126
  for (var i = 0; i < len; ++i) {
127
    this.addUpdate(context, contextPath, updates[i])
128
  }
129
}
130
131
FullSignalK.prototype.addUpdate = function (context, contextPath, update) {
132
  if (typeof update.source !== 'undefined') {
133
    this.updateSource(context, update.source, update.timestamp)
134
  } else if (typeof update['$source'] !== 'undefined') {
135
    this.updateDollarSource(context, update['$source'], update.timestamp)
136
  } else {
137
    console.error('No source in delta update:' + JSON.stringify(update))
138
  }
139
  addValues(context, contextPath, update.source || update['$source'], update.timestamp, update.values)
140
}
141
142
FullSignalK.prototype.updateDollarSource = function (context, dollarSource, timestamp) {
0 ignored issues
show
Unused Code introduced by
The parameter timestamp is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
143
  const parts = dollarSource.split('.')
144
  parts.reduce((cursor, part) => {
145
    if (typeof cursor[part] === 'undefined') {
146
      cursor[part] = {}
147
    }
148
    return cursor[part]
149
  }, this.sources)
150
}
151
152
FullSignalK.prototype.updateSource = function (context, source, timestamp) {
153
  if (!this.sources[source.label]) {
154
    this.sources[source.label] = {}
155
    this.sources[source.label].label = source.label
156
    this.sources[source.label].type = source.type
157
  }
158
159
  if (source.type === 'NMEA2000' || source.src) {
160
    handleNmea2000Source(this.sources[source.label], source, timestamp)
161
    return
162
  }
163
164
  if (source.type === 'NMEA0183' || source.sentence) {
165
    handleNmea0183Source(this.sources[source.label], source, timestamp)
166
    return
167
  }
168
169
  handleOtherSource(this.sources[source.label], source, timestamp)
170
}
171
172
function handleNmea2000Source (labelSource, source, timestamp) {
173
  if (!labelSource[source.src]) {
174
    labelSource[source.src] = {
175
      n2k: {
176
        src: source.src,
177
        pgns: {}
178
      }
179
    }
180
  }
181
  if (source.instance && !labelSource[source.src][source.instance]) {
182
    labelSource[source.src][source.instance] = {}
183
  }
184
  labelSource[source.src].n2k.pgns[source.pgn] = timestamp
185
}
186
187
function handleNmea0183Source (labelSource, source, timestamp) {
188
  var talker = source.talker || 'II'
189
  if (!labelSource[talker]) {
190
    labelSource[talker] = {
191
      talker: talker,
192
      sentences: {}
193
    }
194
  }
195
  labelSource[talker].sentences[source.sentence] = timestamp
196
}
197
198
function handleOtherSource (sourceLeaf, source, timestamp) {
199
  sourceLeaf.timestamp = timestamp
200
}
201
202
function addValues (context, contextPath, source, timestamp, pathValues) {
203
  var len = pathValues.length
204
  for (var i = 0; i < len; ++i) {
205
    addValue(context, contextPath, source, timestamp, pathValues[i])
206
  }
207
}
208
209
function addValue (context, contextPath, source, timestamp, pathValue) {
210
  if (_.isUndefined(pathValue.path) || _.isUndefined(pathValue.value)) {
211
    console.error('Illegal value in delta:' + JSON.stringify(pathValue))
212
    return
213
  }
214
  var valueLeaf
215
  if (pathValue.path.length === 0) {
216
    _.merge(context, pathValue.value)
217
    return
218
  } else {
219
    const splitPath = pathValue.path.split('.')
220
    valueLeaf = splitPath.reduce(function (previous, pathPart, i) {
221
      if (!previous[pathPart]) {
222
        previous[pathPart] = {}
223
        let meta = signalkSchema.getMetadata(contextPath + '.' + pathValue.path)
224
        if (meta && i === splitPath.length - 1) {
225
          // ignore properties from keyswithmetadata.json
226
          meta = JSON.parse(JSON.stringify(meta))
227
          delete meta.properties
228
229
          previous[pathPart].meta = meta
230
        }
231
      }
232
      return previous[pathPart]
233
    }, context)
234
  }
235
236
  var sourceId
237
  if (valueLeaf.values) { // multiple values already
238
    sourceId = getId(source)
239
    if (!valueLeaf.values[sourceId]) {
240
      valueLeaf.values[sourceId] = {}
241
    }
242
    assignValueToLeaf(pathValue.value, valueLeaf.values[sourceId])
243
    valueLeaf.values[sourceId].timestamp = timestamp
244
    setMessage(valueLeaf.values[sourceId], source)
245
  } else if (typeof valueLeaf.value !== 'undefined' && valueLeaf['$source'] !== getId(source)) {
246
    // first multiple value
247
248
    sourceId = valueLeaf['$source']
249
    var tmp = {}
250
    copyLeafValueToLeaf(valueLeaf, tmp)
251
    valueLeaf.values = {}
252
    valueLeaf.values[sourceId] = tmp
253
    valueLeaf.values[sourceId].timestamp = valueLeaf.timestamp
254
255
    sourceId = getId(source)
256
    valueLeaf.values[sourceId] = {}
257
    assignValueToLeaf(pathValue.value, valueLeaf.values[sourceId])
258
    valueLeaf.values[sourceId].timestamp = timestamp
259
    setMessage(valueLeaf.values[sourceId], source)
260
  }
261
  assignValueToLeaf(pathValue.value, valueLeaf)
262
  if (pathValue.path.length !== 0) {
263
    valueLeaf['$source'] = getId(source)
264
    valueLeaf.timestamp = timestamp
265
    setMessage(valueLeaf, source)
266
  }
267
}
268
269
function copyLeafValueToLeaf (fromLeaf, toLeaf) {
270
  _.assign(toLeaf, _.omit(fromLeaf, ['$source', 'timestamp', 'meta']))
271
}
272
273
function assignValueToLeaf (value, leaf) {
274
  leaf.value = value
275
}
276
277
function setMessage (leaf, source) {
278
  if (!source) {
279
    return
280
  }
281
  if (source.pgn) {
282
    leaf.pgn = source.pgn
283
    delete leaf.sentence
284
  }
285
  if (source.sentence) {
286
    leaf.sentence = source.sentence
287
    delete leaf.pgn
288
  }
289
}
290
291
module.exports = FullSignalK
292