src/lib/db/Browser.js
import Emitter from 'events';
import DBWorker, { Job } from 'atvise-dbworker';
import { log, colors } from 'gulp-util';
import { browse_service as BrowseService } from 'node-opcua';
import Project from '../config/ProjectConfig';
const NodeClass = {
Object: 1,
Variable: 2,
};
/**
* Browses the atvise-server database based on one or more root nodes.
*/
export default class DBBrowser extends Emitter {
/**
* Creates a new Browser with some options.
* @param {Object} options The options to apply.
* @param {Boolean} [options.recursive=true] If the browser should also browse subnodes.
*/
constructor(options = {}) {
super();
/**
* The DBWorker used
* @type {DBWorker}
*/
this.worker = new DBWorker(`opc.tcp://${Project.host}:${Project.port.opc}`);
/**
* If the browser should also browse subnodes.
* @type {Boolean}
*/
this.recursive = options.recursive || true;
/**
* The node adresses already browsed.
* @type {Map<String, Boolean>}
*/
this.browsed = {};
this.discovered = 0;
/**
* A regular expression built from the projects' {@link Atviseproject.ignoreNodes}.
* @type {RegExp}
*/
this._ignoredRegExp = new RegExp(`^(${Project.ignoreNodes.map(n => n.toString()).join('|')})`);
this._browseResultMask = BrowseService
.makeResultMask('ReferenceType | NodeClass | TypeDefinition');
}
_browseNodes(session, nodes, cb) {
session.requestedMaxReferencesPerNode = 1000000;
session.browse(nodes, (err, results) => {
if (err) {
cb(err);
} else {
const discoveredVariables = [];
results.forEach((result, resultIndex) => {
if (result.statusCode > 0) {
cb(result.statusCode);
} else {
result.references.forEach(ref => {
const id = ref.nodeId.toString();
if (
this.browsed[id] === undefined &&
(id.split(nodes[resultIndex].nodeId || nodes[resultIndex]).length > 1) &&
id.match(this._ignoredRegExp) === null
) {
this.browsed[id] = true;
if (this.recursive) {
this.worker.addJob(new Job((...args) => this._browseNodes(...args), [{
nodeId: id,
includeSubtypes: true,
browseDirection: BrowseService.BrowseDirection.Forward,
resultMask: this._browseResultMask,
}]));
}
if (ref.nodeClass.value === NodeClass.Variable) {
this.emit('discovered', ref);
this.discovered++;
discoveredVariables.push(ref);
}
}
});
if (result.continuationPoint !== null) {
// TODO: Handle continuation point
}
}
});
if (discoveredVariables.length > 0) {
this.emit('discoveredvariables', discoveredVariables);
}
cb();
}
});
}
// TODO: Document events emitted
/**
* Starts the browser based on some root nodes.
* @param {NodeId[]|String[]} nodes The root nodes to browse.
*/
browse(nodes) {
this.worker.addJob(new Job((...args) => {
this._browseNodes(...args);
}, nodes.map(n => n.toString())));
this.worker.on('error', err => log(err));
this.worker.once('complete', () => {
log('Found', colors.cyan(this.discovered), 'nodes.');
this.emit('complete');
});
this.worker.start();
}
}