Refactor code to use Path class
This commit is contained in:
1 parent
006f83a478
commit
f878d828eb
13 files changed
+663
-784
No files matched your search
-134
@@ -17,8 +17,6 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ParamPath } from "./types";
|
||||
|
||||
export const UNDEFINED_TYPE = "[object Undefined]";
|
||||
export const NULL_TYPE = "[object Null]";
|
||||
export const BOOLEAN_TYPE = "[object Boolean]";
|
||||
@@ -55,138 +53,6 @@ export function generateDeviceId(deviceIdStruct): string {
|
||||
return esc(deviceIdStruct["OUI"]) + "-" + esc(deviceIdStruct["SerialNumber"]);
|
||||
}
|
||||
|
||||
export function parseAlias(pattern, start, res): number {
|
||||
const aliases = [];
|
||||
let i = start;
|
||||
while (i < pattern.length && pattern[i] !== "]") {
|
||||
const alias = [];
|
||||
/* eslint-disable-next-line @typescript-eslint/no-use-before-define */
|
||||
let j = (i = parsePath(pattern, i, alias) + 1);
|
||||
while (pattern[j] !== "]" && pattern[j] !== ",") {
|
||||
if (pattern[j] === '"' && i === j) {
|
||||
++j;
|
||||
while (pattern[j] !== '"' || pattern[j - 1] === "\\") {
|
||||
if (++j >= pattern.length)
|
||||
throw new Error("Invalid alias expression");
|
||||
}
|
||||
}
|
||||
if (++j >= pattern.length) throw new Error("Invalid alias expression");
|
||||
}
|
||||
|
||||
let value = pattern.slice(i, j).trim();
|
||||
i = j;
|
||||
if (value[0] === '"') {
|
||||
try {
|
||||
value = JSON.parse(value);
|
||||
} catch (error) {
|
||||
throw new Error("Invalid alias expression");
|
||||
}
|
||||
}
|
||||
|
||||
alias.push(value);
|
||||
aliases.push(alias);
|
||||
if (pattern[i] === ",") ++i;
|
||||
}
|
||||
|
||||
// Sort to ensure identical expressions have idential string representation
|
||||
function srt(a, b): number {
|
||||
const jMax = Math.min(a.length, b.length);
|
||||
for (let j = 0; j < jMax; j += 2) {
|
||||
const kMax = Math.min(a[j].length, b[j].length);
|
||||
for (let k = 0; k < kMax; ++k) {
|
||||
if (Array.isArray(a[j][k])) {
|
||||
if (Array.isArray(b[j][k])) return srt(a[j][k], b[j][k]);
|
||||
else if (b[j][k] == null) return -1;
|
||||
else return 1;
|
||||
} else if (a[j][k] == null) {
|
||||
if (b[j][k] == null) return 0;
|
||||
else return 1;
|
||||
} else if (b[j][k] == null || Array.isArray(b[j][k])) {
|
||||
return -1;
|
||||
} else if (a[j][k] > b[j][k]) {
|
||||
return 1;
|
||||
} else if (a[j][k] < b[j][k]) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (a[j].length > b[j].length) return -1;
|
||||
else if (a[j].length < b[j].length) return 1;
|
||||
|
||||
if (a[j + 1] > b[j + 1]) return -1;
|
||||
else if (a[j + 1] < b[j + 1]) return 1;
|
||||
}
|
||||
|
||||
if (a.length > b.length) return -1;
|
||||
else if (a.length < b.length) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
aliases.sort(srt);
|
||||
res.push([].concat.apply([], aliases));
|
||||
return i;
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
export function parsePath(pattern, start, res): number;
|
||||
export function parsePath(pattern, start?): ParamPath;
|
||||
export function parsePath(pattern, start?, res?): number | ParamPath {
|
||||
const path = [] as ParamPath;
|
||||
path.wildcard = 0;
|
||||
path.alias = 0;
|
||||
let i = start || 0;
|
||||
|
||||
// Colon separator is needed for parseAlias
|
||||
if (i < pattern.length && pattern[i] !== ":") {
|
||||
for (;;) {
|
||||
if (pattern[i] === "[") {
|
||||
path.alias |= 1 << path.length;
|
||||
i = parseAlias(pattern, i + 1, path) + 1;
|
||||
} else {
|
||||
const j = i;
|
||||
while (i < pattern.length && pattern[i] !== ":" && pattern[i] !== ".")
|
||||
++i;
|
||||
|
||||
const n = pattern.slice(j, i);
|
||||
if (n === "*") path.wildcard |= 1 << path.length;
|
||||
path.push(n);
|
||||
}
|
||||
|
||||
if (i >= pattern.length || pattern[i] === ":") break;
|
||||
else if (pattern[i] !== ".") throw new Error("Invalid alias expression");
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
Object.freeze(path);
|
||||
|
||||
if (res == null) return path;
|
||||
|
||||
res.push(path);
|
||||
return i;
|
||||
}
|
||||
|
||||
export function addPathMeta(path): ParamPath {
|
||||
if (path.alias != null && path.wildcard != null) return path;
|
||||
|
||||
path.alias = 0;
|
||||
path.wildcard = 0;
|
||||
|
||||
for (const [i, p] of path.entries()) {
|
||||
if (Array.isArray(p)) {
|
||||
path.alias |= 1 << i;
|
||||
for (let j = 0; j < p.length; j += 2) addPathMeta(p[j]);
|
||||
} else if (p === "*") {
|
||||
path.wildcard |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
Object.freeze(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
// Source: http://stackoverflow.com/a/6969486
|
||||
export function escapeRegExp(str): string {
|
||||
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ParamPath } from "./types";
|
||||
import Path from "./path";
|
||||
|
||||
export default class PathSet {
|
||||
private lengthIndex: Set<ParamPath>[];
|
||||
private fragmentIndex: Map<string, Set<ParamPath>>[];
|
||||
private lengthIndex: Set<Path>[];
|
||||
private fragmentIndex: Map<string, Set<Path>>[];
|
||||
|
||||
public constructor() {
|
||||
this.lengthIndex = [];
|
||||
@@ -32,7 +32,8 @@ export default class PathSet {
|
||||
return this.lengthIndex.length;
|
||||
}
|
||||
|
||||
public add(path: ParamPath): ParamPath {
|
||||
public add(path: Path): Path {
|
||||
if (path.alias) throw new Error("PathSet does not support aliased paths");
|
||||
const p = this.get(path);
|
||||
|
||||
if (p) return p;
|
||||
@@ -45,43 +46,36 @@ export default class PathSet {
|
||||
const lengthIndex = this.lengthIndex[path.length];
|
||||
lengthIndex.add(path);
|
||||
|
||||
let wildcard = 0;
|
||||
for (let i = 0; i < path.length; ++i) {
|
||||
const fragment = path[i];
|
||||
if (fragment === "*") wildcard = wildcard | (1 << i);
|
||||
|
||||
const fragment = path.segments[i] as string;
|
||||
const fragmentIndex = this.fragmentIndex[i];
|
||||
|
||||
let fragmentIndexSet = fragmentIndex.get(fragment);
|
||||
if (!fragmentIndexSet) {
|
||||
fragmentIndexSet = new Set<ParamPath>();
|
||||
fragmentIndexSet = new Set<Path>();
|
||||
fragmentIndex.set(fragment, fragmentIndexSet);
|
||||
}
|
||||
|
||||
fragmentIndexSet.add(path);
|
||||
}
|
||||
|
||||
if (path.wildcard == null) {
|
||||
path.alias = 0;
|
||||
path.wildcard = wildcard;
|
||||
Object.freeze(path);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public get(pattern: ParamPath): ParamPath {
|
||||
const lengthIndex = this.lengthIndex[pattern.length];
|
||||
public get(path: Path): Path {
|
||||
if (path.alias) throw new Error("PathSet does not support aliased paths");
|
||||
const lengthIndex = this.lengthIndex[path.length];
|
||||
if (!lengthIndex || !lengthIndex.size) return null;
|
||||
|
||||
if (lengthIndex.has(pattern)) return pattern;
|
||||
if (lengthIndex.has(path)) return path;
|
||||
|
||||
const sets = [lengthIndex];
|
||||
|
||||
for (let i = 0; i < pattern.length; ++i) {
|
||||
for (let i = 0; i < path.length; ++i) {
|
||||
const fragmentIndex = this.fragmentIndex[i];
|
||||
if (!fragmentIndex || !fragmentIndex.size) return null;
|
||||
|
||||
const fragment = pattern[i];
|
||||
const fragment = path.segments[i] as string;
|
||||
|
||||
const fragmentIndexSet = fragmentIndex.get(fragment);
|
||||
if (!fragmentIndexSet) return null;
|
||||
@@ -92,39 +86,40 @@ export default class PathSet {
|
||||
sets.sort((a, b) => a.size - b.size);
|
||||
|
||||
const smallestSet = sets.shift();
|
||||
for (let path of smallestSet) {
|
||||
for (let p of smallestSet) {
|
||||
for (const s of sets) {
|
||||
if (!s.has(path)) {
|
||||
path = null;
|
||||
if (!s.has(p)) {
|
||||
p = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path) return path;
|
||||
if (p) return p;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public find(
|
||||
pattern: ParamPath,
|
||||
path: Path,
|
||||
superset: boolean = false,
|
||||
subset: boolean = false,
|
||||
depth: number = pattern.length
|
||||
): ParamPath[] {
|
||||
depth: number = path.length
|
||||
): Path[] {
|
||||
if (path.alias) throw new Error("PathSet does not support aliased paths");
|
||||
const res = [];
|
||||
const groups = [[]];
|
||||
|
||||
for (let i = pattern.length; i <= depth; ++i)
|
||||
for (let i = path.length; i <= depth; ++i)
|
||||
if (this.lengthIndex[i]) groups[0].push(this.lengthIndex[i]);
|
||||
|
||||
if (!groups[0].length) return res;
|
||||
|
||||
for (let i = 0; i < pattern.length; ++i) {
|
||||
for (let i = 0; i < path.length; ++i) {
|
||||
const fragmentIndex = this.fragmentIndex[i];
|
||||
if (!fragmentIndex) return res;
|
||||
|
||||
const fragment = pattern[i];
|
||||
const fragment = path.segments[i] as string;
|
||||
|
||||
if (fragment === "*" && subset) continue;
|
||||
|
||||
@@ -149,22 +144,22 @@ export default class PathSet {
|
||||
|
||||
const smallestGroup = groups.shift();
|
||||
for (const set of smallestGroup) {
|
||||
for (let path of set) {
|
||||
for (let p of set) {
|
||||
for (const group of groups) {
|
||||
let found = false;
|
||||
for (const s of group) {
|
||||
if (s.has(path)) {
|
||||
if (s.has(p)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
path = null;
|
||||
p = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (path) res.push(path);
|
||||
if (p) res.push(p);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
@@ -172,6 +172,12 @@ export default class Path {
|
||||
const alias = this.alias | (path.alias << this.segments.length);
|
||||
return new Path(segments, wildcard, alias);
|
||||
}
|
||||
|
||||
public stripAlias(): Path {
|
||||
if (!this.alias) return this;
|
||||
const segments = this.segments.map(s => (Array.isArray(s) ? "*" : s));
|
||||
return new Path(segments, this.wildcard | this.alias, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
|
||||
+25
-15
@@ -32,13 +32,14 @@ import * as localCache from "./local-cache";
|
||||
import * as db from "./db";
|
||||
import * as logger from "./logger";
|
||||
import * as scheduling from "./scheduling";
|
||||
import Path from "./common/path";
|
||||
import {
|
||||
SessionContext,
|
||||
ParamPath,
|
||||
AcsRequest,
|
||||
SetAcsRequest,
|
||||
SessionFault,
|
||||
Operation
|
||||
Operation,
|
||||
Fault
|
||||
} from "./types";
|
||||
|
||||
const MAX_CYCLES = 4;
|
||||
@@ -144,9 +145,19 @@ function writeResponse(
|
||||
}
|
||||
}
|
||||
|
||||
function recordFault(sessionContext, fault, provisions, channels): void;
|
||||
function recordFault(sessionContext, fault): void;
|
||||
function recordFault(sessionContext, fault, provisions?, channels?): void {
|
||||
function recordFault(
|
||||
sessionContext: SessionContext,
|
||||
fault: Fault,
|
||||
provisions,
|
||||
channels
|
||||
): void;
|
||||
function recordFault(sessionContext: SessionContext, fault: Fault): void;
|
||||
function recordFault(
|
||||
sessionContext: SessionContext,
|
||||
fault: Fault,
|
||||
provisions?,
|
||||
channels?
|
||||
): void {
|
||||
if (!provisions) {
|
||||
provisions = sessionContext.provisions;
|
||||
channels = sessionContext.channels;
|
||||
@@ -157,7 +168,10 @@ function recordFault(sessionContext, fault, provisions?, channels?): void {
|
||||
const provs = sessionContext.faults[channel]
|
||||
? sessionContext.faults[channel].provisions
|
||||
: [];
|
||||
faults[channel] = Object.assign({ provisions: provs }, fault);
|
||||
faults[channel] = Object.assign(
|
||||
{ provisions: provs, timestamp: sessionContext.timestamp },
|
||||
fault
|
||||
) as SessionFault;
|
||||
if (channel.startsWith("task_")) {
|
||||
const taskId = channel.slice(5);
|
||||
for (const t of sessionContext.tasks)
|
||||
@@ -334,17 +348,13 @@ function applyPresets(sessionContext: SessionContext): void {
|
||||
deviceData.attributes.revision = 1;
|
||||
|
||||
const deviceEvents = {};
|
||||
for (const p of deviceData.paths.find(
|
||||
["Events", "*"] as ParamPath,
|
||||
false,
|
||||
true
|
||||
)) {
|
||||
for (const p of deviceData.paths.find(Path.parse("Events.*"), false, true)) {
|
||||
const attrs = deviceData.attributes.get(p);
|
||||
if (attrs && attrs.value && attrs.value[1][0] >= sessionContext.timestamp)
|
||||
deviceEvents[p[1]] = true;
|
||||
deviceEvents[p.segments[1] as string] = true;
|
||||
}
|
||||
|
||||
const parameters: { [name: string]: ParamPath } = {};
|
||||
const parameters: { [name: string]: Path } = {};
|
||||
const filteredPresets = [];
|
||||
|
||||
for (const preset of presets) {
|
||||
@@ -377,7 +387,7 @@ function applyPresets(sessionContext: SessionContext): void {
|
||||
for (const k of Object.keys(preset.precondition)) {
|
||||
sessionContext.channels[preset.channel] = 0;
|
||||
const p = k.split(/([^a-zA-Z0-9\-_.].*)/, 1)[0];
|
||||
parameters[p] = common.parsePath(p);
|
||||
parameters[p] = Path.parse(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,7 +419,7 @@ function applyPresets(sessionContext: SessionContext): void {
|
||||
const parameterValues = {};
|
||||
for (const [k, v] of Object.entries(parameters)) {
|
||||
const unpacked = device.unpack(deviceData, v);
|
||||
if (!unpacked[0]) continue;
|
||||
if (!unpacked.length) continue;
|
||||
const attrs = deviceData.attributes.get(unpacked[0]);
|
||||
if (attrs && attrs.value && attrs.value[1])
|
||||
parameterValues[k] = attrs.value[1][0];
|
||||
|
||||
@@ -21,6 +21,8 @@ import { MongoClient, ObjectID } from "mongodb";
|
||||
import { get } from "./config";
|
||||
import { escapeRegExp } from "./common";
|
||||
import { parse } from "./common/expression-parser";
|
||||
import { DeviceData, Attributes } from "./types";
|
||||
import Path from "./common/path";
|
||||
|
||||
export let tasksCollection,
|
||||
devicesCollection,
|
||||
@@ -87,55 +89,66 @@ function optimizeProjection(obj: {}): {} {
|
||||
return obj;
|
||||
}
|
||||
|
||||
export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
export function fetchDevice(
|
||||
id: string,
|
||||
timestamp: number,
|
||||
patterns: [Path, number][],
|
||||
callback
|
||||
): void {
|
||||
const MAX_DEPTH = +get("MAX_DEPTH", id);
|
||||
|
||||
if (!patterns || !patterns.length) patterns = [[[], (1 << MAX_DEPTH) - 1]];
|
||||
if (!patterns || !patterns.length)
|
||||
patterns = [[Path.parse(""), (1 << MAX_DEPTH) - 1]];
|
||||
|
||||
let projection = {};
|
||||
const projectionTree = {};
|
||||
function func(path, pats, projTree): void {
|
||||
const children = {};
|
||||
function func(
|
||||
path: string,
|
||||
pathLength: number,
|
||||
pats: [Path, number][],
|
||||
projTree
|
||||
): void {
|
||||
const children: { [fragment: string]: [Path, number][] } = {};
|
||||
for (const pat of pats) {
|
||||
const fragment = pat[0][path.length] || "*";
|
||||
const fragment = (pat[0].segments[pathLength] as string) || "*";
|
||||
if (fragment === "*") {
|
||||
if (pat[1] << path.length) projection[path.join(".")] = 1;
|
||||
if (pat[1] << pathLength) projection[path.slice(0, -1)] = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!projTree[fragment]) projTree[fragment] = {};
|
||||
|
||||
if (pat[1] & (1 << path.length)) {
|
||||
projection[path.concat("_timestamp").join(".")] = 1;
|
||||
projection[path.concat("_object").join(".")] = 1;
|
||||
projection[path.concat("_instance").join(".")] = 1;
|
||||
projection[path.concat([fragment, "_timestamp"]).join(".")] = 1;
|
||||
projection[path.concat([fragment, "_value"]).join(".")] = 1;
|
||||
projection[path.concat([fragment, "_type"]).join(".")] = 1;
|
||||
projection[path.concat([fragment, "_object"]).join(".")] = 1;
|
||||
projection[path.concat([fragment, "_instance"]).join(".")] = 1;
|
||||
projection[path.concat([fragment, "_writable"]).join(".")] = 1;
|
||||
projection[path.concat([fragment, "_orig"]).join(".")] = 1;
|
||||
if (pat[1] & (1 << pathLength)) {
|
||||
projection[path + "_timestamp"] = 1;
|
||||
projection[path + "_object"] = 1;
|
||||
projection[path + "_instance"] = 1;
|
||||
projection[path + fragment + "._timestamp"] = 1;
|
||||
projection[path + fragment + "._value"] = 1;
|
||||
projection[path + fragment + "._type"] = 1;
|
||||
projection[path + fragment + "._object"] = 1;
|
||||
projection[path + fragment + "._instance"] = 1;
|
||||
projection[path + fragment + "._writable"] = 1;
|
||||
projection[path + fragment + "._orig"] = 1;
|
||||
}
|
||||
|
||||
if (pat[1] >> (path.length + 1)) {
|
||||
if (pat[1] >> (pathLength + 1)) {
|
||||
if (!children[fragment]) children[fragment] = [];
|
||||
children[fragment].push(pat);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [k, v] of Object.entries(children))
|
||||
func(path.concat(k), v, projTree[k]);
|
||||
func(path + k + ".", pathLength + 1, v, projTree[k]);
|
||||
}
|
||||
|
||||
func([], patterns, projectionTree);
|
||||
func("", 0, patterns, projectionTree);
|
||||
|
||||
delete projectionTree["DeviceID"];
|
||||
delete projectionTree["Events"];
|
||||
delete projectionTree["Tags"];
|
||||
|
||||
const res = [];
|
||||
const loaded = [];
|
||||
const res: [Path, number, Attributes?][] = [];
|
||||
const loaded: [Path, number][] = [];
|
||||
|
||||
projection = optimizeProjection(projection);
|
||||
|
||||
@@ -143,38 +156,47 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
if (k === "" || k === "Events" || k.startsWith("Events.")) {
|
||||
if (k === "" || k === "Events" || k === "Events._writable") {
|
||||
res.push([
|
||||
["Events"],
|
||||
Path.parse("Events"),
|
||||
timestamp,
|
||||
{ object: [timestamp, 1], writable: [timestamp, 0] }
|
||||
]);
|
||||
if (k === "Events._writable") loaded.push([["Events"], 1]);
|
||||
if (k === "Events._writable") loaded.push([Path.parse("Events"), 1]);
|
||||
}
|
||||
if (k === "Events") {
|
||||
projection["_lastInform"] = 1;
|
||||
projection["_lastBoot"] = 1;
|
||||
projection["_lastBootstrap"] = 1;
|
||||
projection["_registered"] = 1;
|
||||
loaded.push([["Events"], (1 << MAX_DEPTH) - 1]);
|
||||
loaded.push([Path.parse("Events"), (1 << MAX_DEPTH) - 1]);
|
||||
} else if (k === "Events.Inform._writable" || k === "Events.Inform") {
|
||||
projection["_lastInform"] = 1;
|
||||
loaded.push([["Events", "Inform"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([Path.parse("Events.Inform"), 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
} else if (k === "Events.1_BOOT._writable" || k === "Events.1_BOOT") {
|
||||
projection["_lastBoot"] = 1;
|
||||
loaded.push([["Events", "1_BOOT"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([Path.parse("Events.1_BOOT"), 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
} else if (
|
||||
k === "Events.0_BOOTSTRAP._writable" ||
|
||||
k === "Events.0_BOOTSTRAP"
|
||||
) {
|
||||
projection["_lastBootstrap"] = 1;
|
||||
loaded.push([["Events", "0_BOOTSTRAP"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([
|
||||
Path.parse("Events.0_BOOTSTRAP"),
|
||||
1 ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
} else if (
|
||||
k === "Events.Registered._writable" ||
|
||||
k === "Events.Registered"
|
||||
) {
|
||||
projection["_registered"] = 1;
|
||||
loaded.push([["Events", "Registered"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([
|
||||
Path.parse("Events.Registered"),
|
||||
1 ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
} else if (k.endsWith("._writable") && k !== "Events._writable") {
|
||||
loaded.push([k.split(".").slice(0, 2), 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([
|
||||
Path.parse(k.split(".", 2).join(".")),
|
||||
1 ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
}
|
||||
if (k !== "") delete projection[k];
|
||||
}
|
||||
@@ -182,42 +204,58 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
if (k === "" || k === "DeviceID" || k.startsWith("DeviceID.")) {
|
||||
if (k === "" || k === "DeviceID" || k === "DeviceID._writable") {
|
||||
res.push([
|
||||
["DeviceID"],
|
||||
Path.parse("DeviceID"),
|
||||
timestamp,
|
||||
{ object: [timestamp, 1], writable: [timestamp, 0] }
|
||||
]);
|
||||
if (k === "DeviceID._writable") loaded.push([["DeviceID"], 1]);
|
||||
if (k === "DeviceID._writable")
|
||||
loaded.push([Path.parse("DeviceID"), 1]);
|
||||
}
|
||||
if (k === "DeviceID") {
|
||||
projection["_id"] = 1;
|
||||
projection["_deviceId"] = 1;
|
||||
loaded.push([["DeviceID"], (1 << MAX_DEPTH) - 1]);
|
||||
loaded.push([Path.parse("DeviceID"), (1 << MAX_DEPTH) - 1]);
|
||||
} else if (k === "DeviceID.ID._writable" || k === "DeviceID.ID") {
|
||||
projection["_id"] = 1;
|
||||
loaded.push([["DeviceID", "ID"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([Path.parse("DeviceID.ID"), 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
} else if (
|
||||
k === "DeviceID.Manufacturer._writable" ||
|
||||
k === "DeviceID.DeManufacturer"
|
||||
) {
|
||||
projection["_deviceId._Manufacturer"] = 1;
|
||||
loaded.push([["DeviceID", "Manufacturer"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([
|
||||
Path.parse("DeviceID.Manufacturer"),
|
||||
1 ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
} else if (
|
||||
k === "DeviceID.ProductClass._writable" ||
|
||||
k === "DeviceID.ProductClass"
|
||||
) {
|
||||
projection["_deviceId._ProductClass"] = 1;
|
||||
loaded.push([["DeviceID", "ProductClass"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([
|
||||
Path.parse("DeviceID.ProductClass"),
|
||||
1 ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
} else if (k === "DeviceID.OUI._writable" || k === "DeviceID.OUI") {
|
||||
projection["_deviceId._OUI"] = 1;
|
||||
loaded.push([["DeviceID", "ProductClass"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([
|
||||
Path.parse("DeviceID.ProductClass"),
|
||||
1 ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
} else if (
|
||||
k === "DeviceID.SerialNumber._writable" ||
|
||||
k === "DeviceID.SerialNumber"
|
||||
) {
|
||||
projection["_deviceId._SerialNumber"] = 1;
|
||||
loaded.push([["DeviceID", "SerialNumber"], 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([
|
||||
Path.parse("DeviceID.SerialNumber"),
|
||||
1 ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
} else if (k.endsWith("._writable") && k !== "DeviceID._writable") {
|
||||
loaded.push([k.split(".").slice(0, 2), 1 ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
loaded.push([
|
||||
Path.parse(k.split(".", 2).join(".")),
|
||||
1 ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
}
|
||||
if (k !== "") delete projection[k];
|
||||
}
|
||||
@@ -225,7 +263,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
if (k === "Tags" || k.startsWith("Tags.")) {
|
||||
if (!projection["_tags"]) {
|
||||
projection["_tags"] = 1;
|
||||
loaded.push([["Tags"], (1 << MAX_DEPTH) - 1]);
|
||||
loaded.push([Path.parse("Tags"), (1 << MAX_DEPTH) - 1]);
|
||||
}
|
||||
delete projection[k];
|
||||
}
|
||||
@@ -240,34 +278,39 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
devicesCollection.findOne({ _id: id }, options, (err, device) => {
|
||||
if (err || !device) return void callback(err);
|
||||
|
||||
function storeParams(obj, path, ts, descendantsFetched, projTree): void {
|
||||
function storeParams(
|
||||
obj,
|
||||
path: string,
|
||||
pathLength: number,
|
||||
ts,
|
||||
descendantsFetched,
|
||||
projTree
|
||||
): void {
|
||||
let thisFetched = false;
|
||||
if (descendantsFetched) {
|
||||
thisFetched = true;
|
||||
} else if (projection[path.join(".")]) {
|
||||
} else if (projection[path.slice(0, -1)]) {
|
||||
descendantsFetched = true;
|
||||
if (
|
||||
path.length &&
|
||||
pathLength &&
|
||||
projection[
|
||||
path
|
||||
.slice(0, -1)
|
||||
.concat("_timestamp")
|
||||
.join(".")
|
||||
path.slice(0, path.lastIndexOf(".", path.length - 2) + 1) +
|
||||
"_timestamp"
|
||||
]
|
||||
) {
|
||||
thisFetched = true;
|
||||
loaded.push([
|
||||
path,
|
||||
((1 << (path.length - 1)) - 1) ^ ((1 << MAX_DEPTH) - 1)
|
||||
Path.parse(path.slice(0, -1)),
|
||||
((1 << (pathLength - 1)) - 1) ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
} else {
|
||||
loaded.push([
|
||||
path,
|
||||
((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)
|
||||
Path.parse(path.slice(0, -1)),
|
||||
((1 << pathLength) - 1) ^ ((1 << MAX_DEPTH) - 1)
|
||||
]);
|
||||
}
|
||||
} else if (projection[path.concat("_writable").join(".")]) {
|
||||
loaded.push([path, 1 << (path.length - 1)]);
|
||||
} else if (projection[path + "_writable"]) {
|
||||
loaded.push([Path.parse(path.slice(0, -1)), 1 << (pathLength - 1)]);
|
||||
thisFetched = true;
|
||||
}
|
||||
|
||||
@@ -277,11 +320,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
if (obj["_instance"] && obj["_object"] == null) obj["_object"] = true;
|
||||
|
||||
if (thisFetched) {
|
||||
const attrs: {
|
||||
object?: [number, number];
|
||||
writable?: [number, number];
|
||||
value?: [number, [string | number | boolean, string]];
|
||||
} = {};
|
||||
const attrs: Attributes = {};
|
||||
let t = obj["_timestamp"] || 1;
|
||||
if (ts > t) t = ts;
|
||||
|
||||
@@ -297,7 +336,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
|
||||
if (obj["_object"] != null) attrs.object = [t, obj["_object"] ? 1 : 0];
|
||||
|
||||
res.push([path, t, attrs]);
|
||||
res.push([Path.parse(path.slice(0, -1)), t, attrs]);
|
||||
}
|
||||
|
||||
for (const [k, v] of Object.entries(obj)) {
|
||||
@@ -305,7 +344,8 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
obj["_object"] = true;
|
||||
storeParams(
|
||||
v,
|
||||
path.concat(k),
|
||||
path + k + ".",
|
||||
pathLength + 1,
|
||||
obj["_timestamp"],
|
||||
descendantsFetched,
|
||||
projTree ? projTree[k] : null
|
||||
@@ -317,14 +357,14 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
if (!descendantsFetched) {
|
||||
if (projTree) {
|
||||
for (const k of Object.keys(projTree)) {
|
||||
const p = path.concat(k);
|
||||
loaded.push([p, ((1 << path.length) - 1) ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
if ((obj["_object"] || !path.length) && obj["_timestamp"])
|
||||
const p = Path.parse(path + k);
|
||||
loaded.push([p, ((1 << pathLength) - 1) ^ ((1 << MAX_DEPTH) - 1)]);
|
||||
if ((obj["_object"] || !pathLength) && obj["_timestamp"])
|
||||
res.push([p, obj["_timestamp"]]);
|
||||
}
|
||||
}
|
||||
} else if ((obj["_object"] || !path.length) && obj["_timestamp"]) {
|
||||
res.push([path.concat("*"), obj["_timestamp"]]);
|
||||
} else if ((obj["_object"] || !pathLength) && obj["_timestamp"]) {
|
||||
res.push([Path.parse(path + "*"), obj["_timestamp"]]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +372,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
switch (k) {
|
||||
case "_lastInform":
|
||||
res.push([
|
||||
["Events", "Inform"],
|
||||
Path.parse("Events.Inform"),
|
||||
+v,
|
||||
{
|
||||
object: [+v, 0],
|
||||
@@ -344,7 +384,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
break;
|
||||
case "_lastBoot":
|
||||
res.push([
|
||||
["Events", "1_BOOT"],
|
||||
Path.parse("Events.1_BOOT"),
|
||||
+v,
|
||||
{
|
||||
object: [+v, 0],
|
||||
@@ -356,7 +396,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
break;
|
||||
case "_lastBootstrap":
|
||||
res.push([
|
||||
["Events", "0_BOOTSTRAP"],
|
||||
Path.parse("Events.0_BOOTSTRAP"),
|
||||
+v,
|
||||
{
|
||||
object: [+v, 0],
|
||||
@@ -369,7 +409,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
case "_registered":
|
||||
// Use current timestamp for registered event attribute timestamps
|
||||
res.push([
|
||||
["Events", "Registered"],
|
||||
Path.parse("Events.Registered"),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
@@ -382,12 +422,12 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
case "_id":
|
||||
if (projection[""] || projection["_id"]) {
|
||||
res.push([
|
||||
["DeviceID", "ID"],
|
||||
Path.parse("DeviceID.ID"),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
writable: [timestamp, 0],
|
||||
value: [timestamp, [v, "xsd:string"]]
|
||||
value: [timestamp, [v as string, "xsd:string"]]
|
||||
}
|
||||
]);
|
||||
}
|
||||
@@ -397,7 +437,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
case "_tags":
|
||||
if ((v as string[]).length) {
|
||||
res.push([
|
||||
["Tags"],
|
||||
Path.parse("Tags"),
|
||||
timestamp,
|
||||
{ object: [timestamp, 1], writable: [timestamp, 0] }
|
||||
]);
|
||||
@@ -406,7 +446,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
for (let t of v as string[]) {
|
||||
t = t.replace(/[^a-zA-Z0-9-]+/g, "_");
|
||||
res.push([
|
||||
["Tags", t],
|
||||
Path.parse("Tags." + t),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
@@ -420,7 +460,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
case "_deviceId":
|
||||
if (v["_Manufacturer"] != null) {
|
||||
res.push([
|
||||
["DeviceID", "Manufacturer"],
|
||||
Path.parse("DeviceID.Manufacturer"),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
@@ -432,7 +472,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
|
||||
if (v["_OUI"] != null) {
|
||||
res.push([
|
||||
["DeviceID", "OUI"],
|
||||
Path.parse("DeviceID.OUI"),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
@@ -444,7 +484,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
|
||||
if (v["_ProductClass"] != null) {
|
||||
res.push([
|
||||
["DeviceID", "ProductClass"],
|
||||
Path.parse("DeviceID.ProductClass"),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
@@ -456,7 +496,7 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
|
||||
if (v["_SerialNumber"] != null) {
|
||||
res.push([
|
||||
["DeviceID", "SerialNumber"],
|
||||
Path.parse("DeviceID.SerialNumber"),
|
||||
timestamp,
|
||||
{
|
||||
object: [timestamp, 0],
|
||||
@@ -470,16 +510,16 @@ export function fetchDevice(id, timestamp, patterns, callback): void {
|
||||
}
|
||||
}
|
||||
|
||||
storeParams(device, [], 0, false, projectionTree);
|
||||
storeParams(device, "", 0, 0, false, projectionTree);
|
||||
callback(null, res, loaded);
|
||||
});
|
||||
}
|
||||
|
||||
export function saveDevice(
|
||||
deviceId,
|
||||
deviceData,
|
||||
isNew,
|
||||
sessionTimestamp,
|
||||
deviceId: string,
|
||||
deviceData: DeviceData,
|
||||
isNew: boolean,
|
||||
sessionTimestamp: number,
|
||||
callback
|
||||
): void {
|
||||
const update = { $set: {}, $unset: {}, $addToSet: {}, $pull: {} };
|
||||
@@ -488,30 +528,25 @@ export function saveDevice(
|
||||
if (diff[0].wildcard !== 1 << (diff[0].length - 1)) continue;
|
||||
|
||||
if (
|
||||
diff[0][0] === "Events" ||
|
||||
diff[0][0] === "DeviceID" ||
|
||||
diff[0][0] === "Tags"
|
||||
diff[0].segments[0] === "Events" ||
|
||||
diff[0].segments[0] === "DeviceID" ||
|
||||
diff[0].segments[0] === "Tags"
|
||||
)
|
||||
continue;
|
||||
|
||||
const parent = deviceData.paths.get(diff[0].slice(0, -1));
|
||||
|
||||
// Param timestamps may be greater than session timestamp to track revisions
|
||||
if (diff[2] > sessionTimestamp) diff[2] = sessionTimestamp;
|
||||
|
||||
if (diff[2] == null && diff[1] != null) {
|
||||
update["$unset"][
|
||||
diff[0]
|
||||
.slice(0, -1)
|
||||
.concat("_timestamp")
|
||||
.join(".")
|
||||
parent.length ? parent.toString() + "._timestamp" : "_timestamp"
|
||||
] = 1;
|
||||
} else {
|
||||
const parent = deviceData.paths.get(diff[0].slice(0, -1));
|
||||
if (parent && (!parent.length || deviceData.attributes.has(parent))) {
|
||||
update["$set"][
|
||||
diff[0]
|
||||
.slice(0, -1)
|
||||
.concat("_timestamp")
|
||||
.join(".")
|
||||
parent.length ? parent.toString() + "._timestamp" : "_timestamp"
|
||||
] = new Date(diff[2]);
|
||||
}
|
||||
}
|
||||
@@ -530,11 +565,11 @@ export function saveDevice(
|
||||
const writable2 = ((diff[2] || {}).writable || [])[1];
|
||||
const writable1 = ((diff[1] || {}).writable || [])[1];
|
||||
|
||||
switch (path[0]) {
|
||||
switch (path.segments[0]) {
|
||||
case "Events":
|
||||
if (diff[0].length === 2 && value2 !== value1) {
|
||||
if (path.length === 2 && value2 !== value1) {
|
||||
if (!diff[2]) {
|
||||
switch (path[1]) {
|
||||
switch (path.segments[1]) {
|
||||
case "Inform":
|
||||
update["$unset"]["_lastInform"] = 1;
|
||||
break;
|
||||
@@ -548,8 +583,8 @@ export function saveDevice(
|
||||
update["$unset"]["_registered"] = 1;
|
||||
}
|
||||
} else {
|
||||
const t = new Date(diff[2].value[1][0]);
|
||||
switch (path[1]) {
|
||||
const t = new Date(diff[2].value[1][0] as number);
|
||||
switch (path.segments[1]) {
|
||||
case "Inform":
|
||||
update["$set"]["_lastInform"] = t;
|
||||
break;
|
||||
@@ -569,7 +604,7 @@ export function saveDevice(
|
||||
case "DeviceID":
|
||||
if (value2 !== value1) {
|
||||
const v = diff[2].value[1][0];
|
||||
switch (path[1]) {
|
||||
switch (path.segments[1]) {
|
||||
case "ID":
|
||||
update["$set"]["_id"] = v;
|
||||
break;
|
||||
@@ -592,21 +627,21 @@ export function saveDevice(
|
||||
if (value2 != null) {
|
||||
if (!update["$addToSet"]["_tags"])
|
||||
update["$addToSet"]["_tags"] = { $each: [] };
|
||||
update["$addToSet"]["_tags"]["$each"].push(diff[0][1]);
|
||||
update["$addToSet"]["_tags"]["$each"].push(path.segments[1]);
|
||||
} else {
|
||||
if (!update["$pull"]["_tags"]) {
|
||||
update["$pull"]["_tags"] = {
|
||||
$in: []
|
||||
};
|
||||
}
|
||||
update["$pull"]["_tags"]["$in"].push(diff[0][1]);
|
||||
update["$pull"]["_tags"]["$in"].push(path.segments[1]);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
if (!diff[2]) {
|
||||
update["$unset"][diff[0].join(".")] = 1;
|
||||
update["$unset"][path.toString()] = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -621,35 +656,38 @@ export function saveDevice(
|
||||
if (value2 !== value1) {
|
||||
if (
|
||||
valueType2 === "xsd:dateTime" &&
|
||||
Number.isInteger(value2)
|
||||
Number.isInteger(value2 as number)
|
||||
) {
|
||||
update["$set"][path.concat("_value").join(".")] = new Date(
|
||||
value2
|
||||
update["$set"][path.toString() + "._value"] = new Date(
|
||||
value2 as number
|
||||
);
|
||||
} else {
|
||||
update["$set"][path.concat("_value").join(".")] = value2;
|
||||
update["$set"][path.toString() + "._value"] = value2;
|
||||
}
|
||||
}
|
||||
|
||||
if (valueType2 !== valueType1)
|
||||
update["$set"][path.concat("_type").join(".")] = valueType2;
|
||||
update["$set"][path.toString() + "._type"] = valueType2;
|
||||
|
||||
if (valueTimestamp2 !== valueTimestamp1) {
|
||||
update["$set"][
|
||||
path.concat("_timestamp").join(".")
|
||||
] = new Date(valueTimestamp2);
|
||||
update["$set"][path.toString() + "._timestamp"] = new Date(
|
||||
valueTimestamp2
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
case "object":
|
||||
if (!diff[1] || !diff[1].object || object2 !== object1)
|
||||
update["$set"][path.concat("_object").join(".")] = !!object2;
|
||||
if (!diff[1] || !diff[1].object || object2 !== object1) {
|
||||
update["$set"][
|
||||
path.length ? path.toString() + "._object" : "_object"
|
||||
] = !!object2;
|
||||
}
|
||||
|
||||
break;
|
||||
case "writable":
|
||||
if (!diff[1] || !diff[1].writable || writable2 !== writable1) {
|
||||
update["$set"][
|
||||
path.concat("_writable").join(".")
|
||||
path.length ? path.toString() + "._writable" : "_writable"
|
||||
] = !!writable2;
|
||||
}
|
||||
}
|
||||
@@ -662,10 +700,11 @@ export function saveDevice(
|
||||
diff[1][attrName][1] != null &&
|
||||
(!diff[2] || !diff[2][attrName] || diff[2][attrName][1] == null)
|
||||
) {
|
||||
update["$unset"][path.concat(`_${attrName}`).join(".")] = 1;
|
||||
const p = path.length ? path.toString() + "." : "";
|
||||
update["$unset"][`${p}_${attrName}`] = 1;
|
||||
if (attrName === "value") {
|
||||
update["$unset"][path.concat("_type").join(".")] = 1;
|
||||
update["$unset"][path.concat("_timestamp").join(".")] = 1;
|
||||
update["$unset"][p + "_type"] = 1;
|
||||
update["$unset"][p + "_timestamp"] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-22
@@ -17,18 +17,19 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Path from "./common/path";
|
||||
import * as config from "./config";
|
||||
import * as common from "./common";
|
||||
import * as device from "./device";
|
||||
import * as scheduling from "./scheduling";
|
||||
|
||||
const MAX_DEPTH = +config.get("MAX_DEPTH");
|
||||
|
||||
export function refresh(sessionContext, provision, declarations): boolean {
|
||||
const path = common.parsePath(provision[1]).slice();
|
||||
const l = path.length;
|
||||
path.length = MAX_DEPTH;
|
||||
path.fill("*", l);
|
||||
const segments = Path.parse(provision[1]).segments.slice();
|
||||
const l = segments.length;
|
||||
segments.length = MAX_DEPTH;
|
||||
segments.fill("*", l);
|
||||
const path = Path.parse(segments.join("."));
|
||||
const every = 1000 * (provision[2] || 1);
|
||||
const offset = scheduling.variance(sessionContext.deviceId, every);
|
||||
const t = scheduling.interval(sessionContext.timestamp, every, offset);
|
||||
@@ -48,9 +49,8 @@ export function refresh(sessionContext, provision, declarations): boolean {
|
||||
}
|
||||
|
||||
export function value(sessionContext, provision, declarations): boolean {
|
||||
const path = common.parsePath(provision[1]);
|
||||
declarations.push({
|
||||
path: path,
|
||||
path: Path.parse(provision[1]),
|
||||
pathGet: 1,
|
||||
pathSet: null,
|
||||
attrGet: { value: 1 },
|
||||
@@ -62,10 +62,8 @@ export function value(sessionContext, provision, declarations): boolean {
|
||||
}
|
||||
|
||||
export function tag(sessionContext, provision, declarations): boolean {
|
||||
const path = ["Tags", provision[1]];
|
||||
|
||||
declarations.push({
|
||||
path: path,
|
||||
path: Path.parse(`Tags.${provision[1]}`),
|
||||
pathGet: 1,
|
||||
pathSet: null,
|
||||
attrGet: { value: 1 },
|
||||
@@ -78,7 +76,7 @@ export function tag(sessionContext, provision, declarations): boolean {
|
||||
|
||||
export function reboot(sessionContext, provision, declarations): boolean {
|
||||
declarations.push({
|
||||
path: ["Reboot"],
|
||||
path: Path.parse("Reboot"),
|
||||
pathGet: 1,
|
||||
pathSet: null,
|
||||
attrGet: { value: 1 },
|
||||
@@ -91,7 +89,7 @@ export function reboot(sessionContext, provision, declarations): boolean {
|
||||
|
||||
export function reset(sessionContext, provision, declarations): boolean {
|
||||
declarations.push({
|
||||
path: ["FactoryReset"],
|
||||
path: Path.parse("FactoryReset"),
|
||||
pathGet: 1,
|
||||
pathSet: null,
|
||||
attrGet: { value: 1 },
|
||||
@@ -104,16 +102,13 @@ export function reset(sessionContext, provision, declarations): boolean {
|
||||
|
||||
export function download(sessionContext, provision, declarations): boolean {
|
||||
const alias = [
|
||||
["FileType"],
|
||||
provision[1] || "",
|
||||
["FileName"],
|
||||
provision[2] || "",
|
||||
["TargetFileName"],
|
||||
provision[3] || ""
|
||||
];
|
||||
`FileType:${JSON.stringify(provision[1] || "")}`,
|
||||
`FileName:${JSON.stringify(provision[2] || "")}`,
|
||||
`TargetFileName:${JSON.stringify(provision[3] || "")}`
|
||||
].join(",");
|
||||
|
||||
declarations.push({
|
||||
path: ["Downloads", alias],
|
||||
path: Path.parse(`Downloads.[${alias}]`),
|
||||
pathGet: 1,
|
||||
pathSet: 1,
|
||||
attrGet: null,
|
||||
@@ -122,7 +117,7 @@ export function download(sessionContext, provision, declarations): boolean {
|
||||
});
|
||||
|
||||
declarations.push({
|
||||
path: ["Downloads", alias, "Download"],
|
||||
path: Path.parse(`Downloads.[${alias}].Download`),
|
||||
pathGet: 1,
|
||||
pathSet: null,
|
||||
attrGet: { value: 1 },
|
||||
@@ -144,7 +139,7 @@ export function instances(
|
||||
|
||||
if (Number.isNaN(count)) return true;
|
||||
|
||||
const path = common.parsePath(provision[1]);
|
||||
const path = Path.parse(provision[1]);
|
||||
|
||||
if (provision[2][0] === "+" || provision[2][0] === "-") {
|
||||
declarations.push({
|
||||
|
||||
+50
-61
@@ -17,7 +17,8 @@
|
||||
* along with GenieACS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ParamPath, DeviceData, Declaration, Clear } from "./types";
|
||||
import Path from "./common/path";
|
||||
import { DeviceData, Declaration, Clear } from "./types";
|
||||
|
||||
const CHANGE_FLAGS = {
|
||||
object: 2,
|
||||
@@ -34,7 +35,7 @@ function parseBool(v): boolean {
|
||||
}
|
||||
|
||||
export function sanitizeParameterValue(
|
||||
parameterValue
|
||||
parameterValue: [string | number | boolean, string]
|
||||
): [string | number | boolean, string?] {
|
||||
if (parameterValue[0] != null) {
|
||||
switch (parameterValue[1]) {
|
||||
@@ -55,7 +56,7 @@ export function sanitizeParameterValue(
|
||||
case "xsd:int":
|
||||
case "xsd:unsignedInt":
|
||||
if (typeof parameterValue[0] !== "number") {
|
||||
const i = parseInt(parameterValue[0]);
|
||||
const i = parseInt(parameterValue[0] as string);
|
||||
if (isNaN(i)) parameterValue[0] = "" + parameterValue[0];
|
||||
else parameterValue[0] = i;
|
||||
}
|
||||
@@ -65,7 +66,7 @@ export function sanitizeParameterValue(
|
||||
// Don't use parseInt because it reads date string as a number
|
||||
let i = +parameterValue[0];
|
||||
if (isNaN(i)) {
|
||||
i = Date.parse(parameterValue[0]);
|
||||
i = Date.parse(parameterValue[0] as string);
|
||||
if (isNaN(i)) parameterValue[0] = "" + parameterValue[0];
|
||||
else parameterValue[0] = i;
|
||||
} else {
|
||||
@@ -88,87 +89,75 @@ export function sanitizeParameterValue(
|
||||
return parameterValue;
|
||||
}
|
||||
|
||||
export function getAliasDeclarations(path, timestamp): Declaration[] {
|
||||
const decs = [] as Declaration[];
|
||||
|
||||
function recursive(p, prefix): void {
|
||||
const pattern = prefix.concat(p);
|
||||
|
||||
for (let i = prefix.length; i < pattern.length; ++i) {
|
||||
if (Array.isArray(pattern[i])) {
|
||||
const pat = pattern[i];
|
||||
pattern[i] = "*";
|
||||
for (let j = 0; j < pat.length; j += 2)
|
||||
recursive(pat[j], pattern.slice(0, i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
decs.push({
|
||||
path: pattern,
|
||||
export function getAliasDeclarations(
|
||||
path: Path,
|
||||
timestamp: number,
|
||||
attrGet = null
|
||||
): Declaration[] {
|
||||
const stripped = path.stripAlias();
|
||||
let decs: Declaration[] = [
|
||||
{
|
||||
path: stripped,
|
||||
pathGet: timestamp,
|
||||
pathSet: null,
|
||||
attrGet: prefix.length ? { value: timestamp } : null,
|
||||
attrGet: attrGet,
|
||||
attrSet: null,
|
||||
defer: true
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
recursive(path, []);
|
||||
if (path.alias) {
|
||||
for (const [i, alias] of path.segments.entries()) {
|
||||
if (Array.isArray(alias)) {
|
||||
const parent = stripped.slice(0, i + 1);
|
||||
for (const [p] of alias as [Path, string][]) {
|
||||
decs = decs.concat(
|
||||
getAliasDeclarations(parent.concat(p), timestamp, {
|
||||
value: timestamp
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decs;
|
||||
}
|
||||
|
||||
export function unpack(
|
||||
deviceData,
|
||||
path: ParamPath,
|
||||
deviceData: DeviceData,
|
||||
path: Path,
|
||||
revision?: number
|
||||
): ParamPath[] {
|
||||
let allMatches = [] as ParamPath[];
|
||||
): Path[] {
|
||||
let allMatches = [] as Path[];
|
||||
if (!path.alias) {
|
||||
for (const p of deviceData.paths.find(path, false, true))
|
||||
if (deviceData.attributes.has(p, revision)) allMatches.push(p);
|
||||
} else {
|
||||
const wildcardPath = path.slice() as ParamPath;
|
||||
wildcardPath.wildcard = path.wildcard;
|
||||
for (let i = 0; i < wildcardPath.length; ++i) {
|
||||
if (Array.isArray(wildcardPath[i])) {
|
||||
wildcardPath[i] = "*";
|
||||
wildcardPath.wildcard |= 1 << i;
|
||||
}
|
||||
}
|
||||
const wildcardPath = path.stripAlias();
|
||||
|
||||
for (const p of deviceData.paths.find(wildcardPath, false, true))
|
||||
if (deviceData.attributes.has(p, revision)) allMatches.push(p);
|
||||
|
||||
for (let i = path.length - 1; i >= 0; --i) {
|
||||
if (Array.isArray(path[i])) {
|
||||
for (let j = 0; j < path[i].length; j += 2) {
|
||||
const p = wildcardPath
|
||||
.slice(0, i + 1)
|
||||
.concat(path[i][j]) as ParamPath;
|
||||
p.alias =
|
||||
((wildcardPath.alias & ((1 << (i + 1)) - 1)) + path[i][j].alias) <<
|
||||
(i + 1);
|
||||
p.wildcard =
|
||||
((wildcardPath.wildcard & ((1 << (i + 1)) - 1)) +
|
||||
path[i][j].wildcard) <<
|
||||
(i + 1);
|
||||
if (path.alias & (i << i)) {
|
||||
for (const [param, val] of path.segments[i] as [Path, string][]) {
|
||||
const p = wildcardPath.slice(0, i + 1).concat(param);
|
||||
const unpacked = unpack(deviceData, p, revision);
|
||||
const filtered = [] as ParamPath[];
|
||||
const filtered: Path[] = [];
|
||||
for (const up of unpacked) {
|
||||
const attributes = deviceData.attributes.get(up, revision);
|
||||
if (
|
||||
attributes &&
|
||||
attributes.value &&
|
||||
attributes.value[1] &&
|
||||
sanitizeParameterValue([
|
||||
path[i][j + 1],
|
||||
attributes.value[1][1]
|
||||
])[0] === attributes.value[1][0]
|
||||
sanitizeParameterValue([val, attributes.value[1][1]])[0] ===
|
||||
attributes.value[1][0]
|
||||
) {
|
||||
for (let m = 0; m < allMatches.length; ++m) {
|
||||
let k;
|
||||
for (k = i; k >= 0; --k) if (allMatches[m][k] !== up[k]) break;
|
||||
for (k = i; k >= 0; --k)
|
||||
if (allMatches[m].segments[k] !== up.segments[k]) break;
|
||||
|
||||
if (k < 0) {
|
||||
filtered.push(allMatches[m]);
|
||||
@@ -185,8 +174,8 @@ export function unpack(
|
||||
|
||||
allMatches.sort((p1, p2) => {
|
||||
for (let i = 0; i < p1.length; ++i) {
|
||||
const a = p1[i];
|
||||
const b = p2[i];
|
||||
const a = p1.segments[i] as string;
|
||||
const b = p2.segments[i] as string;
|
||||
if (a !== b) {
|
||||
// Use numeric sorting for numbers
|
||||
const ia = parseInt(a);
|
||||
@@ -285,7 +274,7 @@ function compareEquality(a, b): boolean {
|
||||
|
||||
export function set(
|
||||
deviceData: DeviceData,
|
||||
path: ParamPath,
|
||||
path: Path,
|
||||
timestamp,
|
||||
attributes,
|
||||
toClear?: Clear[]
|
||||
@@ -345,7 +334,7 @@ export function set(
|
||||
if (path.length > 1) {
|
||||
toClear = set(
|
||||
deviceData,
|
||||
path.slice(0, path.length - 1) as ParamPath,
|
||||
path.slice(0, path.length - 1),
|
||||
timestamp,
|
||||
{ object: [timestamp, 1] },
|
||||
toClear
|
||||
@@ -389,7 +378,7 @@ export function set(
|
||||
|
||||
export function track(
|
||||
deviceData: DeviceData,
|
||||
path: ParamPath,
|
||||
path: Path,
|
||||
marker: string,
|
||||
attributes?
|
||||
): void {
|
||||
@@ -408,7 +397,7 @@ export function track(
|
||||
cur[marker] |= f;
|
||||
}
|
||||
|
||||
export function clearTrackers(deviceData, tracker): void {
|
||||
export function clearTrackers(deviceData: DeviceData, tracker): void {
|
||||
if (Array.isArray(tracker)) {
|
||||
for (const v of deviceData.trackers.values())
|
||||
for (const t of tracker) delete v[t];
|
||||
|
||||
+28
-24
@@ -19,12 +19,12 @@
|
||||
|
||||
import * as vm from "vm";
|
||||
import seedrandom from "seedrandom";
|
||||
import * as common from "./common";
|
||||
import * as device from "./device";
|
||||
import * as extensions from "./extensions";
|
||||
import * as logger from "./logger";
|
||||
import * as scheduling from "./scheduling";
|
||||
import { ParamPath, Fault } from "./types";
|
||||
import Path from "./common/path";
|
||||
import { Fault, SessionContext } from "./types";
|
||||
|
||||
// Used for throwing to exit user script and commit
|
||||
const COMMIT = Symbol();
|
||||
@@ -112,7 +112,7 @@ random.seed = function(s) {
|
||||
};
|
||||
|
||||
class ParameterWrapper {
|
||||
public constructor(path, attributes, unpacked?, unpackedRevision?) {
|
||||
public constructor(path: Path, attributes, unpacked?, unpackedRevision?) {
|
||||
for (const attrName of attributes) {
|
||||
Object.defineProperty(this, attrName, {
|
||||
get: function() {
|
||||
@@ -156,7 +156,7 @@ class ParameterWrapper {
|
||||
|
||||
if (!unpacked.length) return UNDEFINED;
|
||||
|
||||
return unpacked[0].join(".");
|
||||
return unpacked[0].toString();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@ class ParameterWrapper {
|
||||
}
|
||||
|
||||
function declare(
|
||||
path: ParamPath,
|
||||
path: string,
|
||||
timestamps: { [attr: string]: number },
|
||||
values: { [attr: string]: any }
|
||||
): ParameterWrapper {
|
||||
@@ -207,7 +207,7 @@ function declare(
|
||||
|
||||
if (!values) values = {};
|
||||
|
||||
const parsedPath = common.parsePath(path);
|
||||
const parsedPath = Path.parse(path);
|
||||
|
||||
const declaration = {
|
||||
path: parsedPath,
|
||||
@@ -249,11 +249,11 @@ function declare(
|
||||
return new ParameterWrapper(parsedPath, attrs);
|
||||
}
|
||||
|
||||
function clear(path, timestamp, attributes): void {
|
||||
function clear(path: string, timestamp: number, attributes?): void {
|
||||
state.uncommitted = true;
|
||||
|
||||
if (state.revision === state.maxRevision)
|
||||
state.clear.push([common.parsePath(path), timestamp, attributes]);
|
||||
state.clear.push([Path.parse(path), timestamp, attributes]);
|
||||
}
|
||||
|
||||
function commit(): void {
|
||||
@@ -282,19 +282,19 @@ function ext(): any {
|
||||
throw EXT;
|
||||
}
|
||||
|
||||
function log(msg, meta): void {
|
||||
function log(msg: string, meta: {}): void {
|
||||
if (state.revision === state.maxRevision && state.extCounter >= 0) {
|
||||
const details = Object.assign({}, meta, {
|
||||
sessionContext: state.sessionContext,
|
||||
message: `Script: ${msg}`
|
||||
});
|
||||
|
||||
delete details.hostname;
|
||||
delete details.pid;
|
||||
delete details.name;
|
||||
delete details.version;
|
||||
delete details.deviceId;
|
||||
delete details.remoteAddress;
|
||||
delete details["hostname"];
|
||||
delete details["pid"];
|
||||
delete details["name"];
|
||||
delete details["version"];
|
||||
delete details["deviceId"];
|
||||
delete details["remoteAddress"];
|
||||
|
||||
logger.accessInfo(details);
|
||||
}
|
||||
@@ -312,7 +312,7 @@ context.random = random;
|
||||
vm.runInContext("Math.random = random;", context);
|
||||
delete context.random;
|
||||
|
||||
function errorToFault(err): Fault {
|
||||
function errorToFault(err: Error): Fault {
|
||||
if (!err) return null;
|
||||
|
||||
if (!err.name) return { code: "script", message: `${err}` };
|
||||
@@ -327,24 +327,28 @@ function errorToFault(err): Fault {
|
||||
};
|
||||
|
||||
if (err.stack) {
|
||||
fault.detail.stack = err.stack;
|
||||
fault.detail["stack"] = err.stack;
|
||||
// Trim the stack trace at the self-executing anonymous wrapper function
|
||||
const stackTrimIndex = fault.detail.stack.match(
|
||||
const stackTrimIndex = fault.detail["stack"].match(
|
||||
/\s+at\s[^\s]+\s+at\s[^\s]+\s\(vm\.js.+\)/
|
||||
);
|
||||
if (stackTrimIndex)
|
||||
fault.detail.stack = fault.detail.stack.slice(0, stackTrimIndex.index);
|
||||
if (stackTrimIndex) {
|
||||
fault.detail["stack"] = fault.detail["stack"].slice(
|
||||
0,
|
||||
stackTrimIndex.index
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return fault;
|
||||
}
|
||||
|
||||
export function run(
|
||||
script,
|
||||
script: vm.Script,
|
||||
globals,
|
||||
sessionContext,
|
||||
startRevision,
|
||||
maxRevision,
|
||||
sessionContext: SessionContext,
|
||||
startRevision: number,
|
||||
maxRevision: number,
|
||||
callback,
|
||||
extCounter = 0
|
||||
): void {
|
||||
|
||||
+283
-270
File diff suppressed because it is too large.
Load diff
+42
-36
@@ -1,27 +1,24 @@
|
||||
import PathSet from "./path-set";
|
||||
import Path from "./common/path";
|
||||
import PathSet from "./common/path-set";
|
||||
import VersionedMap from "./versioned-map";
|
||||
import InstanceSet from "./instance-set";
|
||||
import { IncomingMessage, ServerResponse } from "http";
|
||||
|
||||
export interface ParamPath extends Array<any> {
|
||||
wildcard: number;
|
||||
alias: number;
|
||||
}
|
||||
|
||||
export type Expression = string | number | boolean | null | any[];
|
||||
|
||||
export interface Fault {
|
||||
code: string;
|
||||
message: string;
|
||||
detail?: {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
};
|
||||
detail?:
|
||||
| FaultStruct
|
||||
| {
|
||||
name: string;
|
||||
message: string;
|
||||
stack?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SessionFault extends Fault {
|
||||
channel: string;
|
||||
timestamp: number;
|
||||
provisions: string[][];
|
||||
retryNow?: boolean;
|
||||
@@ -50,15 +47,15 @@ export interface AttributeValues {
|
||||
|
||||
export interface DeviceData {
|
||||
paths: PathSet;
|
||||
timestamps: VersionedMap<ParamPath, number>;
|
||||
attributes: VersionedMap<ParamPath, Attributes>;
|
||||
loaded: Map<ParamPath, number>;
|
||||
trackers: Map<ParamPath, { [name: string]: number }>;
|
||||
timestamps: VersionedMap<Path, number>;
|
||||
attributes: VersionedMap<Path, Attributes>;
|
||||
loaded: Map<Path, number>;
|
||||
trackers: Map<Path, { [name: string]: number }>;
|
||||
changes: Set<string>;
|
||||
}
|
||||
|
||||
export type VirtualParameterDeclaration = [
|
||||
ParamPath,
|
||||
Path,
|
||||
{ path?: number; object?: number; writable?: number; value?: number }?,
|
||||
{
|
||||
path?: [number, number];
|
||||
@@ -70,22 +67,24 @@ export type VirtualParameterDeclaration = [
|
||||
|
||||
export interface SyncState {
|
||||
refreshAttributes: {
|
||||
exist: Set<ParamPath>;
|
||||
object: Set<ParamPath>;
|
||||
writable: Set<ParamPath>;
|
||||
value: Set<ParamPath>;
|
||||
exist: Set<Path>;
|
||||
object: Set<Path>;
|
||||
writable: Set<Path>;
|
||||
value: Set<Path>;
|
||||
};
|
||||
spv: Map<ParamPath, [string | number | boolean, string]>;
|
||||
gpn: Set<ParamPath>;
|
||||
gpnPatterns: Map<ParamPath, number>;
|
||||
tags: Map<ParamPath, [string | number | boolean, string]>;
|
||||
spv: Map<Path, [string | number | boolean, string]>;
|
||||
gpn: Set<Path>;
|
||||
gpnPatterns: Map<Path, number>;
|
||||
tags: Map<Path, boolean>;
|
||||
virtualParameterDeclarations: VirtualParameterDeclaration[][];
|
||||
instancesToDelete: Map<ParamPath, Set<ParamPath>>;
|
||||
instancesToCreate: Map<ParamPath, InstanceSet>;
|
||||
downloadsToDelete: Set<ParamPath>;
|
||||
instancesToDelete: Map<Path, Set<Path>>;
|
||||
instancesToCreate: Map<Path, InstanceSet>;
|
||||
downloadsToDelete: Set<Path>;
|
||||
downloadsToCreate: InstanceSet;
|
||||
downloadsValues: Map<ParamPath, string | number>;
|
||||
downloadsDownload: Map<ParamPath, number>;
|
||||
downloadsValues: Map<Path, string | number>;
|
||||
downloadsDownload: Map<Path, number>;
|
||||
reboot: number;
|
||||
factoryReset: number;
|
||||
}
|
||||
|
||||
export interface SessionContext {
|
||||
@@ -97,13 +96,19 @@ export interface SessionContext {
|
||||
timeout: number;
|
||||
provisions: any[];
|
||||
channels: { [channel: string]: number };
|
||||
virtualParameters: any[];
|
||||
virtualParameters: [
|
||||
string,
|
||||
AttributeTimestamps,
|
||||
AttributeValues,
|
||||
AttributeTimestamps,
|
||||
AttributeValues
|
||||
][][];
|
||||
revisions: number[];
|
||||
rpcCount: number;
|
||||
iteration: number;
|
||||
cycle: number;
|
||||
extensionsCache: any;
|
||||
declarations: any[];
|
||||
declarations: Declaration[][];
|
||||
faults?: { [channel: string]: SessionFault };
|
||||
retries?: { [channel: string]: number };
|
||||
cacheSnapshot?: string;
|
||||
@@ -112,7 +117,7 @@ export interface SessionContext {
|
||||
httpRequest?: IncomingMessage;
|
||||
faultsTouched?: { [channel: string]: boolean };
|
||||
presetCycles?: number;
|
||||
toLoad?: Map<ParamPath, number>;
|
||||
toLoad?: Map<Path, number>;
|
||||
new?: boolean;
|
||||
debug?: boolean;
|
||||
tasks?: Task[];
|
||||
@@ -154,6 +159,7 @@ export interface Operation {
|
||||
|
||||
export interface AcsRequest {
|
||||
name: string;
|
||||
next?: string;
|
||||
}
|
||||
|
||||
export interface GetAcsRequest extends AcsRequest {
|
||||
@@ -162,6 +168,7 @@ export interface GetAcsRequest extends AcsRequest {
|
||||
parameterNames?: string[];
|
||||
parameterPath?: string;
|
||||
nextLevel?: boolean;
|
||||
instanceValues?: { [name: string]: string };
|
||||
}
|
||||
|
||||
export interface SetAcsRequest extends AcsRequest {
|
||||
@@ -175,7 +182,6 @@ export interface SetAcsRequest extends AcsRequest {
|
||||
parameterList?: [string, string | number | boolean, string][];
|
||||
instanceValues?: { [param: string]: string | number | boolean };
|
||||
objectName?: string;
|
||||
next?: string;
|
||||
DATETIME_MILLISECONDS?: boolean;
|
||||
BOOLEAN_LITERAL?: boolean;
|
||||
commandKey?: string;
|
||||
@@ -266,7 +272,7 @@ export interface QueryOptions {
|
||||
}
|
||||
|
||||
export interface Declaration {
|
||||
path: ParamPath;
|
||||
path: Path;
|
||||
pathGet: number;
|
||||
pathSet?: [number, number];
|
||||
attrGet?: { object?: number; writable?: number; value?: number };
|
||||
@@ -279,7 +285,7 @@ export interface Declaration {
|
||||
}
|
||||
|
||||
export type Clear = [
|
||||
ParamPath,
|
||||
Path,
|
||||
number,
|
||||
{ object?: number; writable?: number; value?: number }?,
|
||||
number?
|
||||
|
||||
Reference in new issue
Block a user