Home Manual Reference Source Test

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();
  }

}