<commit_before>
/**
 * Module dependencies
 */

var _       = require('underscore');
var config  = require('./config.js');
var gpio    = require('rpi-gpio');
var log     = require('logule').init(module, 'cascade');
var model   = require('./model.js');
var nfc     = require('./nfc.js');
var usb     = require('./usb.js');
var user    = require('./user.js');

/**
 * Initialize Cascade
 *
 * @constructor
 */

function Cascade() {

  /**
   * setup rpi pins
   *
   * GPIO 21 = change slot relay
   * GPIO 12 = status light
   *
   * @see https://github.com/JamesBarwell/rpi-gpio.js
   */

  var initPins = [
    config.rpi.relayPin,
    config.rpi.ledPin
  ];

  _.each(initPins, function(pin) {

    log.info(pin);

    gpio.setup(pin, gpio.DIR_OUT, function(err) {
      if (err) {
        throw err;
      }
      gpio.write(pin, true, function(err) {
        if (err) {
          throw err;
        }
      });
    });
  });

  // ..

  var machine = undefined;

  // ..

  this.init();
}

/**
 * init
 */

Cascade.prototype.init = function() {
  log.info('init');
  this.initMachine()
  this.initNfc();
  this.initUsb();
};

/**
 * init machine user
 */

Cascade.prototype.initMachine = function() {
  var self = this;
  User.find({
    where: ['id = ? and username = ?', '1', 'Machine'],
    attributes: ['balance']
  }).error(function(err) {
    //cant get machine user, bail out
    throw err;
  }).success(function(machine) {
    self.machine = machine;
  });
};

/**
 * find user for device event
 *
 * @param id device either usb serial or nfs uid/ats
 */

Cascade.prototype.findUserAndWithdraw = function(id) {
  var amount = 0.50;
  var self = this;

  /**
   * does machine have funds?
   */

  if (!machine || machine.balance < amount) {
    blinkLED(3*config.ledBlinkTime);
    log.error("machine empty");
    return;
  }

  /**
   * get user for device
   */

  user.findUserForDevice(id, function(error, user) {
    if (error) {
      //failed
      return;
    }

    if (user === null) {
      log.error(user + ' does not have a cascade account');

      /**
       * blink LED to show no user account
       */

      blinkLED(config.ledBlinkTime);
      return;
    }

    /**
     * does user have funds?
     */

    if (user.balance < amount) {
      log.error('user does not have enough funds');

      /**
       * blink LED three times to show no user funds
       */

      blinkLED(config.ledBlinkTime);
      setTimeout(function() {blinkLED(config.ledBlinkTime);}, config.ledBlinkTime*2*1);
      setTimeout(function() {blinkLED(config.ledBlinkTime);}, config.ledBlinkTime*2*2);

      return;
    }

    /**
     * cycle relay and drop coins
     */

    cycleSlot(config.relayCycleTime);

    /**
     * update machine account
     */

    self.machine.withdraw(amount);

    /**
     * update user balance
     */

    user.withdraw(amount);

    /**
     * notify user
     */

    user.notifyBalance();
  });
};

// ------------------------------------------------------------------------------------
// NFC
// ------------------------------------------------------------------------------------

/**
 * init nfc detection handlers
 */

Cascade.prototype.initNfc = function() {
  nfc.on('uid', this._handleNfcUid.bind(this));
};

/**
 * trigger on nfc uid event, look up user and spit out coins
 *
 * @param _uid
 * @private
 */

Cascade.prototype._handleNfcUid = function(uid) {
  this.findUserAndWithdraw(uid);
};

// ------------------------------------------------------------------------------------
// USB
// ------------------------------------------------------------------------------------

/**
 * init usb detection handlers
 */

Cascade.prototype.initUsb = function() {
  usb.on('add', this._handleAddUsbDevice.bind(this));
};

/**
 * Handle usb-detection `add` device event
 *
 * @param err
 * @param devices
 * @private
 */

Cascade.prototype._handleAddUsbDevice = function(usbSerialID) {
  this.findUserAndWithdraw(usbSerialID);
};

// ------------------------------------------------------------------------------------
// RPI helpers
// ------------------------------------------------------------------------------------

/**
 * Set pin high and after `delay` set pin low
 * @param delay in milliseconds
 */

function cycleSlot(delay) {
  gpio.write(config.rpi.relayPin, false, function(err) {
    if (err) {
      throw err;
    }
    setTimeout(function() {
      gpio.write(config.rpi.relayPin, true, function(err) {
        if (err) {
          throw err;
        }
      });
    }, delay);
  });
}

/**
 * Blink LED for a number of milliseconds
 * @param delay in milliseconds
 */

function blinkLED(delay) {
  gpio.write(config.rpi.ledPin, false, function(err) {
    if (err) throw err;
    setTimeout(function() {
      gpio.write(config.rpi.ledPin, true, function(err) {
        if (err) throw err;
      });
    }, delay)
  });
}

/**
 * Expose `Cascade`
 * @type {Cascade}
 */

module.exports = (function() {
  var instance;
  if (typeof instance === 'undefined') {
    instance = new Cascade();
  }
  return instance;
}());
<commit_msg>adding user model<commit_after>
/**
 * Module dependencies
 */

var _       = require('underscore');
var config  = require('./config.js');
var gpio    = require('rpi-gpio');
var log     = require('logule').init(module, 'cascade');
var model   = require('./model.js');
var nfc     = require('./nfc.js');
var usb     = require('./usb.js');
var user    = require('./user.js');

/**
 * Initialize Cascade
 *
 * @constructor
 */

function Cascade() {

  /**
   * setup rpi pins
   *
   * GPIO 21 = change slot relay
   * GPIO 12 = status light
   *
   * @see https://github.com/JamesBarwell/rpi-gpio.js
   */

  var initPins = [
    config.rpi.relayPin,
    config.rpi.ledPin
  ];

  _.each(initPins, function(pin) {

    log.info(pin);

    gpio.setup(pin, gpio.DIR_OUT, function(err) {
      if (err) {
        throw err;
      }
      gpio.write(pin, true, function(err) {
        if (err) {
          throw err;
        }
      });
    });
  });

  // ..

  var machine = undefined;

  // ..

  this.init();
}

/**
 * init
 */

Cascade.prototype.init = function() {
  log.info('init');
  this.initMachine()
  this.initNfc();
  this.initUsb();
};

/**
 * init machine user
 */

Cascade.prototype.initMachine = function() {
  var self = this;
  var User = model.User;

  User.find({
    where: ['id = ? and username = ?', '1', 'Machine'],
    attributes: ['balance']
  }).error(function(err) {
    //cant get machine user, bail out
    throw err;
  }).success(function(machine) {
    self.machine = machine;
  });
};

/**
 * find user for device event
 *
 * @param id device either usb serial or nfs uid/ats
 */

Cascade.prototype.findUserAndWithdraw = function(id) {
  var amount = 0.50;
  var self = this;

  /**
   * does machine have funds?
   */

  if (!machine || machine.balance < amount) {
    blinkLED(3*config.ledBlinkTime);
    log.error("machine empty");
    return;
  }

  /**
   * get user for device
   */

  user.findUserForDevice(id, function(error, user) {
    if (error) {
      //failed
      return;
    }

    if (user === null) {
      log.error(user + ' does not have a cascade account');

      /**
       * blink LED to show no user account
       */

      blinkLED(config.ledBlinkTime);
      return;
    }

    /**
     * does user have funds?
     */

    if (user.balance < amount) {
      log.error('user does not have enough funds');

      /**
       * blink LED three times to show no user funds
       */

      blinkLED(config.ledBlinkTime);
      setTimeout(function() {blinkLED(config.ledBlinkTime);}, config.ledBlinkTime*2*1);
      setTimeout(function() {blinkLED(config.ledBlinkTime);}, config.ledBlinkTime*2*2);

      return;
    }

    /**
     * cycle relay and drop coins
     */

    cycleSlot(config.relayCycleTime);

    /**
     * update machine account
     */

    self.machine.withdraw(amount);

    /**
     * update user balance
     */

    user.withdraw(amount);

    /**
     * notify user
     */

    user.notifyBalance();
  });
};

// ------------------------------------------------------------------------------------
// NFC
// ------------------------------------------------------------------------------------

/**
 * init nfc detection handlers
 */

Cascade.prototype.initNfc = function() {
  nfc.on('uid', this._handleNfcUid.bind(this));
};

/**
 * trigger on nfc uid event, look up user and spit out coins
 *
 * @param _uid
 * @private
 */

Cascade.prototype._handleNfcUid = function(uid) {
  this.findUserAndWithdraw(uid);
};

// ------------------------------------------------------------------------------------
// USB
// ------------------------------------------------------------------------------------

/**
 * init usb detection handlers
 */

Cascade.prototype.initUsb = function() {
  usb.on('add', this._handleAddUsbDevice.bind(this));
};

/**
 * Handle usb-detection `add` device event
 *
 * @param err
 * @param devices
 * @private
 */

Cascade.prototype._handleAddUsbDevice = function(usbSerialID) {
  this.findUserAndWithdraw(usbSerialID);
};

// ------------------------------------------------------------------------------------
// RPI helpers
// ------------------------------------------------------------------------------------

/**
 * Set pin high and after `delay` set pin low
 * @param delay in milliseconds
 */

function cycleSlot(delay) {
  gpio.write(config.rpi.relayPin, false, function(err) {
    if (err) {
      throw err;
    }
    setTimeout(function() {
      gpio.write(config.rpi.relayPin, true, function(err) {
        if (err) {
          throw err;
        }
      });
    }, delay);
  });
}

/**
 * Blink LED for a number of milliseconds
 * @param delay in milliseconds
 */

function blinkLED(delay) {
  gpio.write(config.rpi.ledPin, false, function(err) {
    if (err) throw err;
    setTimeout(function() {
      gpio.write(config.rpi.ledPin, true, function(err) {
        if (err) throw err;
      });
    }, delay)
  });
}

/**
 * Expose `Cascade`
 * @type {Cascade}
 */

module.exports = (function() {
  var instance;
  if (typeof instance === 'undefined') {
    instance = new Cascade();
  }
  return instance;
}());
<|endoftext|>


<commit_before>"use strict";

var error = require('debug')('error');
var generate = require('./base');
var room = require('../room');
var verbose = require('debug')('verbose:queue');

var TEAMSIZE = 6;

var user = {};
var teams = { waitPlayer: [], waitConfirm: [] };
var queue = [];

function Team() {
  verbose('Team() constructor called');
  this.room = new room.Room();
  this.members = [];
  this.confirmed = {};
}

Team.prototype.push = function (username) {
  verbose('Team.push(%s)', username);
  var socket = user[username].socket;
  this.room.push(socket);
  this.members.push(username);
  user[username].team = this;
  this.confirmed[username] = false;
};

Team.prototype.move = function (src, dest) {
  verbose('Team.move(%s, %s)', src, dest);
  var index = src.indexOf(this);
  if (index > -1) {
    src.splice(index, 1);
    verbose('Team.move(%s, %s), remove team from src', src, dest);
    if (dest) {
      verbose('Team.move(%s, %s), push team to dest', src, dest);
      dest.push(this);
    }
  } else {
    verbose('Team.move(%s, %s), src does not contain this team.', src, dest);
  }
};

Team.prototype.removeUser = function (username) {
  verbose('Team.removeUser(%s)', username);
  var index = this.members.indexOf(username);
  if (index > -1) {
    this.members.splice(index, 1);
    this.room.remove(user[username].socket);
    user[username].team = null;
    delete this.confirmed[username];
  }
};

function updateClient(team, state) {
  if (!team instanceof Team || typeof state !== 'string') {
    error('updateClient, check arguments');
    return;
  }

  var current;
  if (state === 'wait-player') {
    current = team.members.length;
  } else if (state === 'wait-confirm') {
    current = Object.keys(team.confirmed)
      .map(function (k) {
        return team.confirmed[k];
      }).filter(function (v) {
        return v;
      }).length;
  } else {
    error('queue/update, abnormal state(%s)', state);
    return;
  }

  verbose('emit queue/update, state: %s, current: %s', state, current);
  team.room.emit('queue/update', {
    state: state,
    current: current
  });
}

function assignTeam() {
  verbose('assignTeam');
  if (queue.length && teams.waitPlayer.length === 0) {
    verbose('no team waiting player. make a new one.');
    teams.waitPlayer.push(new Team());
    verbose('teams.waitPlayer.length = %s', teams.waitPlayer.length);
  }

  for (var i = 0; i < teams.waitPlayer.length && queue.length; i++) {
    var team = teams.waitPlayer[i];
    var changed = false;
    while (team.members.length < TEAMSIZE && queue.length) {
      team.push(queue.shift());
      changed = true;
    }

    // if any changes, emit queue/update(wait-player) to every team member
    if (changed) {
      verbose('player(s) assigned. broadcast wait-player');
      updateClient(team, 'wait-player');
    }

    if (team.members.length === TEAMSIZE) {
      verbose('team is full(%s). move this team to wait-confirm list', TEAMSIZE);
      // add this team to wait-confirm list
      team.move(teams.waitPlayer, teams.waitConfirm);

      // emit wait-confirm to every team member.
      updateClient(team, 'wait-confirm');
    }
  }
}

function join() {
  verbose('queue/join, username: %s', this.socket.username);
  queue.push(this.socket.username);
  user[this.socket.username] = { socket: this.socket };
  assignTeam();
}

function exit() {
  var username = this.socket.username;
  verbose('queue/exit, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (team) {
      var isWaitingPlayer = teams.waitPlayer.indexOf(team) > -1;
      var isWaitingConfirm = teams.waitConfirm.indexOf(team) > -1;
      if (isWaitingPlayer || isWaitingConfirm) {
        team.removeUser(username);
        if (isWaitingConfirm) {
          team.move(teams.waitConfirm, teams.waitPlayer);
          updateClient(team, 'wait-player');
        }
      } else {
        error('queue/exit, abnormal state');
      }
    }
  }
}

function confirm() {
  var username = this.socket.username;
  verbose('queue/confirm, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (teams.waitConfirm.indexOf(team) < 0) {
      error('queue/confirm, abnormal state');
      return;
    }

    team.confirmed[username] = true;
    updateClient(team, 'wait-confirm');
    // from now, client knows this confirm makes game start or not.

    var everyoneConfirmed = true;
    for (var u in team.confirmed) {
      verbose('user: %s, confirmed: %s', u, team.confirmed[u]);
      if (!team.confirmed[u]) {
        verbose('queue/confirm, not everyone confirmed yet');
        everyoneConfirmed = false;
        break;
      }
    }

    if (everyoneConfirmed) {
      verbose('queue/confirm, everyone confirmed! start a game');
      team.move(teams.waitConfirm, null);
      // TODO: start game!
    }
  }
}

function disconnect() {
  var username = this.socket.username;
  verbose('disconnect. %s', username);

  var index = queue.indexOf(username);
  if (index > -1) {
    queue.splice(index, 1);
  }

  if (user[username]) {
    var team = user[username].team;
    if (team) {
      var isWaitingPlayer = teams.waitPlayer.indexOf(team) > -1;
      var isWaitingConfirm = teams.waitConfirm.indexOf(team) > -1;
      if (isWaitingPlayer || isWaitingConfirm) {
        team.removeUser(username);
        updateClient(team, 'wait-player');
        if (isWaitingConfirm) {
          verbose('user disconnected was in the team waiting confirm.. this team goes waiting players again ');
          team.move(teams.waitConfirm, teams.waitPlayer);
        }
      }
    }
  }
}

module.exports = generate({
  'queue/join': { name: 'join', function: join },
  'queue/exit': { name: 'exit', function: exit },
  'queue/confirm': { name: 'confirm', function: confirm },
  'queue/dodge': { name: 'dodge', function: exit },
  'disconnect': { name: 'disconnect', function: disconnect }
});
<commit_msg>Reset confirm counter roughly.<commit_after>"use strict";

var error = require('debug')('error');
var generate = require('./base');
var room = require('../room');
var verbose = require('debug')('verbose:queue');

var TEAMSIZE = 6;

var user = {};
var teams = { waitPlayer: [], waitConfirm: [] };
var queue = [];

function Team() {
  verbose('Team() constructor called');
  this.room = new room.Room();
  this.members = [];
  this.confirmed = {};
}

Team.prototype.push = function (username) {
  verbose('Team.push(%s)', username);
  var socket = user[username].socket;
  this.room.push(socket);
  this.members.push(username);
  user[username].team = this;
  this.confirmed[username] = false;
};

Team.prototype.move = function (src, dest) {
  verbose('Team.move(%s, %s)', src, dest);
  var index = src.indexOf(this);
  if (index > -1) {
    // reset confirm count roughly.
    this.confirmed = {};

    src.splice(index, 1);
    verbose('Team.move(%s, %s), remove team from src', src, dest);
    if (dest) {
      verbose('Team.move(%s, %s), push team to dest', src, dest);
      dest.push(this);
    }
  } else {
    verbose('Team.move(%s, %s), src does not contain this team.', src, dest);
  }
};

Team.prototype.removeUser = function (username) {
  verbose('Team.removeUser(%s)', username);
  var index = this.members.indexOf(username);
  if (index > -1) {
    this.members.splice(index, 1);
    this.room.remove(user[username].socket);
    user[username].team = null;
    delete this.confirmed[username];
  }
};

function updateClient(team, state) {
  if (!team instanceof Team || typeof state !== 'string') {
    error('updateClient, check arguments');
    return;
  }

  var current;
  if (state === 'wait-player') {
    current = team.members.length;
  } else if (state === 'wait-confirm') {
    current = Object.keys(team.confirmed)
      .map(function (k) {
        return team.confirmed[k];
      }).filter(function (v) {
        return v;
      }).length;
  } else {
    error('queue/update, abnormal state(%s)', state);
    return;
  }

  verbose('emit queue/update, state: %s, current: %s', state, current);
  team.room.emit('queue/update', {
    state: state,
    current: current
  });
}

function assignTeam() {
  verbose('assignTeam');
  if (queue.length && teams.waitPlayer.length === 0) {
    verbose('no team waiting player. make a new one.');
    teams.waitPlayer.push(new Team());
    verbose('teams.waitPlayer.length = %s', teams.waitPlayer.length);
  }

  for (var i = 0; i < teams.waitPlayer.length && queue.length; i++) {
    var team = teams.waitPlayer[i];
    var changed = false;
    while (team.members.length < TEAMSIZE && queue.length) {
      team.push(queue.shift());
      changed = true;
    }

    // if any changes, emit queue/update(wait-player) to every team member
    if (changed) {
      verbose('player(s) assigned. broadcast wait-player');
      updateClient(team, 'wait-player');
    }

    if (team.members.length === TEAMSIZE) {
      verbose('team is full(%s). move this team to wait-confirm list', TEAMSIZE);
      // add this team to wait-confirm list
      team.move(teams.waitPlayer, teams.waitConfirm);

      // emit wait-confirm to every team member.
      updateClient(team, 'wait-confirm');
    }
  }
}

function join() {
  verbose('queue/join, username: %s', this.socket.username);
  queue.push(this.socket.username);
  user[this.socket.username] = { socket: this.socket };
  assignTeam();
}

function exit() {
  var username = this.socket.username;
  verbose('queue/exit, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (team) {
      var isWaitingPlayer = teams.waitPlayer.indexOf(team) > -1;
      var isWaitingConfirm = teams.waitConfirm.indexOf(team) > -1;
      if (isWaitingPlayer || isWaitingConfirm) {
        team.removeUser(username);
        if (isWaitingConfirm) {
          team.move(teams.waitConfirm, teams.waitPlayer);
          updateClient(team, 'wait-player');
        }
      } else {
        error('queue/exit, abnormal state');
      }
    }
  }
}

function confirm() {
  var username = this.socket.username;
  verbose('queue/confirm, username: %s', username);
  if (user[username]) {
    var team = user[username].team;
    if (teams.waitConfirm.indexOf(team) < 0) {
      error('queue/confirm, abnormal state');
      return;
    }

    team.confirmed[username] = true;
    updateClient(team, 'wait-confirm');
    // from now, client knows this confirm makes game start or not.

    var everyoneConfirmed = true;
    for (var u in team.confirmed) {
      verbose('user: %s, confirmed: %s', u, team.confirmed[u]);
      if (!team.confirmed[u]) {
        verbose('queue/confirm, not everyone confirmed yet');
        everyoneConfirmed = false;
        break;
      }
    }

    if (everyoneConfirmed) {
      verbose('queue/confirm, everyone confirmed! start a game');
      team.move(teams.waitConfirm, null);
      // TODO: start game!
    }
  }
}

function disconnect() {
  var username = this.socket.username;
  verbose('disconnect. %s', username);

  var index = queue.indexOf(username);
  if (index > -1) {
    queue.splice(index, 1);
  }

  if (user[username]) {
    var team = user[username].team;
    if (team) {
      var isWaitingPlayer = teams.waitPlayer.indexOf(team) > -1;
      var isWaitingConfirm = teams.waitConfirm.indexOf(team) > -1;
      if (isWaitingPlayer || isWaitingConfirm) {
        team.removeUser(username);
        updateClient(team, 'wait-player');
        if (isWaitingConfirm) {
          verbose('user disconnected was in the team waiting confirm.. this team goes waiting players again ');
          team.move(teams.waitConfirm, teams.waitPlayer);
        }
      }
    }
  }
}

module.exports = generate({
  'queue/join': { name: 'join', function: join },
  'queue/exit': { name: 'exit', function: exit },
  'queue/confirm': { name: 'confirm', function: confirm },
  'queue/dodge': { name: 'dodge', function: exit },
  'disconnect': { name: 'disconnect', function: disconnect }
});
<|endoftext|>


<commit_before>const { Collection } = require('discord.js');
const EventEmitter = require('events');
const { SQLiteHandlerEvents } = require('../util/Constants');
const path = require('path');

let sql;

/**
 * Options to use for the SQLiteHandler.
 * @typedef {Object} SQLiteOptions
 * @prop {string} [tablename='configs'] - Name of the table.
 * @prop {Object} [defaultConfig={}] - Default configuration.
 * @prop {string[]} [json=[]] - Array of keys to parse and stringify as JSON.
 * @prop {string[]|DatabaseInitFunction} [init=[]] - IDs to use to initialize database.
 * If you want to load the database before login, use `SQLiteHandler#load`.
 */

/**
 * Function used to get IDs for database.
 * @typedef {Function} SQLiteInitFunction
 * @param {AkairoClient} client - The client.
 * @returns {string[]}
 */

/** @extends EventEmitter */
class SQLiteHandler extends EventEmitter {
    /**
     * Creates an SQLiteHandler.
     * Tables must have an 'id' column.
     * @deprecated Use SQLiteProvider
     * @param {string} filepath - Path to .sqlite file.
     * @param {SQLiteOptions} [options={}] - Options for the handler.
     */
    constructor(filepath, options = {}) {
        super();

        sql = require('sqlite');

        /**
         * Path to the database file.
         * @type {string}
         */
        this.filepath = path.resolve(filepath);

        /**
         * Name of the table.
         * @type {string}
         */
        this.tableName = options.tableName || 'configs';

        /**
         * Default configuration.
         * @type {Object}
         */
        this.defaultConfig = options.defaultConfig || {};

        /**
         * Keys to parse and stringify as JSON.
         * @type {string[]}
         */
        this.json = options.json || [];

        /**
         * The database.
         * @type {Database}
         */
        this.db = null;

        /**
         * Configurations stored in memory, mapped by ID to configuration.
         * @type {Collection<string, Object>}
         */
        this.memory = new Collection();

        /**
         * Returns an array of IDs.
         * @method
         * @param {AkairoClient} client - Client to use.
         * @returns {string[]}
         */
        this.init = typeof options.init === 'function' ? options.init : () => options.init;

        /**
         * The Akairo client.
         * @readonly
         * @name SQLiteHandler#client
         * @type {AkairoClient}
         */
    }

    /**
     * Array of IDs.
     * Note that this calls the Collection's `keyArray()`.
     * @type {string[]}
     */
    get ids() {
        return this.memory.keyArray();
    }

    /**
     * Array of configs.
     * Note that this calls the Collection's `array()`.
     * Values from here should be desanitized.
     * @type {Object[]}
     */
    get configs() {
        return this.memory.array();
    }

    /**
     * Sanitizes a string by replacing single quotes with two single quotes.
     * Can return a non-string if `json` is set to false.
     * @param {string} input - Input text.
     * @param {boolean} json - Whether to stringify or not.
     * @returns {any}
     */
    sanitize(input, json = false) {
        if (json && typeof input !== 'string') return JSON.stringify(input).replace(/'/g, '\'\'');
        if (typeof input !== 'string') return input;
        return input.replace(/'/g, '\'\'');
    }

    /**
     * Desanitizes a string for use by replacing two single quotes with a single quote.
     * Can return a non-string if `json` is set to true.
     * @param {string} input - Input text.
     * @param {boolean} [json] - Whether to parse or not.
     * @returns {any}
     */
    desanitize(input, json = false) {
        if (json && typeof input === 'string') return JSON.parse(input.replace(/''/g, '\''));
        if (typeof input !== 'string') return input;
        return input.replace(/''/g, '\'');
    }

    /**
     * Opens the database so that it can be used.
     * @returns {Promise<Database>}
     */
    open() {
        return sql.open(this.filepath).then(db => {
            this.db = db;
            return this.db;
        });
    }

    /**
     * Loads handler and database with IDs.
     * Use manually if you want to load database before client is ready.
     * @param {string[]} ids - Array of IDs.
     * @returns {Promise<SQLiteHandler>}
     */
    load(ids) {
        return this.open().then(db => {
            return db.all(`SELECT * FROM "${this.tableName}"`).then(rows => {
                for (const row of rows) {
                    this.memory.set(row.id, row);
                }

                const promises = [];

                for (let id of ids) {
                    id = this.sanitize(id);
                    if (!this.has(id)) promises.push(this.add(id));
                }

                return Promise.all(promises).then(() => {
                    this.emit(SQLiteHandlerEvents.INIT);
                    return this;
                });
            });
        });
    }

    /**
     * Adds into the database.
     * @param {string} id - ID of entry.
     * @returns {Promise<SQLiteHandler>}
     */
    add(id) {
        if (!this.db) return Promise.reject(new Error(`Database with table ${this.tableName} not opened.`));

        id = this.sanitize(id);
        if (this.has(id)) return Promise.reject(new Error(`${id} already exists in ${this.tableName}.`));

        return this.db.run(`INSERT INTO "${this.tableName}" (id) VALUES ('${id}')`).then(() => {
            const config = Object.assign({}, this.defaultConfig);

            config.id = id;
            this.memory.set(id, config);

            this.emit(SQLiteHandlerEvents.ADD, config, false);
            return this;
        });
    }

    /**
     * Adds into the in-memory config.
     * @param {string} id - ID of entry.
     * @returns {SQLiteHandler}
     */
    addMemory(id) {
        id = this.sanitize(id);
        if (this.has(id)) throw new Error(`${id} already exists in ${this.tableName}.`);

        const config = Object.assign({}, this.defaultConfig);

        config.id = id;
        this.memory.set(id, config);

        this.emit(SQLiteHandlerEvents.ADD, config, true);
        return this;
    }

    /**
     * Removes from the database.
     * @param {string} id - ID of entry.
     * @returns {Promise<SQLiteHandler>}
     */
    remove(id) {
        if (!this.db) return Promise.reject(new Error(`Database with table ${this.tableName} not opened.`));

        id = this.sanitize(id);
        if (!this.has(id)) return Promise.reject(new Error(`${id} does not exist in ${this.tableName}.`));

        return this.db.run(`DELETE FROM "${this.tableName}" WHERE id = '${id}'`).then(() => {
            this.memory.delete(id);
            this.emit(SQLiteHandlerEvents.REMOVE, id, false);
            return this;
        });
    }

    /**
     * Removes from the in-memory config.
     * @param {string} id - ID of entry.
     * @returns {SQLiteHandler}
     */
    removeMemory(id) {
        id = this.sanitize(id);
        if (!this.has(id)) throw new Error(`${id} does not exist in ${this.tableName}.`);

        this.memory.delete(id);
        this.emit(SQLiteHandlerEvents.REMOVE, id, true);
        return this;
    }

    /**
     * Checks if ID exists in config.
     * @param {string} id ID of entry.
     * @returns {boolean}
     */
    has(id) {
        id = this.sanitize(id);
        return this.memory.has(id);
    }

    /**
     * Gets configuration for an ID.
     * @param {string} id - ID of entry.
     * @param {string[]} [keys] - Specific keys to get.
     * Leave blank to get the entire configuration.
     * @returns {Object}
     */
    get(id, keys) {
        id = this.sanitize(id);

        const config = this.memory.get(id) || this.defaultConfig;
        const copy = {};

        for (const key of keys || Object.keys(config)) {
            if (config[key] == null) {
                if (this.json.includes(key) && typeof this.defaultConfig[key] === 'string') {
                    copy[key] = JSON.parse(this.defaultConfig[key]);
                    continue;
                }

                copy[key] = this.defaultConfig[key];
                continue;
            }

            copy[key] = this.desanitize(config[key], this.json.includes(key));
        }

        return copy;
    }

    /**
     * Updates the database.
     * @param {string} id - ID of entry.
     * @param {string} key - Key to set.
     * @param {string|number} value - Value to set.
     * @returns {Promise<SQLiteHandler>}
     */
    set(id, key, value) {
        if (!this.db) return Promise.reject(new Error(`Database with table ${this.tableName} not opened.`));

        id = this.sanitize(id);
        key = this.sanitize(key);
        value = this.sanitize(value, this.json.includes(key));

        if (!this.has(id)) return Promise.reject(new Error(`${id} not found in ${this.tableName}.`));

        const config = this.memory.get(id);

        if (!config.hasOwnProperty(key)) return Promise.reject(new Error(`Key ${key} was not found for ${id} in ${this.tableName}.`));
        if (key === 'id') return Promise.reject(new Error('The id key is read-only.'));

        config[key] = value;
        this.memory.set(id, config);

        if (isNaN(value)) {
            value = `'${value}'`;
        }

        return this.db.run(`UPDATE "${this.tableName}" SET ${key} = ${value} WHERE id = '${id}'`).then(() => {
            this.emit(SQLiteHandlerEvents.SET, config, true);
            return this;
        });
    }

    /**
     * Updates the in-memory configuration.
     * @param {string} id - ID of entry.
     * @param {string} key - Key to set.
     * @param {string|number} value - Value to set.
     * @returns {SQLiteHandler}
     */
    setMemory(id, key, value) {
        id = this.sanitize(id);
        key = this.sanitize(key);
        value = this.sanitize(value, this.json.includes(key));

        if (!this.has(id)) throw new Error(`${id} not found in ${this.tableName}.`);

        const config = this.memory.get(id);

        if (!config.hasOwnProperty(key)) throw new Error(`Key ${key} was not found for ${id} in ${this.tableName}.`);
        if (key === 'id') throw new Error('The id key is read-only.');

        config[key] = value;
        this.memory.set(id, config);
        this.emit(SQLiteHandlerEvents.SET, config, false);
        return this;
    }

    /**
     * Saves an in-memory config to the database.
     * @param {string} id - ID to save.
     * @returns {Promise<SQLiteHandler>}
     */
    save(id) {
        if (!this.db) return Promise.reject(new Error(`Database with table ${this.tableName} not opened.`));

        id = this.sanitize(id);
        if (!this.has(id)) return Promise.reject(new Error(`${id} not found in ${this.tableName}.`));

        const config = this.memory.get(id);
        const sets = [];

        for (const key of Object.keys(config)) {
            let value = config[key];

            if (isNaN(value)) {
                value = `'${value}'`;
            }

            sets.push(`${key} = ${value}`);
        }

        return this.db.get(`SELECT count(1) FROM "${this.tableName}" WHERE id = '${id}'`).then(count => {
            let promise;
            let insert = false;

            if (!count['count(1)']) {
                promise = this.db.run(`INSERT INTO "${this.tableName}" (id) VALUES ('${id}')`);
                insert = true;
            } else {
                promise = Promise.resolve();
            }

            return promise.then(() => {
                this.db.run(`UPDATE "${this.tableName}" SET ${sets.join(', ')} WHERE id = '${id}'`);
                return insert;
            });
        }).then(insert => {
            this.emit(SQLiteHandlerEvents.SAVE, config, insert);
            return this;
        });
    }

    /**
     * Saves all in-memory configs to the database.
     * @returns {Promise<SQLiteHandler>}
     */
    saveAll() {
        const promises = [];
        for (const config of this.memory.values()) promises.push(this.save(config.id));

        return Promise.all(promises).then(() => {
            this.emit(SQLiteHandlerEvents.SAVE_ALL);
            return this;
        });
    }
}

module.exports = SQLiteHandler;

/**
 * Emitted when the handler is initalized.
 * @event SQLiteHandler#init
 */

/**
 * Emitted when a config is added.
 * @event SQLiteHandler#add
 * @param {Object} config - Config added.
 * @param {boolean} memory - Whether or not this was done in memory only.
 */

/**
 * Emitted when an ID is removed.
 * @event SQLiteHandler#remove
 * @param {string} id - ID removed.
 * @param {boolean} memory - Whether or not this was done in memory only.
 */

/**
 * Emitted when something was set.
 * @event SQLiteHandler#set
 * @param {Object} config - Config that changed.
 * @param {boolean} memory - Whether or not this was done in memory only.
 */

/**
 * Emitted when a config was saved from memory.
 * @event SQLiteHandler#save
 * @param {Object} config - Config that was saved.
 * @param {boolean} newInsert - Whether or not the config has been in the database before.
 */

/**
 * Emitted when many configs were saved from memory.
 * @event SQLiteHandler#saveAll
 */
<commit_msg>Docs fix<commit_after>const { Collection } = require('discord.js');
const EventEmitter = require('events');
const { SQLiteHandlerEvents } = require('../util/Constants');
const path = require('path');

let sql;

/**
 * Options to use for the SQLiteHandler.
 * @typedef {Object} SQLiteOptions
 * @prop {string} [tablename='configs'] - Name of the table.
 * @prop {Object} [defaultConfig={}] - Default configuration.
 * @prop {string[]} [json=[]] - Array of keys to parse and stringify as JSON.
 * @prop {string[]|SQLiteInitFunction} [init=[]] - IDs to use to initialize database.
 * If you want to load the database before login, use `SQLiteHandler#load`.
 */

/**
 * Function used to get IDs for database.
 * @typedef {Function} SQLiteInitFunction
 * @param {AkairoClient} client - The client.
 * @returns {string[]}
 */

/** @extends EventEmitter */
class SQLiteHandler extends EventEmitter {
    /**
     * Creates an SQLiteHandler.
     * Tables must have an 'id' column.
     * @deprecated Use SQLiteProvider
     * @param {string} filepath - Path to .sqlite file.
     * @param {SQLiteOptions} [options={}] - Options for the handler.
     */
    constructor(filepath, options = {}) {
        super();

        sql = require('sqlite');

        /**
         * Path to the database file.
         * @type {string}
         */
        this.filepath = path.resolve(filepath);

        /**
         * Name of the table.
         * @type {string}
         */
        this.tableName = options.tableName || 'configs';

        /**
         * Default configuration.
         * @type {Object}
         */
        this.defaultConfig = options.defaultConfig || {};

        /**
         * Keys to parse and stringify as JSON.
         * @type {string[]}
         */
        this.json = options.json || [];

        /**
         * The database.
         * @type {Database}
         */
        this.db = null;

        /**
         * Configurations stored in memory, mapped by ID to configuration.
         * @type {Collection<string, Object>}
         */
        this.memory = new Collection();

        /**
         * Returns an array of IDs.
         * @method
         * @param {AkairoClient} client - Client to use.
         * @returns {string[]}
         */
        this.init = typeof options.init === 'function' ? options.init : () => options.init;

        /**
         * The Akairo client.
         * @readonly
         * @name SQLiteHandler#client
         * @type {AkairoClient}
         */
    }

    /**
     * Array of IDs.
     * Note that this calls the Collection's `keyArray()`.
     * @type {string[]}
     */
    get ids() {
        return this.memory.keyArray();
    }

    /**
     * Array of configs.
     * Note that this calls the Collection's `array()`.
     * Values from here should be desanitized.
     * @type {Object[]}
     */
    get configs() {
        return this.memory.array();
    }

    /**
     * Sanitizes a string by replacing single quotes with two single quotes.
     * Can return a non-string if `json` is set to false.
     * @param {string} input - Input text.
     * @param {boolean} json - Whether to stringify or not.
     * @returns {any}
     */
    sanitize(input, json = false) {
        if (json && typeof input !== 'string') return JSON.stringify(input).replace(/'/g, '\'\'');
        if (typeof input !== 'string') return input;
        return input.replace(/'/g, '\'\'');
    }

    /**
     * Desanitizes a string for use by replacing two single quotes with a single quote.
     * Can return a non-string if `json` is set to true.
     * @param {string} input - Input text.
     * @param {boolean} [json] - Whether to parse or not.
     * @returns {any}
     */
    desanitize(input, json = false) {
        if (json && typeof input === 'string') return JSON.parse(input.replace(/''/g, '\''));
        if (typeof input !== 'string') return input;
        return input.replace(/''/g, '\'');
    }

    /**
     * Opens the database so that it can be used.
     * @returns {Promise<Database>}
     */
    open() {
        return sql.open(this.filepath).then(db => {
            this.db = db;
            return this.db;
        });
    }

    /**
     * Loads handler and database with IDs.
     * Use manually if you want to load database before client is ready.
     * @param {string[]} ids - Array of IDs.
     * @returns {Promise<SQLiteHandler>}
     */
    load(ids) {
        return this.open().then(db => {
            return db.all(`SELECT * FROM "${this.tableName}"`).then(rows => {
                for (const row of rows) {
                    this.memory.set(row.id, row);
                }

                const promises = [];

                for (let id of ids) {
                    id = this.sanitize(id);
                    if (!this.has(id)) promises.push(this.add(id));
                }

                return Promise.all(promises).then(() => {
                    this.emit(SQLiteHandlerEvents.INIT);
                    return this;
                });
            });
        });
    }

    /**
     * Adds into the database.
     * @param {string} id - ID of entry.
     * @returns {Promise<SQLiteHandler>}
     */
    add(id) {
        if (!this.db) return Promise.reject(new Error(`Database with table ${this.tableName} not opened.`));

        id = this.sanitize(id);
        if (this.has(id)) return Promise.reject(new Error(`${id} already exists in ${this.tableName}.`));

        return this.db.run(`INSERT INTO "${this.tableName}" (id) VALUES ('${id}')`).then(() => {
            const config = Object.assign({}, this.defaultConfig);

            config.id = id;
            this.memory.set(id, config);

            this.emit(SQLiteHandlerEvents.ADD, config, false);
            return this;
        });
    }

    /**
     * Adds into the in-memory config.
     * @param {string} id - ID of entry.
     * @returns {SQLiteHandler}
     */
    addMemory(id) {
        id = this.sanitize(id);
        if (this.has(id)) throw new Error(`${id} already exists in ${this.tableName}.`);

        const config = Object.assign({}, this.defaultConfig);

        config.id = id;
        this.memory.set(id, config);

        this.emit(SQLiteHandlerEvents.ADD, config, true);
        return this;
    }

    /**
     * Removes from the database.
     * @param {string} id - ID of entry.
     * @returns {Promise<SQLiteHandler>}
     */
    remove(id) {
        if (!this.db) return Promise.reject(new Error(`Database with table ${this.tableName} not opened.`));

        id = this.sanitize(id);
        if (!this.has(id)) return Promise.reject(new Error(`${id} does not exist in ${this.tableName}.`));

        return this.db.run(`DELETE FROM "${this.tableName}" WHERE id = '${id}'`).then(() => {
            this.memory.delete(id);
            this.emit(SQLiteHandlerEvents.REMOVE, id, false);
            return this;
        });
    }

    /**
     * Removes from the in-memory config.
     * @param {string} id - ID of entry.
     * @returns {SQLiteHandler}
     */
    removeMemory(id) {
        id = this.sanitize(id);
        if (!this.has(id)) throw new Error(`${id} does not exist in ${this.tableName}.`);

        this.memory.delete(id);
        this.emit(SQLiteHandlerEvents.REMOVE, id, true);
        return this;
    }

    /**
     * Checks if ID exists in config.
     * @param {string} id ID of entry.
     * @returns {boolean}
     */
    has(id) {
        id = this.sanitize(id);
        return this.memory.has(id);
    }

    /**
     * Gets configuration for an ID.
     * @param {string} id - ID of entry.
     * @param {string[]} [keys] - Specific keys to get.
     * Leave blank to get the entire configuration.
     * @returns {Object}
     */
    get(id, keys) {
        id = this.sanitize(id);

        const config = this.memory.get(id) || this.defaultConfig;
        const copy = {};

        for (const key of keys || Object.keys(config)) {
            if (config[key] == null) {
                if (this.json.includes(key) && typeof this.defaultConfig[key] === 'string') {
                    copy[key] = JSON.parse(this.defaultConfig[key]);
                    continue;
                }

                copy[key] = this.defaultConfig[key];
                continue;
            }

            copy[key] = this.desanitize(config[key], this.json.includes(key));
        }

        return copy;
    }

    /**
     * Updates the database.
     * @param {string} id - ID of entry.
     * @param {string} key - Key to set.
     * @param {string|number} value - Value to set.
     * @returns {Promise<SQLiteHandler>}
     */
    set(id, key, value) {
        if (!this.db) return Promise.reject(new Error(`Database with table ${this.tableName} not opened.`));

        id = this.sanitize(id);
        key = this.sanitize(key);
        value = this.sanitize(value, this.json.includes(key));

        if (!this.has(id)) return Promise.reject(new Error(`${id} not found in ${this.tableName}.`));

        const config = this.memory.get(id);

        if (!config.hasOwnProperty(key)) return Promise.reject(new Error(`Key ${key} was not found for ${id} in ${this.tableName}.`));
        if (key === 'id') return Promise.reject(new Error('The id key is read-only.'));

        config[key] = value;
        this.memory.set(id, config);

        if (isNaN(value)) {
            value = `'${value}'`;
        }

        return this.db.run(`UPDATE "${this.tableName}" SET ${key} = ${value} WHERE id = '${id}'`).then(() => {
            this.emit(SQLiteHandlerEvents.SET, config, true);
            return this;
        });
    }

    /**
     * Updates the in-memory configuration.
     * @param {string} id - ID of entry.
     * @param {string} key - Key to set.
     * @param {string|number} value - Value to set.
     * @returns {SQLiteHandler}
     */
    setMemory(id, key, value) {
        id = this.sanitize(id);
        key = this.sanitize(key);
        value = this.sanitize(value, this.json.includes(key));

        if (!this.has(id)) throw new Error(`${id} not found in ${this.tableName}.`);

        const config = this.memory.get(id);

        if (!config.hasOwnProperty(key)) throw new Error(`Key ${key} was not found for ${id} in ${this.tableName}.`);
        if (key === 'id') throw new Error('The id key is read-only.');

        config[key] = value;
        this.memory.set(id, config);
        this.emit(SQLiteHandlerEvents.SET, config, false);
        return this;
    }

    /**
     * Saves an in-memory config to the database.
     * @param {string} id - ID to save.
     * @returns {Promise<SQLiteHandler>}
     */
    save(id) {
        if (!this.db) return Promise.reject(new Error(`Database with table ${this.tableName} not opened.`));

        id = this.sanitize(id);
        if (!this.has(id)) return Promise.reject(new Error(`${id} not found in ${this.tableName}.`));

        const config = this.memory.get(id);
        const sets = [];

        for (const key of Object.keys(config)) {
            let value = config[key];

            if (isNaN(value)) {
                value = `'${value}'`;
            }

            sets.push(`${key} = ${value}`);
        }

        return this.db.get(`SELECT count(1) FROM "${this.tableName}" WHERE id = '${id}'`).then(count => {
            let promise;
            let insert = false;

            if (!count['count(1)']) {
                promise = this.db.run(`INSERT INTO "${this.tableName}" (id) VALUES ('${id}')`);
                insert = true;
            } else {
                promise = Promise.resolve();
            }

            return promise.then(() => {
                this.db.run(`UPDATE "${this.tableName}" SET ${sets.join(', ')} WHERE id = '${id}'`);
                return insert;
            });
        }).then(insert => {
            this.emit(SQLiteHandlerEvents.SAVE, config, insert);
            return this;
        });
    }

    /**
     * Saves all in-memory configs to the database.
     * @returns {Promise<SQLiteHandler>}
     */
    saveAll() {
        const promises = [];
        for (const config of this.memory.values()) promises.push(this.save(config.id));

        return Promise.all(promises).then(() => {
            this.emit(SQLiteHandlerEvents.SAVE_ALL);
            return this;
        });
    }
}

module.exports = SQLiteHandler;

/**
 * Emitted when the handler is initalized.
 * @event SQLiteHandler#init
 */

/**
 * Emitted when a config is added.
 * @event SQLiteHandler#add
 * @param {Object} config - Config added.
 * @param {boolean} memory - Whether or not this was done in memory only.
 */

/**
 * Emitted when an ID is removed.
 * @event SQLiteHandler#remove
 * @param {string} id - ID removed.
 * @param {boolean} memory - Whether or not this was done in memory only.
 */

/**
 * Emitted when something was set.
 * @event SQLiteHandler#set
 * @param {Object} config - Config that changed.
 * @param {boolean} memory - Whether or not this was done in memory only.
 */

/**
 * Emitted when a config was saved from memory.
 * @event SQLiteHandler#save
 * @param {Object} config - Config that was saved.
 * @param {boolean} newInsert - Whether or not the config has been in the database before.
 */

/**
 * Emitted when many configs were saved from memory.
 * @event SQLiteHandler#saveAll
 */
<|endoftext|>


<commit_before>/**
 * @file Validation
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @private
 */

import { Vector3, Color } from "../../lib/three.es6.js";

import { Debug, Log } from "../globals.js";
import { defaults } from "../utils.js";
import { ClashPicker } from "../utils/picker.js";
import { uniformArray3 } from "../math/array-utils.js";
import { guessElement } from "../structure/structure-utils.js";

function getSele( a, atomname, useAltcode ){
    const icode = a.icode.value;
    const chain = a.chain.value;
    const altcode = a.altcode.value;
    let sele = a.resnum.value;
    if( icode.trim() ) sele += "^" + icode;
    if( chain.trim() ) sele += ":" + chain;
    if( atomname ) sele += "." + atomname;
    if( useAltcode && altcode.trim() ) sele += "%" + altcode;
    sele += "/" + ( parseInt( a.model.value ) - 1 );
    return sele;
}

function setBitDict( dict, key, bit ){
    if( dict[ key ] === undefined ){
        dict[ key ] = bit;
    }else{
        dict[ key ] |= bit;
    }
}

function hasAttrValue( attr, value ){
    return attr !== undefined && attr.value === value;
}

function getAtomSele( ap ){
    const icode = ap.inscode;
    const chain = ap.chainname;
    const atomname = ap.atomname;
    const altcode = ap.altloc;
    let sele = ap.resno;
    if( icode ) sele += "^" + icode;
    if( chain ) sele += ":" + chain;
    if( atomname ) sele += "." + atomname;
    if( altcode ) sele += "%" + altcode;
    sele += "/" + ap.modelIndex;
    return sele;
}

function getProblemCount( clashDict, g, ga ){
    let geoProblemCount = 0;

    const clashes = g.getElementsByTagName( "clash" );
    for( let j = 0, jl = clashes.length; j < jl; ++j ){
        if( clashDict[ clashes[ j ].attributes.cid.value ] ){
            geoProblemCount += 1;
            break;
        }
    }

    const angleOutliers = g.getElementsByTagName( "angle-outlier" );
    if( angleOutliers.length > 0 ){
        geoProblemCount += 1;
    }

    const bondOutliers = g.getElementsByTagName( "bond-outlier" );
    if( bondOutliers.length > 0 ){
        geoProblemCount += 1;
    }

    const planeOutliers = g.getElementsByTagName( "plane-outlier" );
    if( planeOutliers.length > 0 ){
        geoProblemCount += 1;
    }

    if( hasAttrValue( ga.rota, "OUTLIER" ) ){
        geoProblemCount += 1;
    }

    if( hasAttrValue( ga.rama, "OUTLIER" ) ){
        geoProblemCount += 1;
    }

    if( hasAttrValue( ga.RNApucker, "outlier" ) ){
        geoProblemCount += 1;
    }

    return geoProblemCount;
}

class Validation{

    constructor( name, path ){

        this.name = name;
        this.path = path;

        this.rsrzDict = {};
        this.rsccDict = {};
        this.clashDict = {};
        this.clashArray = [];
        this.geoDict = {};
        this.geoAtomDict = {};
        this.atomDict = {};
        this.clashSele = "NONE";

    }

    get type (){ return "validation"; }

    fromXml( xml ){

        if( Debug ) Log.time( "Validation.fromXml" );

        const rsrzDict = this.rsrzDict;
        const rsccDict = this.rsccDict;
        const clashDict = this.clashDict;
        const clashArray = this.clashArray;
        const geoDict = this.geoDict;
        const geoAtomDict = this.geoAtomDict;
        const atomDict = this.atomDict;

        const groups = xml.getElementsByTagName( "ModelledSubgroup" );

        const _clashDict = {};
        const clashList = [];

        if( Debug ) Log.time( "Validation.fromXml#clashDict" );

        for( let i = 0, il = groups.length; i < il; ++i ){

            const g = groups[ i ];
            const ga = g.attributes;

            const sele = getSele( ga );
            if( ga.rsrz !== undefined ){
                rsrzDict[ sele ] = parseFloat( ga.rsrz.value );
            }
            if( ga.rscc !== undefined ){
                rsccDict[ sele ] = parseFloat( ga.rscc.value );
            }
            ga.sele = sele;

            const clashes = g.getElementsByTagName( "clash" );

            for( let j = 0, jl = clashes.length; j < jl; ++j ){

                const ca = clashes[ j ].attributes;
                const atom = ca.atom.value;

                if( guessElement( atom ) !== "H" ){

                    const cid = ca.cid.value;
                    const atomSele = getSele( ga, atom, true );
                    atomDict[ atomSele ] = true;

                    if( _clashDict[ cid ] === undefined ){
                        _clashDict[ cid ] = {
                            sele1: atomSele,
                            res1: sele
                        };
                    }else{
                        const c = _clashDict[ cid ];
                        if( c.res1 !== sele ){
                            c.sele2 = atomSele;
                            c.res2 = sele;
                            clashList.push( c.res1, sele );
                            clashDict[ cid ] = c;
                            clashArray.push( c );
                        }
                    }

                }

            }

        }

        if( Debug ) Log.timeEnd( "Validation.fromXml#clashDict" );

        for( let i = 0, il = groups.length; i < il; ++i ){

            const g = groups[ i ];
            const ga = g.attributes;

            const sele = ga.sele;
            const isPolymer = ga.seq.value !== ".";

            if( isPolymer ){

                const geoProblemCount = getProblemCount( clashDict, g, ga );
                if( geoProblemCount > 0 ){
                    geoDict[ sele ] = geoProblemCount;
                }

            }else{

                const clashes = g.getElementsByTagName( "clash" );
                const mogBondOutliers = g.getElementsByTagName( "mog-bond-outlier" );
                const mogAngleOutliers = g.getElementsByTagName( "mog-angle-outlier" );

                if( mogBondOutliers.length > 0 || mogAngleOutliers.length > 0 || clashes.length > 0 ){

                    const atomDict = {};
                    geoAtomDict[ sele ] = atomDict;

                    for( let j = 0, jl = clashes.length; j < jl; ++j ){
                        const ca = clashes[ j ].attributes;
                        if( clashDict[ ca.cid.value ] ){
                            setBitDict( atomDict, ca.atom.value, 1 );
                        }
                    }

                    for( let j = 0, jl = mogBondOutliers.length; j < jl; ++j ){
                        const mbo = mogBondOutliers[ j ].attributes;
                        mbo.atoms.value.split( "," ).forEach( function( atomname ){
                            setBitDict( atomDict, atomname, 2 );
                        } );
                    }

                    for( let j = 0, jl = mogAngleOutliers.length; j < jl; ++j ){
                        const mao = mogAngleOutliers[ j ].attributes;
                        mao.atoms.value.split( "," ).forEach( function( atomname ){
                            setBitDict( atomDict, atomname, 4 );
                        } );
                    }

                }

            }

        }

        this.clashSele = clashList.length ? clashList.join( " OR " ) : "NONE";

        if( Debug ) Log.timeEnd( "Validation.fromXml" );

    }

    getClashData( params ){

        if( Debug ) Log.time( "Validation.getClashData" );

        const p = params || {};

        const s = p.structure;
        const as = s.atomSet;
        const c = new Color( defaults( p.color, "#f0027f" ) );

        const ap1 = s.getAtomProxy();
        const ap2 = s.getAtomProxy();
        const vDir = new Vector3();
        const vPos1 = new Vector3();
        const vPos2 = new Vector3();

        const clashArray = this.clashArray;
        const n = clashArray.length;

        const position1 = new Float32Array( n * 3 );
        const position2 = new Float32Array( n * 3 );
        const color = uniformArray3( n, c.r, c.g, c.b );
        const radius = new Float32Array( n );
        const picking = new Float32Array( n );

        if( Debug ) Log.time( "Validation.getClashData#atomDict" );

        const atomDict = this.atomDict;

        s.eachAtom( function( ap ){
            const sele = getAtomSele( ap );
            if( atomDict[ sele ] === true ){
                atomDict[ sele ] = ap.index;
            }
        } );

        if( Debug ) Log.timeEnd( "Validation.getClashData#atomDict" );

        let i = 0;

        clashArray.forEach( function( c, idx ){

            ap1.index = atomDict[ c.sele1 ];
            ap2.index = atomDict[ c.sele2 ];

            if( !as.has( ap1.index ) || !as.has( ap2.index ) ||
                ap1.index === undefined || ap2.index === undefined ) return;

            vDir.subVectors( ap2, ap1 ).setLength( ap1.vdw );
            vPos1.copy( ap1 ).add( vDir );

            vDir.subVectors( ap1, ap2 ).setLength( ap2.vdw );
            vPos2.copy( ap2 ).add( vDir );

            const dHalf = ap1.distanceTo( ap2 ) / 2;
            const r1 = Math.sqrt( ap1.vdw * ap1.vdw - dHalf * dHalf );
            const r2 = Math.sqrt( ap2.vdw * ap2.vdw - dHalf * dHalf );

            vPos1.toArray( position1, i * 3 );
            vPos2.toArray( position2, i * 3 );
            radius[ i ] = ( r1 + r2 ) / 2;
            picking[ i ] = idx;

            ++i;

        } );

        if( Debug ) Log.timeEnd( "Validation.getClashData" );

        return {
            position1: position1.subarray( 0, i * 3 ),
            position2: position2.subarray( 0, i * 3 ),
            color: color.subarray( 0, i * 3 ),
            color2: color.subarray( 0, i * 3 ),
            radius: radius.subarray( 0, i ),
            picking: new ClashPicker( picking.subarray( 0, i ), this, s )
        };

    }

}

export default Validation;
<commit_msg>fixed bitarray related error in validation object<commit_after>/**
 * @file Validation
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @private
 */

import { Vector3, Color } from "../../lib/three.es6.js";

import { Debug, Log } from "../globals.js";
import { defaults } from "../utils.js";
import { ClashPicker } from "../utils/picker.js";
import { uniformArray3 } from "../math/array-utils.js";
import { guessElement } from "../structure/structure-utils.js";

function getSele( a, atomname, useAltcode ){
    const icode = a.icode.value;
    const chain = a.chain.value;
    const altcode = a.altcode.value;
    let sele = a.resnum.value;
    if( icode.trim() ) sele += "^" + icode;
    if( chain.trim() ) sele += ":" + chain;
    if( atomname ) sele += "." + atomname;
    if( useAltcode && altcode.trim() ) sele += "%" + altcode;
    sele += "/" + ( parseInt( a.model.value ) - 1 );
    return sele;
}

function setBitDict( dict, key, bit ){
    if( dict[ key ] === undefined ){
        dict[ key ] = bit;
    }else{
        dict[ key ] |= bit;
    }
}

function hasAttrValue( attr, value ){
    return attr !== undefined && attr.value === value;
}

function getAtomSele( ap ){
    const icode = ap.inscode;
    const chain = ap.chainname;
    const atomname = ap.atomname;
    const altcode = ap.altloc;
    let sele = ap.resno;
    if( icode ) sele += "^" + icode;
    if( chain ) sele += ":" + chain;
    if( atomname ) sele += "." + atomname;
    if( altcode ) sele += "%" + altcode;
    sele += "/" + ap.modelIndex;
    return sele;
}

function getProblemCount( clashDict, g, ga ){
    let geoProblemCount = 0;

    const clashes = g.getElementsByTagName( "clash" );
    for( let j = 0, jl = clashes.length; j < jl; ++j ){
        if( clashDict[ clashes[ j ].attributes.cid.value ] ){
            geoProblemCount += 1;
            break;
        }
    }

    const angleOutliers = g.getElementsByTagName( "angle-outlier" );
    if( angleOutliers.length > 0 ){
        geoProblemCount += 1;
    }

    const bondOutliers = g.getElementsByTagName( "bond-outlier" );
    if( bondOutliers.length > 0 ){
        geoProblemCount += 1;
    }

    const planeOutliers = g.getElementsByTagName( "plane-outlier" );
    if( planeOutliers.length > 0 ){
        geoProblemCount += 1;
    }

    if( hasAttrValue( ga.rota, "OUTLIER" ) ){
        geoProblemCount += 1;
    }

    if( hasAttrValue( ga.rama, "OUTLIER" ) ){
        geoProblemCount += 1;
    }

    if( hasAttrValue( ga.RNApucker, "outlier" ) ){
        geoProblemCount += 1;
    }

    return geoProblemCount;
}

class Validation{

    constructor( name, path ){

        this.name = name;
        this.path = path;

        this.rsrzDict = {};
        this.rsccDict = {};
        this.clashDict = {};
        this.clashArray = [];
        this.geoDict = {};
        this.geoAtomDict = {};
        this.atomDict = {};
        this.clashSele = "NONE";

    }

    get type (){ return "validation"; }

    fromXml( xml ){

        if( Debug ) Log.time( "Validation.fromXml" );

        const rsrzDict = this.rsrzDict;
        const rsccDict = this.rsccDict;
        const clashDict = this.clashDict;
        const clashArray = this.clashArray;
        const geoDict = this.geoDict;
        const geoAtomDict = this.geoAtomDict;
        const atomDict = this.atomDict;

        const groups = xml.getElementsByTagName( "ModelledSubgroup" );

        const _clashDict = {};
        const clashList = [];

        if( Debug ) Log.time( "Validation.fromXml#clashDict" );

        for( let i = 0, il = groups.length; i < il; ++i ){

            const g = groups[ i ];
            const ga = g.attributes;

            const sele = getSele( ga );
            if( ga.rsrz !== undefined ){
                rsrzDict[ sele ] = parseFloat( ga.rsrz.value );
            }
            if( ga.rscc !== undefined ){
                rsccDict[ sele ] = parseFloat( ga.rscc.value );
            }
            ga.sele = sele;

            const clashes = g.getElementsByTagName( "clash" );

            for( let j = 0, jl = clashes.length; j < jl; ++j ){

                const ca = clashes[ j ].attributes;
                const atom = ca.atom.value;

                if( guessElement( atom ) !== "H" ){

                    const cid = ca.cid.value;
                    const atomSele = getSele( ga, atom, true );
                    atomDict[ atomSele ] = true;

                    if( _clashDict[ cid ] === undefined ){
                        _clashDict[ cid ] = {
                            sele1: atomSele,
                            res1: sele
                        };
                    }else{
                        const c = _clashDict[ cid ];
                        if( c.res1 !== sele ){
                            c.sele2 = atomSele;
                            c.res2 = sele;
                            clashList.push( c.res1, sele );
                            clashDict[ cid ] = c;
                            clashArray.push( c );
                        }
                    }

                }

            }

        }

        if( Debug ) Log.timeEnd( "Validation.fromXml#clashDict" );

        for( let i = 0, il = groups.length; i < il; ++i ){

            const g = groups[ i ];
            const ga = g.attributes;

            const sele = ga.sele;
            const isPolymer = ga.seq.value !== ".";

            if( isPolymer ){

                const geoProblemCount = getProblemCount( clashDict, g, ga );
                if( geoProblemCount > 0 ){
                    geoDict[ sele ] = geoProblemCount;
                }

            }else{

                const clashes = g.getElementsByTagName( "clash" );
                const mogBondOutliers = g.getElementsByTagName( "mog-bond-outlier" );
                const mogAngleOutliers = g.getElementsByTagName( "mog-angle-outlier" );

                if( mogBondOutliers.length > 0 || mogAngleOutliers.length > 0 || clashes.length > 0 ){

                    const atomDict = {};
                    geoAtomDict[ sele ] = atomDict;

                    for( let j = 0, jl = clashes.length; j < jl; ++j ){
                        const ca = clashes[ j ].attributes;
                        if( clashDict[ ca.cid.value ] ){
                            setBitDict( atomDict, ca.atom.value, 1 );
                        }
                    }

                    for( let j = 0, jl = mogBondOutliers.length; j < jl; ++j ){
                        const mbo = mogBondOutliers[ j ].attributes;
                        mbo.atoms.value.split( "," ).forEach( function( atomname ){
                            setBitDict( atomDict, atomname, 2 );
                        } );
                    }

                    for( let j = 0, jl = mogAngleOutliers.length; j < jl; ++j ){
                        const mao = mogAngleOutliers[ j ].attributes;
                        mao.atoms.value.split( "," ).forEach( function( atomname ){
                            setBitDict( atomDict, atomname, 4 );
                        } );
                    }

                }

            }

        }

        this.clashSele = clashList.length ? clashList.join( " OR " ) : "NONE";

        if( Debug ) Log.timeEnd( "Validation.fromXml" );

    }

    getClashData( params ){

        if( Debug ) Log.time( "Validation.getClashData" );

        const p = params || {};

        const s = p.structure;
        const atomSet = s.atomSet;
        const c = new Color( defaults( p.color, "#f0027f" ) );

        const ap1 = s.getAtomProxy();
        const ap2 = s.getAtomProxy();
        const vDir = new Vector3();
        const vPos1 = new Vector3();
        const vPos2 = new Vector3();

        const clashArray = this.clashArray;
        const n = clashArray.length;

        const position1 = new Float32Array( n * 3 );
        const position2 = new Float32Array( n * 3 );
        const color = uniformArray3( n, c.r, c.g, c.b );
        const radius = new Float32Array( n );
        const picking = new Float32Array( n );

        if( Debug ) Log.time( "Validation.getClashData#atomDict" );

        const atomDict = this.atomDict;

        s.eachAtom( function( ap ){
            const sele = getAtomSele( ap );
            if( atomDict[ sele ] === true ){
                atomDict[ sele ] = ap.index;
            }
        } );

        if( Debug ) Log.timeEnd( "Validation.getClashData#atomDict" );

        let i = 0;

        clashArray.forEach( function( c, idx ){

            ap1.index = atomDict[ c.sele1 ];
            ap2.index = atomDict[ c.sele2 ];

            if( ap1.index === undefined || ap2.index === undefined ||
                !atomSet.isSet( ap1.index, ap2.index ) ) return;

            vDir.subVectors( ap2, ap1 ).setLength( ap1.vdw );
            vPos1.copy( ap1 ).add( vDir );

            vDir.subVectors( ap1, ap2 ).setLength( ap2.vdw );
            vPos2.copy( ap2 ).add( vDir );

            const dHalf = ap1.distanceTo( ap2 ) / 2;
            const r1 = Math.sqrt( ap1.vdw * ap1.vdw - dHalf * dHalf );
            const r2 = Math.sqrt( ap2.vdw * ap2.vdw - dHalf * dHalf );

            vPos1.toArray( position1, i * 3 );
            vPos2.toArray( position2, i * 3 );
            radius[ i ] = ( r1 + r2 ) / 2;
            picking[ i ] = idx;

            ++i;

        } );

        if( Debug ) Log.timeEnd( "Validation.getClashData" );

        return {
            position1: position1.subarray( 0, i * 3 ),
            position2: position2.subarray( 0, i * 3 ),
            color: color.subarray( 0, i * 3 ),
            color2: color.subarray( 0, i * 3 ),
            radius: radius.subarray( 0, i ),
            picking: new ClashPicker( picking.subarray( 0, i ), this, s )
        };

    }

}

export default Validation;
<|endoftext|>


<commit_before>/*
 * Copyright [1999-2014] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

(function($) {
  var elements = [];
  var coords = [];

  var wiggle = 1000;
  var miles_away = 50000;

  function still_alive(el) {
    var p = el.parents();
    if(!p.length) { return false; }
    if($(p[p.length-1]).prop("tagName") != "HTML") { return false; }
    return true;
  }

  function refresh_element(i,el) {
    var etop = el.offset().top;
    var ebot = etop +el.outerHeight(true);
    coords[i] = [etop-wiggle,ebot+wiggle];
  }

  function refresh() {
    for(var i=0;i<elements.length;i++) {
      refresh_element(i,elements[i]);
      if(!still_alive(elements[i])) {
        elements.splice(i,1);
        coords.splice(i,1);
        i--;
      }
    }
  }

  function awaken(el) {
    el.trigger('awaken');
    el.addClass('__awake');
    refresh();
  }

  function sleepen(el) {
    el.trigger('sleepen');
    el.removeClass('__awake');
    refresh();
  }

  function check() {
    var wtop = $(window).scrollTop();
    var wbot = wtop + $(window).height();
    var height = $(document).height();
    var i;
    // Awaken some elements?
    while(true) {
      for(i=0;i<elements.length;i++) {
        if(elements[i].hasClass('__awake')) { continue; }
        if(wbot>coords[i][0] && wtop<coords[i][1]) {
          awaken(elements[i]);
          break; // from the top
        }
      }
      break; // all done
    }
    // Send some to sleep?
    for(i=0;i<elements.length;i++) {
      if(coords[i][0] < miles_away/2 ||
         coords[i][1] > height - miles_away/2) {
        elements[i].removeClass('__miles_away');
      } else if(coords[i][0]-wtop > miles_away ||
                wbot-coords[i][1] > miles_away) {
        if(!elements[i].hasClass('__miles_away')) {
          elements[i].addClass('__miles_away');
          if(elements[i].hasClass('__awake')) {
            sleepen(elements[i]);
          }
        }
      } else {
        elements[i].removeClass('__miles_away');
      }
    }
  }

  function eager() {
    var i;
    refresh();
    check();
    var wtop = $(window).scrollTop();
    var wbot = wtop + $(window).height();
    var height = $(document).height();
    var targets = [null,null,null,null];
    for(i=0;i<elements.length;i++) {
      var prio = 3;
      if(coords[i][1] > height - miles_away/2) { prio = 2; }
      if(coords[i][0] < miles_away && !targets[1]) { prio = 1; }
      if(wbot>coords[i][0] && wtop<coords[i][1]) { prio = 0; }
      if(elements[i].hasClass('__awake')) { continue; }
      if(elements[i].hasClass('__miles_away')) { continue; }
      targets[prio] = elements[i];
    }
    for(i=0;i<targets.length;i++) {
      if(targets[i]) { awaken(targets[i]); break; }
    }
  }

  var check_soon = $.debounce(check,500);
  $(window).scroll(function() { check_soon(); });

  $.fn.lazy = function(arg) {
    this.each(function(i,el) {
      var $this = $(this);
      if(!$this.hasClass('__lazy')) {
        elements.push($this);
        refresh_element(elements.length-1,$this);
        $this.addClass('__lazy');
      } else {
        $this.removeClass('__awake');
      }
    });
  };

  var refresh_soon = $.debounce(refresh,500);
  $.lazy = function(arg,val) {
    if(arg == 'refresh') {
      refresh_soon();
    } else if(arg == 'periodic') {
      setInterval(function() { refresh_soon(); check_soon(); },5000);
    } else if(arg == 'eager') {
      eager();
    }
  };

})(jQuery);
<commit_msg>Less lazy laziness.<commit_after>/*
 * Copyright [1999-2014] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

(function($) {
  var elements = [];
  var coords = [];

  var wiggle = 10000;
  var miles_away = 100000;

  function still_alive(el) {
    var p = el.parents();
    if(!p.length) { return false; }
    if($(p[p.length-1]).prop("tagName") != "HTML") { return false; }
    return true;
  }

  function refresh_element(i,el) {
    var etop = el.offset().top;
    var ebot = etop +el.outerHeight(true);
    coords[i] = [etop-wiggle,ebot+wiggle];
  }

  function refresh() {
    for(var i=0;i<elements.length;i++) {
      refresh_element(i,elements[i]);
      if(!still_alive(elements[i])) {
        elements.splice(i,1);
        coords.splice(i,1);
        i--;
      }
    }
  }

  function awaken(el) {
    el.trigger('awaken');
    el.addClass('__awake');
    refresh();
  }

  function sleepen(el) {
    el.trigger('sleepen');
    el.removeClass('__awake');
    refresh();
  }

  function check() {
    var wtop = $(window).scrollTop();
    var wbot = wtop + $(window).height();
    var height = $(document).height();
    var i;
    // Awaken some elements?
    while(true) {
      for(i=0;i<elements.length;i++) {
        if(elements[i].hasClass('__awake')) { continue; }
        if(wbot>coords[i][0] && wtop<coords[i][1]) {
          awaken(elements[i]);
          break; // from the top
        }
      }
      break; // all done
    }
    // Send some to sleep?
    for(i=0;i<elements.length;i++) {
      if(coords[i][0] < miles_away/2 ||
         coords[i][1] > height - miles_away/2) {
        elements[i].removeClass('__miles_away');
      } else if(coords[i][0]-wtop > miles_away ||
                wbot-coords[i][1] > miles_away) {
        if(!elements[i].hasClass('__miles_away')) {
          elements[i].addClass('__miles_away');
          if(elements[i].hasClass('__awake')) {
            sleepen(elements[i]);
          }
        }
      } else {
        elements[i].removeClass('__miles_away');
      }
    }
  }

  function eager() {
    var i;
    refresh();
    check();
    var wtop = $(window).scrollTop();
    var wbot = wtop + $(window).height();
    var height = $(document).height();
    var targets = [null,null,null,null];
    for(i=0;i<elements.length;i++) {
      var prio = 3;
      if(coords[i][1] > height - miles_away/2) { prio = 2; }
      if(coords[i][0] < miles_away && !targets[1]) { prio = 1; }
      if(wbot>coords[i][0] && wtop<coords[i][1]) { prio = 0; }
      if(elements[i].hasClass('__awake')) { continue; }
      if(elements[i].hasClass('__miles_away')) { continue; }
      targets[prio] = elements[i];
    }
    for(i=0;i<targets.length;i++) {
      if(targets[i]) { awaken(targets[i]); break; }
    }
  }

  var check_soon = $.debounce(check,500);
  $(window).scroll(function() { check_soon(); });

  $.fn.lazy = function(arg) {
    this.each(function(i,el) {
      var $this = $(this);
      if(!$this.hasClass('__lazy')) {
        elements.push($this);
        refresh_element(elements.length-1,$this);
        $this.addClass('__lazy');
      } else {
        $this.removeClass('__awake');
      }
    });
  };

  var refresh_soon = $.debounce(refresh,500);
  $.lazy = function(arg,val) {
    if(arg == 'refresh') {
      refresh_soon();
    } else if(arg == 'periodic') {
      setInterval(function() { refresh_soon(); check_soon(); },5000);
    } else if(arg == 'eager') {
      eager();
    }
  };

})(jQuery);
<|endoftext|>


<commit_before>TC.control = TC.control || {};

if (!TC.Control) {
    TC.syncLoadJS(TC.apiLocation + 'TC/Control');
}

TC.control.OverviewMap = function () {
    var self = this;

    TC.Control.apply(self, arguments);

    self.$events = $(self);
    self.isLoaded = false;

    self.layer = null;

    self.wrap = new TC.wrap.control.OverviewMap(self);
};

TC.inherit(TC.control.OverviewMap, TC.Control);

(function () {
    var ctlProto = TC.control.OverviewMap.prototype;

    ctlProto.CLASS = 'tc-ctl-ovmap';

    if (TC.isDebug) {
        ctlProto.template = TC.apiLocation + "TC/templates/OverviewMap.html";
    }
    else {
        ctlProto.template = function () { dust.register(ctlProto.CLASS, body_0); function body_0(chk, ctx) { return chk.w("

"); } body_0.__dustBody = !0; return body_0 };
    }

    ctlProto.register = function (map) {
        const self = this;

        const result = TC.Control.prototype.register.call(self, map);

        const instanceLayer = function (layer) {
            var lyr;

            var findLayerById = function (id, layers) {
                var result = null;
                for (var i = 0; i < layers.length; i++) {
                    var lyr = layers[i];
                    var l = lyr.id || lyr;
                    if (l === id) {
                        result = lyr;
                        break;
                    }
                }
                return result;
            };

            if (typeof layer === 'string') {
                var lyrObj = findLayerById(layer, map.options.availableBaseLayers);
                if (!$.isPlainObject(lyrObj)) {
                    lyrObj = findLayerById(layer, map.options.baseLayers);
                }
                if ($.isPlainObject(lyrObj)) {
                    lyr = new TC.layer.Raster(lyrObj);
                }
            }
            else {
                if (layer instanceof TC.Layer) {
                    lyr = layer;
                }
                else if (layer.type === TC.Consts.layerType.VECTOR || layer.type === TC.Consts.layerType.KML || layer.type === TC.Consts.layerType.WFS) {
                    lyr = new TC.layer.Vector(layer);
                }
                else {
                    lyr = new TC.layer.Raster(layer);
                }
            }

            return lyr;
        };

        const registerLayer = function (layer) {
            var lyr;

            lyr = instanceLayer(layer);

            return lyr;
        };

        map.loaded(function () {
            self.defaultLayer = registerLayer(self.options.layer);
            self.layer = registerLayer(map.baseLayer.overviewMapLayer || self.options.layer || map.options.baseLayers[0] || map.options.availableBaseLayers[0]);
            self.wrap.register(map);

            const resetOVMapProjection = function (e) {
                const resetOptions = {};
                self.layer.getCapabilitiesPromise().then(function () {
                    if (!self.layer.isCompatible(map.crs) && self.layer.wrap.getCompatibleMatrixSets(map.crs).length === 0) {
                        resetOptions.layer = self.layer.getFallbackLayer();
                    }
                    self.wrap.reset(resetOptions);
                });
            };

            resetOVMapProjection({ crs: map.crs });

            const changeBaseLayer = function (e) {
                const self = this;

                if (self.map.baseLayer.type === TC.Consts.layerType.WMS || self.map.baseLayer.type === TC.Consts.layerType.WMTS) {
                    var newLayer = self.map.baseLayer.overviewMapLayer || self.options.layer;
                    if (self.layer.id !== newLayer) {
                        var overviewMapLayer = registerLayer(newLayer);
                        self.wrap.reset({
                            layer: overviewMapLayer
                        }).then(function (layer) {
                            self.layer = layer;
                        });
                    } else if (TC.Consts.event.PROJECTIONCHANGE.indexOf(e.type) > -1) {
                        self.wrap.reset({
                            layer: self.layer
                        }).then(function (layer) {
                            self.layer = layer;
                        });
                    }
                }
            };

            map.on(TC.Consts.event.PROJECTIONCHANGE + ' ' + TC.Consts.event.BASELAYERCHANGE, changeBaseLayer.bind(self));
        });

        return result;
    };

    ctlProto.loaded = function (callback) {
        var self = this;

        if ($.isFunction(callback)) {
            if (self.isLoaded && self.map && self.map.isLoaded) {
                callback();
            }
            else {
                self.$events.on(TC.Consts.event.MAPLOAD, callback);
            }
        }
    };

    ctlProto.activate = function () {
        this.enable();
    };

    ctlProto.deactivate = function () {
        this.disable();
    };

    ctlProto.enable = function () {
        var self = this;
        TC.Control.prototype.enable.call(self);
        self.wrap.enable();
    };

    ctlProto.disable = function () {
        var self = this;
        TC.Control.prototype.disable.call(self);
        self.wrap.disable();
    };

})();<commit_msg>Reduce jQuery use<commit_after>TC.control = TC.control || {};

if (!TC.Control) {
    TC.syncLoadJS(TC.apiLocation + 'TC/Control');
}

TC.control.OverviewMap = function () {
    var self = this;

    TC.Control.apply(self, arguments);

    self.isLoaded = false;

    self.layer = null;
};

TC.inherit(TC.control.OverviewMap, TC.Control);

(function () {
    var ctlProto = TC.control.OverviewMap.prototype;

    ctlProto.CLASS = 'tc-ctl-ovmap';

    if (TC.isDebug) {
        ctlProto.template = TC.apiLocation + "TC/templates/OverviewMap.html";
    }
    else {
        ctlProto.template = function () { dust.register(ctlProto.CLASS, body_0); function body_0(chk, ctx) { return chk.w("

"); } body_0.__dustBody = !0; return body_0 };
    }

    ctlProto.register = function (map) {
        const self = this;

        const instanceLayer = function (layer) {
            var lyr;

            var findLayerById = function (id, layers) {
                var result = null;
                for (var i = 0; i < layers.length; i++) {
                    var lyr = layers[i];
                    var l = lyr.id || lyr;
                    if (l === id) {
                        result = lyr;
                        break;
                    }
                }
                return result;
            };

            if (typeof layer === 'string') {
                var lyrObj = findLayerById(layer, map.options.availableBaseLayers);
                if (!$.isPlainObject(lyrObj)) {
                    lyrObj = findLayerById(layer, map.options.baseLayers);
                }
                if ($.isPlainObject(lyrObj)) {
                    lyr = new TC.layer.Raster(lyrObj);
                }
            }
            else {
                if (layer instanceof TC.Layer) {
                    lyr = layer;
                }
                else if (layer.type === TC.Consts.layerType.VECTOR || layer.type === TC.Consts.layerType.KML || layer.type === TC.Consts.layerType.WFS) {
                    lyr = new TC.layer.Vector(layer);
                }
                else {
                    lyr = new TC.layer.Raster(layer);
                }
            }

            return lyr;
        };

        const registerLayer = function (layer) {
            var lyr;

            lyr = instanceLayer(layer);

            return lyr;
        };

        const resetOVMapProjection = function (e) {
            const resetOptions = {};
            self.layer.getCapabilitiesPromise().then(function () {
                if (!self.layer.isCompatible(map.crs) && self.layer.wrap.getCompatibleMatrixSets(map.crs).length === 0) {
                    resetOptions.layer = self.layer.getFallbackLayer();
                }
                self.wrap.reset(resetOptions);
            });
        };

        const changeBaseLayer = function (e) {
            const self = this;

            if (self.map.baseLayer.type === TC.Consts.layerType.WMS || self.map.baseLayer.type === TC.Consts.layerType.WMTS || self.options.layer) {
                var newLayer = self.map.baseLayer.overviewMapLayer || self.options.layer;
                if (self.layer.id !== newLayer) {
                    var overviewMapLayer = registerLayer(newLayer);
                    self.wrap.reset({
                        layer: overviewMapLayer
                    }).then(function (layer) {
                        self.layer = layer;
                    });
                } else if (TC.Consts.event.PROJECTIONCHANGE.indexOf(e.type) > -1) {
                    self.wrap.reset({
                        layer: self.layer
                    }).then(function (layer) {
                        self.layer = layer;
                    });
                }
            }
        };

        const result = new Promise(function (resolve, reject) {
            TC.Control.prototype.register.call(self, map)
                .then(function (ctl) {
                    self.wrap = new TC.wrap.control.OverviewMap(self);
                    map.loaded(function () {
                        self.defaultLayer = registerLayer(self.options.layer);
                        self.layer = registerLayer(map.baseLayer.overviewMapLayer || self.options.layer || map.options.baseLayers[0] || map.options.availableBaseLayers[0]);

                        self.wrap.register(map);

                        resetOVMapProjection({ crs: map.crs });

                        map.on(TC.Consts.event.PROJECTIONCHANGE + ' ' + TC.Consts.event.BASELAYERCHANGE, changeBaseLayer.bind(self));
                    });
                    resolve(ctl);
                })
                .catch(function (err) {
                    reject(err);
                });
        });

        return result;
    };

    ctlProto.loaded = function (callback) {
        var self = this;

        if ($.isFunction(callback)) {
            if (self.isLoaded && self.map && self.map.isLoaded) {
                callback();
            }
            else {
                self.on(TC.Consts.event.MAPLOAD, callback);
            }
        }
    };

    ctlProto.activate = function () {
        this.enable();
    };

    ctlProto.deactivate = function () {
        this.disable();
    };

    ctlProto.enable = function () {
        var self = this;
        TC.Control.prototype.enable.call(self);
        self.wrap.enable();
    };

    ctlProto.disable = function () {
        var self = this;
        TC.Control.prototype.disable.call(self);
        self.wrap.disable();
    };

})();<|endoftext|>


<commit_before>/*
 * Copyright 2015 Splunk, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"): you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

module.exports = function(RED) {
    function HTTPEventCollector(config) {
        RED.nodes.createNode(this,config);
        var context = this.context();
        var node = this;
        var myMessage = null;

        // Change to require("splunk-logging").Logger;
        var SplunkLogger = require("./index/index").Logger;

        /**
         * Only the token property is required.
         */
        this.myURI = config.inputURI.toString();
        this.myToken = config.inputToken.toString();
        this.mySourcetype = config.inputSourcetype.toString();
        this.myHost = config.inputHost.toString();
        this.mySource = config.inputSource.toString();
        this.myIndex = config.inputIndex.toString();

        // Token and URI data
        var loggerConfig = {
            token: this.myToken,
            url: this.myURI
        };

        // Create a new logger
        var Logger = new SplunkLogger(loggerConfig);

        Logger.error = function(err, context) {
            // Handle errors here
            console.log("error", err, "context", context);
        };

        this.on('input', function(msg) {

            // Attempt to convert msg.payload to a json structure.
            try{
                myMessage = JSON.parse(msg.payload)
            }
            catch(err){
                myMessage = msg.payload
            }

            // Define the payload to send to HTTP Event Collector
            var payload = {
                // Data sent from previous node msg.payload
                message : { payload: myMessage,
                msgMetaData : msg
                },
                // Metadata
                metadata: {
                    source: this.mySource,
                    sourcetype: this.mySourcetype,
                    index: this.myIndex,
                    host: this.myHost
                },
                // Severity is also optional
                severity: "info"
            };

            // removes duplication of data

            delete payload.message.msgMetaData.payload;

            console.log("Sending payload", payload);

            /**
             * Since maxBatchCount is set to 1 by default,
             * calling send will immediately send the payload.
             *
             * The underlying HTTP POST request is made to
             *
             *     https://localhost:8088/services/collector/event/1.0
             *
             * with the following body
             *
             *     {
             *         "source": "chicken coop",
             *         "sourcetype": "httpevent",
             *         "index": "main",
             *         "host": "farm.local",
             *         "event": {
             *             "message": {
             *                 "temperature": "70F",
             *                 "chickenCount": 500
             *             },
             *             "severity": "info"
             *         }
             *     }
             *
             */
            Logger.send(payload, function(err, resp, body) {
                // If successful, body will be { text: 'Success', code: 0 }
                console.log("Response from Splunk", body);
            });
        });
    }
    RED.nodes.registerType("http-event-collector",HTTPEventCollector);
};
<commit_msg>Dependencies Changed Directories<commit_after>/*
 * Copyright 2015 Splunk, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"): you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

module.exports = function(RED) {
    function HTTPEventCollector(config) {
        RED.nodes.createNode(this,config);
        var context = this.context();
        var node = this;
        var myMessage = null;

        // Change to require("splunk-logging").Logger;
        var SplunkLogger = require("/../node_modeules/splunk-bunyan-logger/index").Logger;

        /**
         * Only the token property is required.
         */
        this.myURI = config.inputURI.toString();
        this.myToken = config.inputToken.toString();
        this.mySourcetype = config.inputSourcetype.toString();
        this.myHost = config.inputHost.toString();
        this.mySource = config.inputSource.toString();
        this.myIndex = config.inputIndex.toString();

        // Token and URI data
        var loggerConfig = {
            token: this.myToken,
            url: this.myURI
        };

        // Create a new logger
        var Logger = new SplunkLogger(loggerConfig);

        Logger.error = function(err, context) {
            // Handle errors here
            console.log("error", err, "context", context);
        };

        this.on('input', function(msg) {

            // Attempt to convert msg.payload to a json structure.
            try{
                myMessage = JSON.parse(msg.payload)
            }
            catch(err){
                myMessage = msg.payload
            }

            // Define the payload to send to HTTP Event Collector
            var payload = {
                // Data sent from previous node msg.payload
                message : { payload: myMessage,
                msgMetaData : msg
                },
                // Metadata
                metadata: {
                    source: this.mySource,
                    sourcetype: this.mySourcetype,
                    index: this.myIndex,
                    host: this.myHost
                },
                // Severity is also optional
                severity: "info"
            };

            // removes duplication of data

            delete payload.message.msgMetaData.payload;

            console.log("Sending payload", payload);

            /**
             * Since maxBatchCount is set to 1 by default,
             * calling send will immediately send the payload.
             *
             * The underlying HTTP POST request is made to
             *
             *     https://localhost:8088/services/collector/event/1.0
             *
             * with the following body
             *
             *     {
             *         "source": "chicken coop",
             *         "sourcetype": "httpevent",
             *         "index": "main",
             *         "host": "farm.local",
             *         "event": {
             *             "message": {
             *                 "temperature": "70F",
             *                 "chickenCount": 500
             *             },
             *             "severity": "info"
             *         }
             *     }
             *
             */
            Logger.send(payload, function(err, resp, body) {
                // If successful, body will be { text: 'Success', code: 0 }
                console.log("Response from Splunk", body);
            });
        });
    }
    RED.nodes.registerType("http-event-collector",HTTPEventCollector);
};
<|endoftext|>