<commit_before>var common = require('./common'),
    config = require('./config'),
    events = require('events'),
    redis = require('redis'),
    util = require('util');

function Yakusoku() {
	events.EventEmitter.call(this);
	/* TEMP */
	this.tag = '3:moe';
}

util.inherits(Yakusoku, events.EventEmitter);
exports.Yakusoku = Yakusoku;
var Y = Yakusoku.prototype;

Y.connect = function () {
	if (!this.r) {
		this.r = redis.createClient();
		this.r.on('error', console.error.bind(console));
	}
	return this.r;
};

Y.disconnect = function () {
	if (this.r)
		this.r.quit();
	if (this.k) {
		this.k.unsubscribe();
		this.k.quit();
	}
};

Y.kiku = function (thread, callback) {
	if (!this.k) {
		this.k = redis.createClient();
		this.k.on('error', console.error.bind(console));
	}
	var ev;
	function on_subscribe_error(err) {
		this.k.removeListener(ev, on_subscribe);
		this.k.removeListener('error', on_subscribe_error);
		callback(err);
	}
	function on_subscribe(chan, count) {
		this.k.removeListener(ev, on_subscribe);
		this.k.removeListener('error', on_subscribe_error);
		callback(null);
	}
	this.k.on('error', on_subscribe_error.bind(this));
	if (thread) {
		this.k.subscribe('thread:' + thread);
		ev = 'subscribe';
		this.k.on(ev, on_subscribe.bind(this));
		this.k.on('message', this._on_message.bind(this));
	}
	else {
		this.k.psubscribe('thread:*');
		ev = 'psubscribe';
		this.k.on(ev, on_subscribe.bind(this));
		this.k.on('pmessage', this._on_pmessage.bind(this));
	}
};

Y.kikanai = function (thread) {
	this.k.unsubscribe();
	this.k.removeAllListeners('message');
	this.k.removeAllListeners('pmessage');
};

Y._on_message = function (chan, msg) {
	this.emit('update', chan, msg);
};

Y._on_pmessage = function (pat, chan, msg) {
	this.emit('update', chan, msg);
};

function is_empty(obj) {
	if (!obj)
		return false;
	for (var key in obj)
		if (obj.hasOwnProperty(key))
			return false;
	return true;
}

Y.insert_post = function (msg, body, ip, callback) {
	var r = this.connect();
	var self = this;
	/* Multi isn't needed here, yay. */
	if (msg.op) {
		r.exists('thread:' + msg.op, function (err, exists) {
			if (err)
				callback(err);
			else if (!exists)
				callback('Thread does not exist.');
			else
				self._insert(msg, body, ip, callback);
		});
	}
	else
		self._insert(msg, body, ip, callback);
};

Y._insert = function (msg, body, ip, callback) {
	var r = this.connect();
	var tag_key = 'tag:' + this.tag;
	var self = this;
	r.incr(tag_key + ':ctr', function (err, num) {
		if (err)
			return callback(err);
		var view = {time: msg.time, ip: ip};
		var op = msg.op;
		if (msg.name)
			view.name = msg.name;
		if (msg.trip)
			view.trip = msg.trip;
		if (msg.email)
			view.email = msg.email;
		if (op)
			view.op = msg.op;
		if (msg.image)
			add_image_view(msg.image, view);

		var key = (op ? 'post:' : 'thread:') + num;
		var bump = !op || view.email != 'sage';
		var m = r.multi();
		if (bump)
			m.incr(tag_key + ':bumpctr');
		m.hmset(key, view);
		m.set(key + ':body', body);
		if (op)
			m.rpush('thread:' + op + ':posts', num);

		/* Denormalize for backlog */
		view.body = body;
		view.num = num;
		self._log(m, op ? op : num, common.INSERT_POST, view);

		m.exec(function (err, results) {
			if (err)
				return callback(err);
			else if (!bump)
				return callback(null, num);
			r.zadd(tag_key + ':threads', results[0], num,
						function (err) {
				if (err)
					callback(err);
				else
					callback(null, num);
			});
		});
	});
};

function add_image_view(image, dest) {
	dest.imgMD5 = image.MD5;
	dest.imgext = image.ext;
	dest.imgsize = image.size;
	dest.imgtime = image.time;
}

Y.add_image = function (post_num, image, callback) {
	var r = this.connect();
	var key = 'post:' + post_num;
	r.exists(key, function (err, exists) {
		if (err)
			callback(err);
		else if (!exists)
			callback("Post does not exist.");
		else {
			var view = {};
			add_image_view(image, view);
			r.hmset(key, view, callback);
		}
	});
};

Y.append_post = function (num, is_reply, tail, limit, callback) {
	var r = this.connect();
	var key = (is_reply ? 'post:' : 'thread:') + num + ':body';
	/* Don't need to check .exists() thanks to client state */
	r.append(key, tail, function (err, new_len) {
		if (err)
			callback(err);
		else if (new_len > limit)
			trim_post(r, key, limit, callback, 0);
		else
			callback(null, new_len);
	});
};

function trim_post(r, key, limit, callback, tries) {
	r.watch(key);
	r.substr(key, 0, limit, function (err, body) {
		if (err || tries > 5) {
			console.error("Warning: Overlong post permitted");
			return callback(err);
		}
		r.multi().set(key, body).exec(function (err) {
			if (err)
				trim_post(r, key, limit, callback, tries+1);
			else
				callback(null, limit);
		});
	});
}

Y.finish_post = function (num, is_reply, callback) {
	var r = this.connect();
	var key = (is_reply ? 'post:' : 'thread:') + num;
	/* Don't need to check .exists() thanks to client state */
	r.get(key + ':body', function (err, body) {
		if (err)
			return callback(err);
		r.hmset(key, 'body', body, function (err) {
			if (err)
				callback(err);
			else
				r.del(key + ':body', callback);
		});
	});
};

Y._log = function (m, thread, act) {
	var rec = [act];
	for (var i = 3; i < arguments.length; i++)
		rec.push(arguments[i]);
	var msg = JSON.stringify(rec);
	m.rpush('backlog', msg);
	m.publish('thread:' + thread, msg);
};

Y.fetch_backlog = function (sync, watching, callback) {
	var r = this.connect();
	r.lrange('backlog', sync, -1, function (err, log) {
		if (err)
			return callback(err);
		// TODO: Do something with watching
		// Naive impl for now
		callback(null, sync + log.length, log);
	});
};

Y.get_sync_number = function (callback) {
	this.connect().llen('backlog', callback);
};

Y.thread_exists = function (num, callback) {
	this.connect().exists('thread:' + num, callback);
};

Y.get_tag = function () {
	var r = this.connect();
	var self = this;
	r.zrevrange('tag:' + this.tag + ':threads', 0, -1, function (err, ns) {
		if (err)
			return self.emit('error', err);
		self.emit('begin');
		var reader = new Reader(self);
		reader.on('error', self.emit.bind(self, 'error'));
		reader.on('thread', self.emit.bind(self, 'thread'));
		reader.on('post', self.emit.bind(self, 'post'));
		self._get_each_thread(reader, 0, ns);
	});
};

Y._get_each_thread = function (reader, ix, nums) {
	if (!nums || ix >= nums.length) {
		this.emit('end');
		return;
	}
	var self = this;
	var next_please = function () {
		reader.removeListener('end', next_please);
		reader.removeListener('nomatch', next_please);
		self._get_each_thread(reader, ix+1, nums);
	};
	reader.on('end', next_please);
	reader.on('nomatch', next_please);
	reader.get_thread(nums[ix], false);
};

function Reader(yakusoku) {
	events.EventEmitter.call(this);
	this.y = yakusoku;
}

util.inherits(Reader, events.EventEmitter);
exports.Reader = Reader;

Reader.prototype.get_thread = function (num, redirect_ok) {
	var r = this.y.connect();
	var key = 'thread:' + num;
	var self = this;
	r.hgetall(key, function (err, pre_post) {
		if (err)
			return self.emit('error', err);
		if (is_empty(pre_post)) {
			if (!redirect_ok)
				return self.emit('nomatch');
			r.hget('post:' + num, 'op',
						function (err, op) {
				if (err)
					self.emit('error', err);
				else if (!op)
					self.emit('nomatch');
				else
					self.emit('redirect', op);
			});
			return;
		}
		self.emit('begin');
		pre_post.num = num;
		with_body(r, key, pre_post, function (err, post) {
			if (err)
				return self.emit('error', err);
			self.emit('thread', post);
			r.lrange(key + ':posts', 0, -1, function (err, nums) {
				if (err)
					return self.emit('error', err);
				self._get_each_reply(0, nums);
			});
		});
	});
};

Reader.prototype._get_each_reply = function (ix, nums) {
	if (!nums || ix >= nums.length)
		return this.emit('end');
	var r = this.y.connect();
	var num = nums[ix];
	var key = 'post:' + num;
	var next_please = this._get_each_reply.bind(this, ix + 1, nums);
	var self = this;
	r.hgetall(key, function (err, pre_post) {
		if (err)
			return self.emit('error', err);
		if (is_empty(pre_post))
			return next_please();
		pre_post.num = num;
		with_body(r, key, pre_post, function (err, post) {
			if (err)
				return self.emit('error', err);
			var has_next = ix + 1 < nums.length;
			self.emit('post', post, has_next);
			next_please();
		});
	});
};

function with_body(r, key, post, callback) {
	/* Convenience */
	post.time = parseInt(post.time);
	post.op = parseInt(post.op);

	if (post.body !== undefined)
		callback(null, post);
	else
		r.get(key + ':body', function (err, body) {
			if (err)
				return callback(err);
			if (body !== null) {
				post.body = body;
				post.editing = true;
				return callback(null, post);
			}
			// Race condition between finishing posts
			r.hget(key, 'body', function (err, body) {
				if (err)
					return callback(err);
				post.body = body;
				callback(null, post);
			});
		});
};
<commit_msg>Add some removeAllListeners()<commit_after>var common = require('./common'),
    config = require('./config'),
    events = require('events'),
    redis = require('redis'),
    util = require('util');

function Yakusoku() {
	events.EventEmitter.call(this);
	/* TEMP */
	this.tag = '3:moe';
}

util.inherits(Yakusoku, events.EventEmitter);
exports.Yakusoku = Yakusoku;
var Y = Yakusoku.prototype;

Y.connect = function () {
	if (!this.r) {
		this.r = redis.createClient();
		this.r.on('error', console.error.bind(console));
	}
	return this.r;
};

Y.disconnect = function () {
	if (this.r) {
		this.r.quit();
		this.r.removeAllListeners();
	}
	if (this.k) {
		this.k.unsubscribe();
		this.k.quit();
		this.k.removeAllListeners();
	}
	this.removeAllListeners();
};

Y.kiku = function (thread, callback) {
	if (!this.k) {
		this.k = redis.createClient();
		this.k.on('error', console.error.bind(console));
	}
	var ev;
	function on_subscribe_error(err) {
		this.k.removeListener(ev, on_subscribe);
		this.k.removeListener('error', on_subscribe_error);
		callback(err);
	}
	function on_subscribe(chan, count) {
		this.k.removeListener(ev, on_subscribe);
		this.k.removeListener('error', on_subscribe_error);
		callback(null);
	}
	this.k.on('error', on_subscribe_error.bind(this));
	if (thread) {
		this.k.subscribe('thread:' + thread);
		ev = 'subscribe';
		this.k.on(ev, on_subscribe.bind(this));
		this.k.on('message', this._on_message.bind(this));
	}
	else {
		this.k.psubscribe('thread:*');
		ev = 'psubscribe';
		this.k.on(ev, on_subscribe.bind(this));
		this.k.on('pmessage', this._on_pmessage.bind(this));
	}
};

Y.kikanai = function (thread) {
	this.k.unsubscribe();
	this.k.removeAllListeners('message');
	this.k.removeAllListeners('pmessage');
};

Y._on_message = function (chan, msg) {
	this.emit('update', chan, msg);
};

Y._on_pmessage = function (pat, chan, msg) {
	this.emit('update', chan, msg);
};

function is_empty(obj) {
	if (!obj)
		return false;
	for (var key in obj)
		if (obj.hasOwnProperty(key))
			return false;
	return true;
}

Y.insert_post = function (msg, body, ip, callback) {
	var r = this.connect();
	var self = this;
	/* Multi isn't needed here, yay. */
	if (msg.op) {
		r.exists('thread:' + msg.op, function (err, exists) {
			if (err)
				callback(err);
			else if (!exists)
				callback('Thread does not exist.');
			else
				self._insert(msg, body, ip, callback);
		});
	}
	else
		self._insert(msg, body, ip, callback);
};

Y._insert = function (msg, body, ip, callback) {
	var r = this.connect();
	var tag_key = 'tag:' + this.tag;
	var self = this;
	r.incr(tag_key + ':ctr', function (err, num) {
		if (err)
			return callback(err);
		var view = {time: msg.time, ip: ip};
		var op = msg.op;
		if (msg.name)
			view.name = msg.name;
		if (msg.trip)
			view.trip = msg.trip;
		if (msg.email)
			view.email = msg.email;
		if (op)
			view.op = msg.op;
		if (msg.image)
			add_image_view(msg.image, view);

		var key = (op ? 'post:' : 'thread:') + num;
		var bump = !op || view.email != 'sage';
		var m = r.multi();
		if (bump)
			m.incr(tag_key + ':bumpctr');
		m.hmset(key, view);
		m.set(key + ':body', body);
		if (op)
			m.rpush('thread:' + op + ':posts', num);

		/* Denormalize for backlog */
		view.body = body;
		view.num = num;
		self._log(m, op ? op : num, common.INSERT_POST, view);

		m.exec(function (err, results) {
			if (err)
				return callback(err);
			else if (!bump)
				return callback(null, num);
			r.zadd(tag_key + ':threads', results[0], num,
						function (err) {
				if (err)
					callback(err);
				else
					callback(null, num);
			});
		});
	});
};

function add_image_view(image, dest) {
	dest.imgMD5 = image.MD5;
	dest.imgext = image.ext;
	dest.imgsize = image.size;
	dest.imgtime = image.time;
}

Y.add_image = function (post_num, image, callback) {
	var r = this.connect();
	var key = 'post:' + post_num;
	r.exists(key, function (err, exists) {
		if (err)
			callback(err);
		else if (!exists)
			callback("Post does not exist.");
		else {
			var view = {};
			add_image_view(image, view);
			r.hmset(key, view, callback);
		}
	});
};

Y.append_post = function (num, is_reply, tail, limit, callback) {
	var r = this.connect();
	var key = (is_reply ? 'post:' : 'thread:') + num + ':body';
	/* Don't need to check .exists() thanks to client state */
	r.append(key, tail, function (err, new_len) {
		if (err)
			callback(err);
		else if (new_len > limit)
			trim_post(r, key, limit, callback, 0);
		else
			callback(null, new_len);
	});
};

function trim_post(r, key, limit, callback, tries) {
	r.watch(key);
	r.substr(key, 0, limit, function (err, body) {
		if (err || tries > 5) {
			console.error("Warning: Overlong post permitted");
			return callback(err);
		}
		r.multi().set(key, body).exec(function (err) {
			if (err)
				trim_post(r, key, limit, callback, tries+1);
			else
				callback(null, limit);
		});
	});
}

Y.finish_post = function (num, is_reply, callback) {
	var r = this.connect();
	var key = (is_reply ? 'post:' : 'thread:') + num;
	/* Don't need to check .exists() thanks to client state */
	r.get(key + ':body', function (err, body) {
		if (err)
			return callback(err);
		r.hmset(key, 'body', body, function (err) {
			if (err)
				callback(err);
			else
				r.del(key + ':body', callback);
		});
	});
};

Y._log = function (m, thread, act) {
	var rec = [act];
	for (var i = 3; i < arguments.length; i++)
		rec.push(arguments[i]);
	var msg = JSON.stringify(rec);
	m.rpush('backlog', msg);
	m.publish('thread:' + thread, msg);
};

Y.fetch_backlog = function (sync, watching, callback) {
	var r = this.connect();
	r.lrange('backlog', sync, -1, function (err, log) {
		if (err)
			return callback(err);
		// TODO: Do something with watching
		// Naive impl for now
		callback(null, sync + log.length, log);
	});
};

Y.get_sync_number = function (callback) {
	this.connect().llen('backlog', callback);
};

Y.thread_exists = function (num, callback) {
	this.connect().exists('thread:' + num, callback);
};

Y.get_tag = function () {
	var r = this.connect();
	var self = this;
	r.zrevrange('tag:' + this.tag + ':threads', 0, -1, function (err, ns) {
		if (err)
			return self.emit('error', err);
		self.emit('begin');
		var reader = new Reader(self);
		reader.on('error', self.emit.bind(self, 'error'));
		reader.on('thread', self.emit.bind(self, 'thread'));
		reader.on('post', self.emit.bind(self, 'post'));
		self._get_each_thread(reader, 0, ns);
	});
};

Y._get_each_thread = function (reader, ix, nums) {
	if (!nums || ix >= nums.length) {
		this.emit('end');
		reader.removeAllListeners();
		return;
	}
	var self = this;
	var next_please = function () {
		reader.removeListener('end', next_please);
		reader.removeListener('nomatch', next_please);
		self._get_each_thread(reader, ix+1, nums);
	};
	reader.on('end', next_please);
	reader.on('nomatch', next_please);
	reader.get_thread(nums[ix], false);
};

function Reader(yakusoku) {
	events.EventEmitter.call(this);
	this.y = yakusoku;
}

util.inherits(Reader, events.EventEmitter);
exports.Reader = Reader;

Reader.prototype.get_thread = function (num, redirect_ok) {
	var r = this.y.connect();
	var key = 'thread:' + num;
	var self = this;
	r.hgetall(key, function (err, pre_post) {
		if (err)
			return self.emit('error', err);
		if (is_empty(pre_post)) {
			if (!redirect_ok)
				return self.emit('nomatch');
			r.hget('post:' + num, 'op',
						function (err, op) {
				if (err)
					self.emit('error', err);
				else if (!op)
					self.emit('nomatch');
				else
					self.emit('redirect', op);
			});
			return;
		}
		self.emit('begin');
		pre_post.num = num;
		with_body(r, key, pre_post, function (err, post) {
			if (err)
				return self.emit('error', err);
			self.emit('thread', post);
			r.lrange(key + ':posts', 0, -1, function (err, nums) {
				if (err)
					return self.emit('error', err);
				self._get_each_reply(0, nums);
			});
		});
	});
};

Reader.prototype._get_each_reply = function (ix, nums) {
	if (!nums || ix >= nums.length)
		return this.emit('end');
	var r = this.y.connect();
	var num = nums[ix];
	var key = 'post:' + num;
	var next_please = this._get_each_reply.bind(this, ix + 1, nums);
	var self = this;
	r.hgetall(key, function (err, pre_post) {
		if (err)
			return self.emit('error', err);
		if (is_empty(pre_post))
			return next_please();
		pre_post.num = num;
		with_body(r, key, pre_post, function (err, post) {
			if (err)
				return self.emit('error', err);
			var has_next = ix + 1 < nums.length;
			self.emit('post', post, has_next);
			next_please();
		});
	});
};

function with_body(r, key, post, callback) {
	/* Convenience */
	post.time = parseInt(post.time);
	post.op = parseInt(post.op);

	if (post.body !== undefined)
		callback(null, post);
	else
		r.get(key + ':body', function (err, body) {
			if (err)
				return callback(err);
			if (body !== null) {
				post.body = body;
				post.editing = true;
				return callback(null, post);
			}
			// Race condition between finishing posts
			r.hget(key, 'body', function (err, body) {
				if (err)
					return callback(err);
				post.body = body;
				callback(null, post);
			});
		});
};
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

const fs = require('fs');
const path = require('path');
const glob = require('glob');
const winston = require('winston');
const { Validator } = require('jsonschema');
const dupKeyValidator = require('json-dup-key-validator');
const moment = require('moment-timezone');
const isEqual = require('lodash/isEqual');

const log = winston.createLogger({
  transports: [new winston.transports.Console()],
  format: winston.format.combine(
    winston.format.timestamp({ format: 'HH:mm:ss' }),
    winston.format.colorize({ error: true, warning: true }),
    winston.format.printf(info => `[${info.timestamp}] ${info.level}: ${info.message}`)
  )
});

/**
 * Validate a JSON file.
 * @param {string} filePath Absolute path to the file
 * @param {jsonschema.Validator} validator Validator object
 * @returns {(string|null)} Error if failed, null if passed
 */
const validateJSON = (filePath, validator) => {
  const data = fs.readFileSync(filePath, 'utf-8');

  const isInvalid = dupKeyValidator.validate(data, false);
  if (isInvalid !== undefined) {
    return isInvalid.replace(/\n/g, '\\n');
  }

  const obj = JSON.parse(data);
  const results = validator.validate(obj, '/');

  if (results.errors.length > 0) {
    const messageList = results.errors.map(result => String(result));
    return messageList.join('\n');
  }

  return null;
};

/**
 * Validate the scene exceptions JSON files.
 * @returns {boolean} Validation result.
 */
const validateSceneExceptions = () => {
  const validator = new Validator();
  validator.addSchema({
    'id': '/indexerSchema',
    'type': 'object',
    'additionalProperties': false,
    'patternProperties': {
      '^([1-9]\\d+)$': {
        'type': 'object',
        'additionalProperties': false,
        'patternProperties': {
          '^(-1|\\d+)$': {
            'type': 'array',
            'minItems': 1,
            'uniqueItems': true,
            'items': {
              'type': 'string'
            }
          }
        }
      }
    }
  });

  return glob
    .sync('scene_exceptions/scene_exceptions_*.json', {
      cwd: __dirname,
      absolute: true
    }).map(filePath => {
      const fileName = path.basename(filePath);
      log.info(`Validating: scene_exceptions/${fileName}`);

      // Example: scene_exceptions_tvdb.json
      const indexer = fileName.split('.')[0].split('_').pop();
      validator.addSchema({
        'id': '/',
        'type': 'object',
        'additionalProperties': false,
        'properties': {
          [indexer]: {
            '$ref': '/indexerSchema'
          }
        }
      });

      const result = validateJSON(filePath, validator);
      if (result !== null) {
        log.error(`${result}\n`);
        return false;
      }

      console.log();
      return true;
    }).every(Boolean);
};

/**
 * Validate the scene exceptions JSON files.
 * @returns {boolean} Validation result.
 */
const validateBrokenProviders = () => {
  const relFile = 'providers/broken_providers.json';
  const filePath = path.resolve(__dirname, relFile);
  log.info(`Validating: ${relFile}`);

  const validator = new Validator();
  validator.addSchema({
    'id': '/',
    'type': 'array',
    'minItems': 1,
    'uniqueItems': true,
    'items': {
      'type': 'string'
    }
  });

  const result = validateJSON(filePath, validator);
  if (result !== null) {
    log.error(`${result}\n`);
    return false;
  }

  console.log();
  return true;
};

/**
 * Validate the network timezones list.
 * @returns {boolean} Validation result.
 */
const validateNetworkTimezones = () => {
  const relFile = 'sb_network_timezones/network_timezones.txt';
  const filePath = path.resolve(__dirname, relFile);
  log.info(`Validating: ${relFile}`);

  let errors = 0;
  const data = fs.readFileSync(filePath, 'utf-8');
  const linePattern = /^.+:[\w/]+$/;

  // https://stackoverflow.com/a/5202185/7597273
  const rsplit = (str, sep, maxsplit) => {
    const split = str.split(sep);
    return maxsplit ?
      [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit)) :
      split;
  };

  const logError = (message, index, line) => {
    if (line) {
      log.error(`Line #${index}: ${message}\n\t\`${line}\``);
    } else {
      log.error(`Line #${index}: ${message}`);
    }
    errors++;
  };

  const timezones = moment.tz.names();
  const lines = data.split('\n');

  if (!data.trim() || lines.length === 0) {
    log.warn('File is empty.');
    return true;
  }

  lines.forEach((line, index) => {
    const lineIndex = index + 1;
    const trimmedLine = line.trim();

    if (lineIndex === lines.length) {
      if (trimmedLine) {
        logError('Please leave one empty line at the end of the file.', lineIndex);
      } else {
        // Last line should not be processed.
        return;
      }
    }

    if (!trimmedLine) {
      logError('Please remove empty lines.', lineIndex);
      return;
    }

    if (line.includes('\r')) {
      logError('`\\r` found - please only use Linux EOL (`\\n`).', lineIndex);
    }

    if (!linePattern.test(line)) {
      logError('Line format invalid, please use: `Network Name:Timezone`', lineIndex, line);
    }

    const timezone = rsplit(line, ':', 1)[1];
    if (!timezones.includes(timezone)) {
      logError(`Timezone \`${timezone}\` is invalid.`, lineIndex, line);
    }
  });

  const sortedLines = lines.slice().filter(Boolean);
  sortedLines.sort();
  sortedLines.push('');

  const sorted = isEqual(lines, sortedLines);
  if (!sorted) {
    if (process.argv.includes('--fix')) {
      log.info(`Writing sorted lines to ${relFile}.`);
      fs.writeFileSync(filePath, sortedLines.join('\n'));
    } else {
      log.error('Lines are unsorted. Run `yarn validate --fix` to fix.');
    }
  }

  console.log();
  return errors === 0 && sorted;
};

const results = [
  validateSceneExceptions(),
  validateBrokenProviders(),
  validateNetworkTimezones()
];
process.exit(
  Number(
    !results.every(Boolean)
  )
);
<commit_msg>Validate that there are no duplicate networks on the list<commit_after>#!/usr/bin/env node
'use strict';

const fs = require('fs');
const path = require('path');
const glob = require('glob');
const winston = require('winston');
const { Validator } = require('jsonschema');
const dupKeyValidator = require('json-dup-key-validator');
const moment = require('moment-timezone');
const isEqual = require('lodash/isEqual');

const log = winston.createLogger({
  transports: [new winston.transports.Console()],
  format: winston.format.combine(
    winston.format.timestamp({ format: 'HH:mm:ss' }),
    winston.format.colorize({ error: true, warning: true }),
    winston.format.printf(info => `[${info.timestamp}] ${info.level}: ${info.message}`)
  )
});

/**
 * Validate a JSON file.
 * @param {string} filePath Absolute path to the file
 * @param {jsonschema.Validator} validator Validator object
 * @returns {(string|null)} Error if failed, null if passed
 */
const validateJSON = (filePath, validator) => {
  const data = fs.readFileSync(filePath, 'utf-8');

  const isInvalid = dupKeyValidator.validate(data, false);
  if (isInvalid !== undefined) {
    return isInvalid.replace(/\n/g, '\\n');
  }

  const obj = JSON.parse(data);
  const results = validator.validate(obj, '/');

  if (results.errors.length > 0) {
    const messageList = results.errors.map(result => String(result));
    return messageList.join('\n');
  }

  return null;
};

/**
 * Validate the scene exceptions JSON files.
 * @returns {boolean} Validation result.
 */
const validateSceneExceptions = () => {
  const validator = new Validator();
  validator.addSchema({
    'id': '/indexerSchema',
    'type': 'object',
    'additionalProperties': false,
    'patternProperties': {
      '^([1-9]\\d+)$': {
        'type': 'object',
        'additionalProperties': false,
        'patternProperties': {
          '^(-1|\\d+)$': {
            'type': 'array',
            'minItems': 1,
            'uniqueItems': true,
            'items': {
              'type': 'string'
            }
          }
        }
      }
    }
  });

  return glob
    .sync('scene_exceptions/scene_exceptions_*.json', {
      cwd: __dirname,
      absolute: true
    }).map(filePath => {
      const fileName = path.basename(filePath);
      log.info(`Validating: scene_exceptions/${fileName}`);

      // Example: scene_exceptions_tvdb.json
      const indexer = fileName.split('.')[0].split('_').pop();
      validator.addSchema({
        'id': '/',
        'type': 'object',
        'additionalProperties': false,
        'properties': {
          [indexer]: {
            '$ref': '/indexerSchema'
          }
        }
      });

      const result = validateJSON(filePath, validator);
      if (result !== null) {
        log.error(`${result}\n`);
        return false;
      }

      console.log();
      return true;
    }).every(Boolean);
};

/**
 * Validate the scene exceptions JSON files.
 * @returns {boolean} Validation result.
 */
const validateBrokenProviders = () => {
  const relFile = 'providers/broken_providers.json';
  const filePath = path.resolve(__dirname, relFile);
  log.info(`Validating: ${relFile}`);

  const validator = new Validator();
  validator.addSchema({
    'id': '/',
    'type': 'array',
    'minItems': 1,
    'uniqueItems': true,
    'items': {
      'type': 'string'
    }
  });

  const result = validateJSON(filePath, validator);
  if (result !== null) {
    log.error(`${result}\n`);
    return false;
  }

  console.log();
  return true;
};

/**
 * Validate the network timezones list.
 * @returns {boolean} Validation result.
 */
const validateNetworkTimezones = () => {
  const relFile = 'sb_network_timezones/network_timezones.txt';
  const filePath = path.resolve(__dirname, relFile);
  log.info(`Validating: ${relFile}`);

  let errors = 0;
  const data = fs.readFileSync(filePath, 'utf-8');
  const linePattern = /^.+:[\w/]+$/;

  // https://stackoverflow.com/a/5202185/7597273
  const rsplit = (str, sep, maxsplit) => {
    const split = str.split(sep);
    return maxsplit ?
      [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit)) :
      split;
  };

  const logError = (message, index, line) => {
    if (line) {
      log.error(`Line #${index}: ${message}\n\t\`${line}\``);
    } else {
      log.error(`Line #${index}: ${message}`);
    }
    errors++;
  };

  const timezones = moment.tz.names();
  const lines = data.split('\n');

  if (!data.trim() || lines.length === 0) {
    log.warn('File is empty.');
    return true;
  }

  const networkNames = {};

  lines.forEach((line, index) => {
    const lineIndex = index + 1;
    const trimmedLine = line.trim();

    if (lineIndex === lines.length) {
      if (trimmedLine) {
        logError('Please leave one empty line at the end of the file.', lineIndex);
      } else {
        // Last line should not be processed.
        return;
      }
    }

    if (!trimmedLine) {
      logError('Please remove empty lines.', lineIndex);
      return;
    }

    if (line.includes('\r')) {
      logError('`\\r` found - please only use Linux EOL (`\\n`).', lineIndex);
    }

    if (!linePattern.test(line)) {
      logError('Line format invalid, please use: `Network Name:Timezone`', lineIndex, line);
    }

    const [network, timezone] = rsplit(line, ':', 1);
    if (!timezones.includes(timezone)) {
      logError(`Timezone \`${timezone}\` is invalid.`, lineIndex, line);
    }

    if (networkNames[network] === undefined) {
      networkNames[network] = lineIndex;
    } else {
      logError(`Duplicate entry found.`, lineIndex, line);
      logError(`First entry.`, networkNames[network], lines[networkNames[network] - 1]);
    }
  });

  const sortedLines = lines.slice().filter(Boolean);
  sortedLines.sort();
  sortedLines.push('');

  const sorted = isEqual(lines, sortedLines);
  if (!sorted) {
    if (process.argv.includes('--fix')) {
      log.info(`Writing sorted lines to ${relFile}.`);
      fs.writeFileSync(filePath, sortedLines.join('\n'));
    } else {
      log.error('Lines are unsorted. Run `yarn validate --fix` to fix.');
    }
  }

  console.log();
  return errors === 0 && sorted;
};

const results = [
  validateSceneExceptions(),
  validateBrokenProviders(),
  validateNetworkTimezones()
];
process.exit(
  Number(
    !results.every(Boolean)
  )
);
<|endoftext|>


<commit_before>'use strict';

var gutil = require('gulp-util');
var coveralls = require('gulp-coveralls');
var istanbul = require('gulp-istanbul');
var jshint = require('gulp-jshint');
var mocha = require('gulp-mocha');

var fs = require('fs');
var path = require('path');
var stripAnsi = require('strip-ansi');
var code = require('code-context');
var mdu = require('markdown-utils');
var relative = require('relative');
var verb = require('verb');

verb.helper('list', function (dir) {
  var files = fs.readdirSync(dir);
  var res = files.reduce(function (acc, fp) {
    fp = path.resolve(dir, fp);
    if (!/index\.js/.test(fp) && /\.js/.test(fp)) {
      acc[fp] = require(fp);
    }
    return acc;
  }, {});
  return format(res).replace(/^\s*/, '');
});

verb.option('debugEngine', true);
verb.data({docsDiff: {}, testDiff: {}});

verb.task('readme', function() {
  verb.src('.verb.md')
    .pipe(verb.dest('.'));
});

verb.task('lint', function () {
  verb.src(['index.js', 'lib/**/*.js'])
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('jshint-stylish'));
});

verb.task('test', function (cb) {
  verb.src(['index.js', 'lib/**/*.js'])
    .pipe(istanbul({includeUntested: true}))
    .pipe(istanbul.hookRequire())
    .on('finish', function () {
      verb.src('test/*.js')
        .on('error', gutil.log)
        .pipe(mocha())
        .pipe(istanbul.writeReports())
        .on('end', cb);
    });
});

verb.task('default', ['readme', 'lint', 'test']);

/**
 * TODO: move this out into a plugin
 */

function summary(fp) {
  var str = fs.readFileSync(fp, 'utf8');
  str = stripAnsi(str).replace(/^=.*/gm, '');
  return str;
}

function context(str) {
  return code(str).reduce(function (acc, ele) {
    if (ele && ele.type !== 'comment') {
      acc[ele.name] = ele.begin;
    }
    return acc;
  }, {});
}

function format(obj) {
  var keys = Object.keys(obj);
  var len = keys.length;
  var i = 0;
  var res = '\n';
  var count = len;

  while (len--) {
    var fp = keys[i++];
    var ctx = context(fs.readFileSync(fp, 'utf8'));
    var name = path.basename(fp, path.extname(fp));
    res += '\n+ ' + mdu.strong(mdu.link(name, relative(fp))) + '\n';

    var list = obj[fp];
    var items = Object.keys(list);
    // todo: move this to a new function
    res += items.map(function (method) {
      count++;
      var line = ctx[method];
      var link = method;
      if (line) {
        link = mdu.link(method, relative(fp) + '#L' + line);
      }
      return '  - ' + link;
    }).sort().join('\n');
  }

  return count
    + ' helpers organized into the following categories:'
    + res;
}
<commit_msg>clean up<commit_after>'use strict';

var gutil = require('gulp-util');
var istanbul = require('gulp-istanbul');
var jshint = require('gulp-jshint');
var mocha = require('gulp-mocha');

var fs = require('fs');
var path = require('path');
var code = require('code-context');
var mdu = require('markdown-utils');
var relative = require('relative');
var verb = require('verb');

verb.option('debugEngine', true);
verb.data({docsDiff: {}, testDiff: {}});

verb.task('readme', function() {
  return verb.src('.verb.md')
    .pipe(verb.dest('.'));
});

verb.task('lint', function () {
  return verb.src(['index.js', 'lib/**/*.js'])
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('jshint-stylish'));
});

verb.task('test', function (cb) {
  verb.src(['index.js', 'lib/**/*.js'])
    .pipe(istanbul({includeUntested: true}))
    .pipe(istanbul.hookRequire())
    .on('finish', function () {
      verb.src(['test/*.js'])
        .pipe(mocha())
        .on('error', gutil.log)
        .pipe(istanbul.writeReports())
        .on('end', cb)
    })
});

verb.task('default', ['readme', 'lint', 'test']);

verb.helper('list', function (dir) {
  var files = fs.readdirSync(dir);
  var res = files.reduce(function (acc, fp) {
    fp = path.resolve(dir, fp);
    if (!/index\.js/.test(fp) && /\.js/.test(fp)) {
      acc[fp] = require(fp);
    }
    return acc;
  }, {});
  return format(res).replace(/^\s*/, '');
});

/**
 * TODO: move this out into a plugin
 */

function context(str) {
  return code(str).reduce(function (acc, ele) {
    if (ele && ele.type !== 'comment') {
      acc[ele.name] = ele.begin;
    }
    return acc;
  }, {});
}

function format(obj) {
  var keys = Object.keys(obj);
  var len = keys.length;
  var i = 0;
  var res = '\n';
  var count = len;

  while (len--) {
    var fp = keys[i++];
    var ctx = context(fs.readFileSync(fp, 'utf8'));
    var name = path.basename(fp, path.extname(fp));
    res += '\n+ ' + mdu.strong(mdu.link(name, relative(fp))) + '\n';

    var list = obj[fp];
    var items = Object.keys(list);
    // todo: move this to a new function
    res += items.map(function (method) {
      count++;
      var line = ctx[method];
      var link = method;
      if (line) {
        link = mdu.link(method, relative(fp) + '#L' + line);
      }
      return '  - ' + link;
    }).sort().join('\n');
  }

  return count
    + ' helpers organized into the following categories:'
    + res;
}
<|endoftext|>


<commit_before>(function () {
    'use strict';

    angular
        .module('app.attendees')
        .controller('Attendees', Attendees);

    Attendees.$inject = ['config', 'datacontext'];

    function Attendees(config, datacontext) {
        /*jshint validthis: true */
        var vm = this;

        var keyCodes = config.keyCodes;

        vm.attendeeCount = 0;
        vm.attendeeFilteredCount = 0;
        vm.attendeeSearch = '';
        vm.attendees = [];
        vm.filteredAttendees = [];
        vm.paging = {
            currentPage: 1,
            maxPagesToShow: 5,
            pageSize: 15
        };
        vm.pageChanged = pageChanged;
        vm.refresh = refresh;
        vm.search = search;
        vm.title = 'Attendees';

        Object.defineProperty(vm.paging, 'pageCount', {
            get: function () {
                return Math.floor(vm.attendeeFilteredCount / vm.paging.pageSize) + 1;
            }
        });

        activate();

        function activate() {
//            TODO: Using a resolver on all routes or datacontext.ready in every controller
//            return datacontext.ready([getAttendees()]);
            return getAttendees();
        }

        function getAttendeeCount() {
            return datacontext.attendee.getCount().then(function (data) {
                vm.attendeeCount = data;
                return vm.attendeeCount;
            });
        }

        function getAttendees(forceRefresh) {
            return datacontext.attendee.getAll(
                    forceRefresh,
                    vm.paging.currentPage,
                    vm.paging.pageSize,
                    vm.attendeeSearch)
                .then(function (data) {
                    vm.attendees = data;
                    if (!vm.attendeeCount || forceRefresh) {
                        // Only grab the full count once or on refresh
                        getAttendeeCount();
                    }
                    getAttendeeFilteredCount();
                    return data;
                });
        }

        function getAttendeeFilteredCount() {
            vm.attendeeFilteredCount = datacontext.attendee.getFilteredCount(vm.attendeeSearch);
        }

        function pageChanged() {
            getAttendees();
        }

        function refresh() {
            return getAttendees(true);
        }

        function search($event) {
            if ($event.keyCode === keyCodes.esc) {
                vm.attendeeSearch = '';
            }
            getAttendees();
        }
    }
})();<commit_msg>fix for paging, related issue #34<commit_after>(function () {
    'use strict';

    angular
        .module('app.attendees')
        .controller('Attendees', Attendees);

    Attendees.$inject = ['config', 'datacontext'];

    function Attendees(config, datacontext) {
        /*jshint validthis: true */
        var vm = this;

        var keyCodes = config.keyCodes;

        vm.attendeeCount = 0;
        vm.attendeeFilteredCount = 0;
        vm.attendeeSearch = '';
        vm.attendees = [];
        vm.filteredAttendees = [];
        vm.paging = {
            currentPage: 1,
            maxPagesToShow: 5,
            pageSize: 15
        };
        vm.pageChanged = pageChanged;
        vm.refresh = refresh;
        vm.search = search;
        vm.title = 'Attendees';

        Object.defineProperty(vm.paging, 'pageCount', {
            get: function () {
                var val = 1;

                if (vm.attendeeFilteredCount % vm.paging.pageSize == 0)
                    val = 0;

                return Math.floor(vm.attendeeFilteredCount / vm.paging.pageSize) + val;
            }
        });

        activate();

        function activate() {
//            TODO: Using a resolver on all routes or datacontext.ready in every controller
//            return datacontext.ready([getAttendees()]);
            return getAttendees();
        }

        function getAttendeeCount() {
            return datacontext.attendee.getCount().then(function (data) {
                vm.attendeeCount = data;
                return vm.attendeeCount;
            });
        }

        function getAttendees(forceRefresh) {
            return datacontext.attendee.getAll(
                    forceRefresh,
                    vm.paging.currentPage,
                    vm.paging.pageSize,
                    vm.attendeeSearch)
                .then(function (data) {
                    vm.attendees = data;
                    if (!vm.attendeeCount || forceRefresh) {
                        // Only grab the full count once or on refresh
                        getAttendeeCount();
                    }
                    getAttendeeFilteredCount();
                    return data;
                });
        }

        function getAttendeeFilteredCount() {
            vm.attendeeFilteredCount = datacontext.attendee.getFilteredCount(vm.attendeeSearch);
        }

        function pageChanged() {
            getAttendees();
        }

        function refresh() {
            return getAttendees(true);
        }

        function search($event) {
            if ($event.keyCode === keyCodes.esc) {
                vm.attendeeSearch = '';
            }
            getAttendees();
        }
    }
})();
<|endoftext|>


<commit_before>/**
 * Trajectory control Widget for IPython
 *
 */

require([
    "jquery",
    "widgets/js/widget",
    'jqueryui',
    ],
function($, WidgetManager) {
    var TrajectoryControls = IPython.DOMWidgetView.extend({
        render : function() {

            this.fps = this.model.get('fps');
            this.width = 600;
            this.height = 20;
            var model = this.model;
            // Create the ui elements
            //
            var tc = $('<div/>');
            tc.addClass("ui-widget-header")
              .addClass("ui-corner-all")
              .css({ padding: "6px"})
              .width(this.width);

            var that = this;
            var slider = $('<div/>').slider({
                value: 0,
                max: this.model.get('n_frames'),
                slide: function (event, ui) {
                    model.set('frame', ui.value);
                    that.touch();
                },
            });

            this.running = false;

            var playButton = $('<button/>').button({
                    text: false,
                    icons: {
                        primary: "ui-icon-play"
                    },
                });

            playButton.click(function(event) {
                    if (!that.running) {
                        that.play();
                        that.running = true;
                    } else {
                        that.pause();
                        that.running = false;
                    }
                });
            playButton.width(this.height).height(this.height);
            playButton.css("float", "left");
            playButton.appendTo(tc);

            // We add an extra container for the slider just for the styling
            var sliderContainer = $("<div/>").css({ margin: "4px 16px 0px 36px" });

            slider.appendTo(sliderContainer);
            sliderContainer.appendTo(tc);

            this.setElement(tc);

            this.slider = slider;
            this.playButton = playButton;
        },

        update : function () {
            this.fps = this.model.get('fps');
            return TrajectoryControls.__super__.update.apply(this);
        },

        play : function () {
            var slider = this.slider;
            var that = this;
            this.playButton.button('option', { icons: { primary : "ui-icon-pause"} });

            if (slider.slider('value') == slider.slider('option', 'max')) {
                slider.slider('value', 0);
            }

            this.playCallbackId = setInterval( function () {

                if (slider.slider('value') < slider.slider('option', 'max')) {
                    slider.slider('value', slider.slider('value') + 1);
                    that.model.set('frame', slider.slider('value'));
                    that.touch();
                } else {
                    that.pause();
                    that.running = false;
                }

            }, 1000/this.fps);

        },

        pause : function () {
            this.playButton.button('option', { icons: { primary : "ui-icon-play"} });
            clearInterval(this.playCallbackId);
        },

        /* We receive custom messages from our python conterpart with DOMWidget.send */
        on_msg: function(msg) {
            if (msg.type == 'callMethod') {
                this[msg.methodName].call(this, msg.args);
            }

            return TrajectoryControls.__super__.update.apply(this);
        },

    });

    WidgetManager.register_widget_view('TrajectoryControls', TrajectoryControls);
});<commit_msg>Added frame count closes #6<commit_after>/**
 * Trajectory control Widget for IPython
 *
 */

require([
    "jquery",
    "widgets/js/widget",
    'jqueryui',
    ],
function($, WidgetManager) {
    var TrajectoryControls = IPython.DOMWidgetView.extend({
        render : function() {

            this.fps = this.model.get('fps');
            this.width = 600;
            this.height = 20;
            var model = this.model;
            // Create the ui elements
            //
            var tc = $('<div/>');
            tc.addClass("ui-widget-header")
              .addClass("ui-corner-all")
              .css({ padding: "6px", display: 'flex'})
              .width(this.width);

            var that = this;
            var slider = $('<div/>').slider({
                value: 0,
                max: this.model.get('n_frames'),
                slide: function (event, ui) {
                    model.set('frame', ui.value);
                    that.touch();
                },
            });

            this.running = false;

            var playButton = $('<button/>').button({
                    text: false,
                    icons: {
                        primary: "ui-icon-play"
                    },
                });

            playButton.click(function(event) {
                    if (!that.running) {
                        that.play();
                        that.running = true;
                    } else {
                        that.pause();
                        that.running = false;
                    }
                });

            playButton.width(this.height).height(this.height);
            playButton.css("float", "left");
            playButton.appendTo(tc);

            // Calculate the number of character to prepare for the correct space

            var maxLength = String(model.get("n_frames")).length;

            var frameIndicator = $("<span/>").text(model.get("frame") + "/" + model.get("n_frames"))
                                             .css( { "width": (2*maxLength) + "em",
                                                     "text-align": "right",
                                                     "margin-right": "6px" } );

            model.on("change:frame", function () {
                frameIndicator.text(model.get("frame") + "/" + model.get("n_frames"));
            });
            // We add an extra container for the slider just for the styling
            var sliderContainer = $("<div/>").css({ "margin": "4px 16px" ,
                                                    "flex-grow" : "1" });

            slider.appendTo(sliderContainer);
            sliderContainer.appendTo(tc);
            frameIndicator.appendTo(tc);

            this.setElement(tc);

            this.slider = slider;
            this.playButton = playButton;
        },

        update : function () {
            this.fps = this.model.get('fps');
            return TrajectoryControls.__super__.update.apply(this);
        },

        play : function () {
            var slider = this.slider;
            var that = this;
            this.playButton.button('option', { icons: { primary : "ui-icon-pause"} });

            if (slider.slider('value') == slider.slider('option', 'max')) {
                slider.slider('value', 0);
            }

            this.playCallbackId = setInterval( function () {

                if (slider.slider('value') < slider.slider('option', 'max')) {
                    slider.slider('value', slider.slider('value') + 1);
                    that.model.set('frame', slider.slider('value'));
                    that.touch();
                } else {
                    that.pause();
                    that.running = false;
                }

            }, 1000/this.fps);

        },

        pause : function () {
            this.playButton.button('option', { icons: { primary : "ui-icon-play"} });
            clearInterval(this.playCallbackId);
        },

        /* We receive custom messages from our python conterpart with DOMWidget.send */
        on_msg: function(msg) {
            if (msg.type == 'callMethod') {
                this[msg.methodName].call(this, msg.args);
            }

            return TrajectoryControls.__super__.update.apply(this);
        },

    });

    WidgetManager.register_widget_view('TrajectoryControls', TrajectoryControls);
});<|endoftext|>


<commit_before>import React from 'react'
import PortfolioEntry from './PortfolioEntry'

export default class PortfolioTable extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      entries: [],
      stockValues: {},
      portfolioValue: ''
    }
  }
  componentWillReceiveProps(nextProps) {
    this.setState({
      entries: nextProps.portfolioStocks,
      stockValues: nextProps.stockValues,
      portfolioValue: nextProps.portfolioValue
    })
  }

  render() {
    return (

          Asset
          Qty
          Value
          Mix

        {this.state.entries.map((item, index) => (
          this.state.stockValues[item.ticker] !== undefined ? <PortfolioEntry item={item} key={index} stockValues={this.state.stockValues} portfolioValue={this.state.portfolioValue} /> : null
        ))}

    )
  }
}<commit_msg>Add tbody to remove warnings<commit_after>import React from 'react'
import PortfolioEntry from './PortfolioEntry'

export default class PortfolioTable extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      entries: [],
      stockValues: {},
      portfolioValue: ''
    }
  }
  componentWillReceiveProps(nextProps) {
    this.setState({
      entries: nextProps.portfolioStocks,
      stockValues: nextProps.stockValues,
      portfolioValue: nextProps.portfolioValue
    })
  }

  render() {
    return (

            Asset
            Qty
            Value
            Mix

          {this.state.entries ?
          this.state.entries.map((item, index) => (
            this.state.stockValues[item.ticker] !== undefined ? <PortfolioEntry item={item} key={index} stockValues={this.state.stockValues} portfolioValue={this.state.portfolioValue} /> : null
          ))
          :
          null
          }

    )
  }
}<|endoftext|>


<commit_before>"use strict";

define(['logManager',
    'clientUtil',
    'js/Constants'], function (logManager,
                                    util,
                                    CONSTANTS) {

    var GridPanelSetsControl;

    GridPanelSetsControl = function (options) {
        var self = this;
        this._client = options.client;
        this._panel = options.panel;
        this._dataGridWidget = options.widget;

        this._dataGridWidget._rowDelete = false;
        this._dataGridWidget._rowEdit = false;

        this._setContainerID = null;

        this._logger = logManager.create("GridPanelSetsControl");

        this._selectedObjectChanged = function (__project, nodeId) {
            self.selectedObjectChanged(nodeId);
        };

        this._logger.debug("Created");
    };

    GridPanelSetsControl.prototype.selectedObjectChanged = function (nodeId) {
        var self = this;

        this._logger.debug("SELECTEDOBJECT_CHANGED nodeId '" + nodeId + "'");

        //remove current territory patterns
        if (this._setContainerID) {
            this._client.removeUI(this._territoryId);
            this._dataGridWidget.clear();
        }

        this._setContainerID = nodeId;

        if (this._setContainerID !== null && this._setContainerID !== undefined) {
            //put new node's info into territory rules
            this._selfPatterns = {};
            this._selfPatterns[nodeId] = { "children": 0 };

            this._territoryId = this._client.addUI(this, function (events) {
                self._processSetContainer();
            });
            //update the territory
            this._client.updateTerritory(this._territoryId, this._selfPatterns);
        }
    };

    GridPanelSetsControl.prototype.destroy = function () {
        this.detachClientEventListeners();
        this._client.removeUI(this._territoryId);
    };

    GridPanelSetsControl.prototype._processSetContainer = function () {
        var setContainer = this._client.getNode(this._setContainerID),
            setDescriptor,
            setNames,
            i,
            set,
            setMembers,
            j,
            memberRegistryNames,
            k;

        var gridContent = {};

        this._insertList = [];

        this._dataGridWidget.clear();

        if (setContainer) {
            //get set names
            //get set members
            //get set registries

            setNames = setContainer.getSetNames();

            i = setNames.length;
            while (i--) {
                set = setNames[i];
                setMembers = setContainer.getMemberIds(set);

                //fill set names and member list
                setDescriptor = {"ID": set,
                    "Members": setMembers };

                j = setMembers.length;
                while (j--) {
                    //get set registry
                    memberRegistryNames = setContainer.getMemberRegistryNames(set, setMembers[j]);
                    k = memberRegistryNames.length;
                    while (k--) {
                        var setMemberRegName = /*set + '_' + */memberRegistryNames[k];
                        setDescriptor[setMemberRegName] = setDescriptor[setMemberRegName] || [];
                        var keyValue = setMembers[j] + ': ' + JSON.stringify(setContainer.getMemberRegistry(set, setMembers[j], memberRegistryNames[k]));
                        setDescriptor[setMemberRegName].push(keyValue);
                    }
                }

                this._insertList.push(setDescriptor);
            }

            this._dataGridWidget.insertObjects(this._insertList);
        }
    };

    GridPanelSetsControl.prototype.attachClientEventListeners = function () {
        this.detachClientEventListeners();
        this._client.addEventListener(this._client.events.SELECTEDOBJECT_CHANGED, this._selectedObjectChanged);
    };

    GridPanelSetsControl.prototype.detachClientEventListeners = function () {
        this._client.removeEventListener(this._client.events.SELECTEDOBJECT_CHANGED, this._selectedObjectChanged);
    };

    return GridPanelSetsControl;
});<commit_msg>display node title in panel<commit_after>"use strict";

define(['logManager',
    'clientUtil',
    'js/Constants',
    'js/NodePropertyNames'], function (logManager,
                                    util,
                                    CONSTANTS,
                                    nodePropertyNames) {

    var GridPanelSetsControl;

    GridPanelSetsControl = function (options) {
        var self = this;
        this._client = options.client;
        this._panel = options.panel;
        this._dataGridWidget = options.widget;

        this._dataGridWidget._rowDelete = false;
        this._dataGridWidget._rowEdit = false;

        this._setContainerID = null;

        this._logger = logManager.create("GridPanelSetsControl");

        this._selectedObjectChanged = function (__project, nodeId) {
            self.selectedObjectChanged(nodeId);
        };

        this._logger.debug("Created");
    };

    GridPanelSetsControl.prototype.selectedObjectChanged = function (nodeId) {
        var self = this;

        this._logger.debug("SELECTEDOBJECT_CHANGED nodeId '" + nodeId + "'");

        //remove current territory patterns
        if (this._setContainerID) {
            this._client.removeUI(this._territoryId);
            this._dataGridWidget.clear();
        }

        this._setContainerID = nodeId;

        if (this._setContainerID !== null && this._setContainerID !== undefined) {
            //put new node's info into territory rules
            this._selfPatterns = {};
            this._selfPatterns[nodeId] = { "children": 0 };

            this._territoryId = this._client.addUI(this, function (events) {
                self._processSetContainer();
            });
            //update the territory
            this._client.updateTerritory(this._territoryId, this._selfPatterns);
        }
    };

    GridPanelSetsControl.prototype.destroy = function () {
        this.detachClientEventListeners();
        this._client.removeUI(this._territoryId);
    };

    GridPanelSetsControl.prototype._processSetContainer = function () {
        var setContainer = this._client.getNode(this._setContainerID),
            setDescriptor,
            setNames,
            i,
            set,
            setMembers,
            j,
            memberRegistryNames,
            k,
            title = " (" + this._setContainerID + ")";

        this._insertList = [];

        if (setContainer) {
            title = (setContainer.getAttribute(nodePropertyNames.Attributes.name) || 'N/A') + title;
            //get set names
            //get set members
            //get set registries

            setNames = setContainer.getSetNames();

            i = setNames.length;
            while (i--) {
                set = setNames[i];
                setMembers = setContainer.getMemberIds(set);

                //fill set names and member list
                setDescriptor = {"ID": set,
                    "Members": setMembers };

                j = setMembers.length;
                while (j--) {
                    //get set registry
                    memberRegistryNames = setContainer.getMemberRegistryNames(set, setMembers[j]);
                    k = memberRegistryNames.length;
                    while (k--) {
                        var setMemberRegName = /*set + '_' + */memberRegistryNames[k];
                        setDescriptor[setMemberRegName] = setDescriptor[setMemberRegName] || [];
                        var keyValue = setMembers[j] + ': ' + JSON.stringify(setContainer.getMemberRegistry(set, setMembers[j], memberRegistryNames[k]));
                        setDescriptor[setMemberRegName].push(keyValue);
                    }
                }

                this._insertList.push(setDescriptor);
            }

            this._dataGridWidget.insertObjects(this._insertList);
        }

        this._panel.setTitle(title);
    };

    GridPanelSetsControl.prototype.attachClientEventListeners = function () {
        this.detachClientEventListeners();
        this._client.addEventListener(this._client.events.SELECTEDOBJECT_CHANGED, this._selectedObjectChanged);
    };

    GridPanelSetsControl.prototype.detachClientEventListeners = function () {
        this._client.removeEventListener(this._client.events.SELECTEDOBJECT_CHANGED, this._selectedObjectChanged);
    };

    return GridPanelSetsControl;
});<|endoftext|>


<commit_before>Template.notifications.helpers({
  notifications: function() {
    return Messages.find({receiverId: Meteor.userId(), read: false},
        {sort: {timestamp: -1}});
  },
  notificationCount: function(){
  	return Messages.find({receiverId: Meteor.userId(), read: false}).count();
  },
  getNameFromId: getNameFromId
});

Template.notifications.events({
  'click li.notification': function(e) {
    e.preventDefault();
    e.stopPropagation();

    var target = $(e.currentTarget);
    if (target.hasClass('hide-content')) {
      target.removeClass('hide-content');
    } else {
      Meteor.call('setMessageRead', this._id);
      //Messages.update(this._id, {$set: {read: true}});
    }
  }
});<commit_msg>limit notifications showing in header no more than 10<commit_after>Template.notifications.helpers({
  notifications: function() {
    return Messages.find({receiverId: Meteor.userId(), read: false},
        {sort: {timestamp: -1}, limit: 10});
  },
  notificationCount: function(){
  	return Messages.find({receiverId: Meteor.userId(), read: false}).count();
  },
  getNameFromId: getNameFromId
});

Template.notifications.events({
  'click li.notification': function(e) {
    e.preventDefault();
    e.stopPropagation();

    var target = $(e.currentTarget);
    if (target.hasClass('hide-content')) {
      target.removeClass('hide-content');
    } else {
      Meteor.call('setMessageRead', this._id);
      //Messages.update(this._id, {$set: {read: true}});
    }
  }
});<|endoftext|>


<commit_before>import $ from 'jquery';
import _ from 'underscore';

import AssetstoreCollection from 'girder/collections/AssetstoreCollection';
import AssetstoreModel from 'girder/models/AssetstoreModel';
import EditAssetstoreWidget from 'girder/views/widgets/EditAssetstoreWidget';
import NewAssetstoreWidget from 'girder/views/widgets/NewAssetstoreWidget';
import View from 'girder/views/View';
import { AssetstoreType } from 'girder/constants';
import { cancelRestRequests } from 'girder/rest';
import { confirm } from 'girder/dialog';
import events from 'girder/events';
import { formatSize } from 'girder/misc';
import { getCurrentUser } from 'girder/auth';

import AssetstoresTemplate from 'girder/templates/body/assetstores.pug';

import 'girder/stylesheets/body/assetstores.styl';

import 'as-jqplot/dist/jquery.jqplot.js';
import 'as-jqplot/dist/jquery.jqplot.css'; // jquery.jqplot.min.css
import 'as-jqplot/dist/plugins/jqplot.pieRenderer.js';
import 'bootstrap/js/tooltip';

/**
 * This private data structure is a dynamic way to map assetstore types to the views
 * that should be rendered to import data into them.
 */
import FilesystemImportView from 'girder/views/body/FilesystemImportView';
import S3ImportView from 'girder/views/body/S3ImportView';
var assetstoreImportViewMap = {};
assetstoreImportViewMap[AssetstoreType.FILESYSTEM] = FilesystemImportView;
assetstoreImportViewMap[AssetstoreType.S3] = S3ImportView;

/**
 * This view shows the admin console, which links to all available admin pages.
 */
var AssetstoresView = View.extend({
    events: {
        'click .g-set-current': 'setCurrentAssetstore',
        'click .g-delete-assetstore': 'deleteAssetstore',
        'click .g-edit-assetstore': 'editAssetstore'
    },

    initialize: function (settings) {
        cancelRestRequests('fetch');
        this.assetstoreEdit = settings.assetstoreEdit || false;
        this.importableTypes = [
            AssetstoreType.FILESYSTEM,
            AssetstoreType.S3
        ].concat(settings.importableTypes || []);

        this.newAssetstoreWidget = new NewAssetstoreWidget({
            parentView: this
        }).on('g:created', this.addAssetstore, this);

        // Fetch all of the current assetstores
        this.collection = new AssetstoreCollection();
        this.collection.on('g:changed', function () {
            this.render();
        }, this).fetch();
    },

    render: function () {
        if (!getCurrentUser() || !getCurrentUser().get('admin')) {
            this.$el.text('Must be logged in as admin to view this page.');
            return;
        }
        this.$el.html(AssetstoresTemplate({
            assetstores: this.collection.toArray(),
            types: AssetstoreType,
            importableTypes: this.importableTypes,
            getAssetstoreImportRoute: this.getAssetstoreImportRoute
        }));

        this.newAssetstoreWidget.setElement(this.$('#g-new-assetstore-container')).render();
        this.$('.g-assetstore-button-container[title]').tooltip();

        _.each(this.$('.g-assetstore-capacity-chart'),
            this.capacityChart, this);

        if (this.assetstoreEdit) {
            this.editAssetstoreDialog(this.assetstoreEdit);
            this.assetstoreEdit = false;
        }

        return this;
    },

    addAssetstore: function (assetstore) {
        this.collection.add(assetstore);
        this.render();
    },

    /**
     * Renders the capacities of the assetstores in a pie chart using Chart.js.
     * @param el The canvas element to render in.
     */
    capacityChart: function (el) {
        var assetstore = this.collection.get($(el).attr('cid'));
        var capacity = assetstore.get('capacity');
        var used = capacity.total - capacity.free;
        var data = [
            ['Used (' + formatSize(used) + ')', used],
            ['Free (' + formatSize(capacity.free) + ')', capacity.free]
        ];
        $(el).jqplot([data], {
            seriesDefaults: {
                renderer: $.jqplot.PieRenderer,
                rendererOptions: {
                    sliceMargin: 2,
                    shadow: false,
                    highlightMouseOver: false,
                    showDataLabels: true,
                    padding: 5,
                    startAngle: 180
                }
            },
            legend: {
                show: true,
                location: 'e',
                background: 'transparent',
                border: 'none'
            },
            grid: {
                background: 'transparent',
                border: 'none',
                borderWidth: 0,
                shadow: false
            },
            gridPadding: {top: 10, right: 10, bottom: 10, left: 10}
        });
    },

    setCurrentAssetstore: function (evt) {
        var el = $(evt.currentTarget);
        var assetstore = this.collection.get(el.attr('cid'));
        assetstore.set({current: true});
        assetstore.off('g:saved').on('g:saved', function () {
            events.trigger('g:alert', {
                icon: 'ok',
                text: 'Changed current assetstore.',
                type: 'success',
                timeout: 4000
            });
            this.collection.fetch({}, true);
        }, this).off('g:error').on('g:error', function (err) {
            events.trigger('g:alert', {
                icon: 'cancel',
                text: err.responseJSON.message,
                type: 'danger'
            });
        }).save();
    },

    deleteAssetstore: function (evt) {
        var el = $(evt.currentTarget);
        var assetstore = this.collection.get(el.attr('cid'));

        confirm({
            text: 'Are you sure you want to delete the assetstore ' +
                  assetstore.escape('name') + '?  There are no files ' +
                  'stored in it, and no data will be lost.',
            escapedHtml: true,
            yesText: 'Delete',
            confirmCallback: _.bind(function () {
                assetstore.on('g:deleted', function () {
                    events.trigger('g:alert', {
                        icon: 'ok',
                        text: 'Assetstore deleted.',
                        type: 'success',
                        timeout: 4000
                    });
                    this.collection.fetch({}, true);
                }, this).off('g:error').on('g:error', function (resp) {
                    events.trigger('g:alert', {
                        icon: 'attention',
                        text: resp.responseJSON.message,
                        type: 'danger',
                        timeout: 4000
                    });
                }, this).destroy();
            }, this)
        });
    },

    editAssetstore: function (evt) {
        var cid = $(evt.currentTarget).attr('cid');
        this.editAssetstoreDialog(cid);
    },

    editAssetstoreDialog: function (cid) {
        var assetstore = this.collection.get(cid);
        var container = $('#g-dialog-container');

        var editAssetstoreWidget = new EditAssetstoreWidget({
            el: container,
            model: assetstore,
            parentView: this
        }).off('g:saved').on('g:saved', function () {
            this.render();
        }, this);
        editAssetstoreWidget.render();
    }
}, {
    import: function (assetstoreId) {
        var assetstore = new AssetstoreModel({ _id: assetstoreId });
        assetstore.once('g:fetched', function () {
            var View = assetstoreImportViewMap[assetstore.get('type')];
            if (View) {
                events.trigger('g:navigateTo', View, {
                    assetstore: assetstore
                });
            } else {
                throw new Error('No such view');
            }
        }).fetch();
    }
});

export default AssetstoresView;

<commit_msg>Destroy jqplot in AssetstoresView<commit_after>import $ from 'jquery';
import _ from 'underscore';

import AssetstoreCollection from 'girder/collections/AssetstoreCollection';
import AssetstoreModel from 'girder/models/AssetstoreModel';
import EditAssetstoreWidget from 'girder/views/widgets/EditAssetstoreWidget';
import NewAssetstoreWidget from 'girder/views/widgets/NewAssetstoreWidget';
import View from 'girder/views/View';
import { AssetstoreType } from 'girder/constants';
import { cancelRestRequests } from 'girder/rest';
import { confirm } from 'girder/dialog';
import events from 'girder/events';
import { formatSize } from 'girder/misc';
import { getCurrentUser } from 'girder/auth';

import AssetstoresTemplate from 'girder/templates/body/assetstores.pug';

import 'girder/stylesheets/body/assetstores.styl';

import 'as-jqplot/dist/jquery.jqplot.js';
import 'as-jqplot/dist/jquery.jqplot.css'; // jquery.jqplot.min.css
import 'as-jqplot/dist/plugins/jqplot.pieRenderer.js';
import 'bootstrap/js/tooltip';

/**
 * This private data structure is a dynamic way to map assetstore types to the views
 * that should be rendered to import data into them.
 */
import FilesystemImportView from 'girder/views/body/FilesystemImportView';
import S3ImportView from 'girder/views/body/S3ImportView';
var assetstoreImportViewMap = {};
assetstoreImportViewMap[AssetstoreType.FILESYSTEM] = FilesystemImportView;
assetstoreImportViewMap[AssetstoreType.S3] = S3ImportView;

/**
 * This view shows the admin console, which links to all available admin pages.
 */
var AssetstoresView = View.extend({
    events: {
        'click .g-set-current': 'setCurrentAssetstore',
        'click .g-delete-assetstore': 'deleteAssetstore',
        'click .g-edit-assetstore': 'editAssetstore'
    },

    initialize: function (settings) {
        cancelRestRequests('fetch');
        this.plots = [];
        this.assetstoreEdit = settings.assetstoreEdit || false;
        this.importableTypes = [
            AssetstoreType.FILESYSTEM,
            AssetstoreType.S3
        ].concat(settings.importableTypes || []);

        this.newAssetstoreWidget = new NewAssetstoreWidget({
            parentView: this
        }).on('g:created', this.addAssetstore, this);

        // Fetch all of the current assetstores
        this.collection = new AssetstoreCollection();
        this.collection.on('g:changed', function () {
            this.render();
        }, this).fetch();
    },

    destroy: function () {
        this._destroyPlots();
        View.prototype.destroy.call(this);
    },

    _destroyPlots: function () {
        for (let plot of this.plots) {
            plot.data('jqplot').destroy();
        }
        this.plots = [];
    },

    render: function () {
        if (!getCurrentUser() || !getCurrentUser().get('admin')) {
            this.$el.text('Must be logged in as admin to view this page.');
            return;
        }
        this._destroyPlots();
        this.$el.html(AssetstoresTemplate({
            assetstores: this.collection.toArray(),
            types: AssetstoreType,
            importableTypes: this.importableTypes,
            getAssetstoreImportRoute: this.getAssetstoreImportRoute
        }));

        this.newAssetstoreWidget.setElement(this.$('#g-new-assetstore-container')).render();
        this.$('.g-assetstore-button-container[title]').tooltip();

        _.each(this.$('.g-assetstore-capacity-chart'),
            this.capacityChart, this);

        if (this.assetstoreEdit) {
            this.editAssetstoreDialog(this.assetstoreEdit);
            this.assetstoreEdit = false;
        }

        return this;
    },

    addAssetstore: function (assetstore) {
        this.collection.add(assetstore);
        this.render();
    },

    /**
     * Renders the capacities of the assetstores in a pie chart using Chart.js.
     * @param el The canvas element to render in.
     */
    capacityChart: function (el) {
        var assetstore = this.collection.get($(el).attr('cid'));
        var capacity = assetstore.get('capacity');
        var used = capacity.total - capacity.free;
        var data = [
            ['Used (' + formatSize(used) + ')', used],
            ['Free (' + formatSize(capacity.free) + ')', capacity.free]
        ];
        var plot = $(el).jqplot([data], {
            seriesDefaults: {
                renderer: $.jqplot.PieRenderer,
                rendererOptions: {
                    sliceMargin: 2,
                    shadow: false,
                    highlightMouseOver: false,
                    showDataLabels: true,
                    padding: 5,
                    startAngle: 180
                }
            },
            legend: {
                show: true,
                location: 'e',
                background: 'transparent',
                border: 'none'
            },
            grid: {
                background: 'transparent',
                border: 'none',
                borderWidth: 0,
                shadow: false
            },
            gridPadding: {top: 10, right: 10, bottom: 10, left: 10}
        });
        this.plots.push(plot);
    },

    setCurrentAssetstore: function (evt) {
        var el = $(evt.currentTarget);
        var assetstore = this.collection.get(el.attr('cid'));
        assetstore.set({current: true});
        assetstore.off('g:saved').on('g:saved', function () {
            events.trigger('g:alert', {
                icon: 'ok',
                text: 'Changed current assetstore.',
                type: 'success',
                timeout: 4000
            });
            this.collection.fetch({}, true);
        }, this).off('g:error').on('g:error', function (err) {
            events.trigger('g:alert', {
                icon: 'cancel',
                text: err.responseJSON.message,
                type: 'danger'
            });
        }).save();
    },

    deleteAssetstore: function (evt) {
        var el = $(evt.currentTarget);
        var assetstore = this.collection.get(el.attr('cid'));

        confirm({
            text: 'Are you sure you want to delete the assetstore ' +
                  assetstore.escape('name') + '?  There are no files ' +
                  'stored in it, and no data will be lost.',
            escapedHtml: true,
            yesText: 'Delete',
            confirmCallback: _.bind(function () {
                assetstore.on('g:deleted', function () {
                    events.trigger('g:alert', {
                        icon: 'ok',
                        text: 'Assetstore deleted.',
                        type: 'success',
                        timeout: 4000
                    });
                    this.collection.fetch({}, true);
                }, this).off('g:error').on('g:error', function (resp) {
                    events.trigger('g:alert', {
                        icon: 'attention',
                        text: resp.responseJSON.message,
                        type: 'danger',
                        timeout: 4000
                    });
                }, this).destroy();
            }, this)
        });
    },

    editAssetstore: function (evt) {
        var cid = $(evt.currentTarget).attr('cid');
        this.editAssetstoreDialog(cid);
    },

    editAssetstoreDialog: function (cid) {
        var assetstore = this.collection.get(cid);
        var container = $('#g-dialog-container');

        var editAssetstoreWidget = new EditAssetstoreWidget({
            el: container,
            model: assetstore,
            parentView: this
        }).off('g:saved').on('g:saved', function () {
            this.render();
        }, this);
        editAssetstoreWidget.render();
    }
}, {
    import: function (assetstoreId) {
        var assetstore = new AssetstoreModel({ _id: assetstoreId });
        assetstore.once('g:fetched', function () {
            var View = assetstoreImportViewMap[assetstore.get('type')];
            if (View) {
                events.trigger('g:navigateTo', View, {
                    assetstore: assetstore
                });
            } else {
                throw new Error('No such view');
            }
        }).fetch();
    }
});

export default AssetstoresView;

<|endoftext|>


<commit_before>/**
 * This view shows the admin console, which links to all available admin pages.
 */
girder.views.AssetstoresView = girder.View.extend({
    events: {
        'click .g-set-current': 'setCurrentAssetstore',
        'click .g-delete-assetstore': 'deleteAssetstore'
    },

    initialize: function () {

        // Fetch all of the current assetstores
        if (girder.currentUser && girder.currentUser.get('admin')) {
            this.collection = new girder.collections.AssetstoreCollection();
            this.collection.on('g:changed', function () {
                this.render();
            }, this).fetch();
        }
        else {
            this.render();
        }
        // This page should be re-rendered if the user logs in or out
        girder.events.on('g:login', this.render, this);
    },

    render: function () {
        if (!girder.currentUser || !girder.currentUser.get('admin')) {
            this.$el.text('Must be logged in as admin to view this page.');
            return;
        }
        this.$el.html(jade.templates.assetstores({
            assetstores: this.collection.models,
            types: girder.AssetstoreType
        }));
        this.newAssetstoreWidget = new girder.views.NewAssetstoreWidget({
            el: this.$('#g-new-assetstore-container')
        });
        this.newAssetstoreWidget
            .off().on('g:created', this.addAssetstore, this).render();

        _.each(this.$('.g-assetstore-capacity-chart'),
            this.capacityChart, this);

        return this;
    },

    addAssetstore: function (assetstore) {
        this.collection.add(assetstore);
        this.render();
    },

    /**
     * Renders the capacities of the assetstores in a pie chart using Chart.js.
     * @param el The canvas element to render in.
     */
    capacityChart: function (el) {
        var assetstore = this.collection.get($(el).attr('cid'));
        var capacity = assetstore.get('capacity');
        var data = [
            ['Free', capacity.free],
            ['Used', capacity.total - capacity.free]
        ];
        var plot = $(el).jqplot([data], {
            seriesDefaults: {
                renderer: $.jqplot.PieRenderer,
                rendererOptions: {
                    sliceMargin: 2,
                    shadow: false,
                    highlightMouseOver: false
                }
            },
            legend: {
                show: true,
                location: 'e',
                border: 'none'
            },
            grid: {
                background: '#fff',
                borderColor: '#fff',
                shadow: false
            }
        });
    },

    setCurrentAssetstore: function (evt) {
        var el = $(evt.currentTarget);
        var assetstore = this.collection.get(el.attr('cid'));
        assetstore.set({current: true});
        assetstore.off('g:saved').on('g:saved', function () {
            girder.events.trigger('g:alert', {
                icon: 'ok',
                text: 'Changed current assetstore.',
                type: 'success',
                timeout: 4000
            });
            this.collection.fetch({}, true);
        }, this).save();
    },

    deleteAssetstore: function (evt) {
        var el = $(evt.currentTarget);
        var assetstore = this.collection.get(el.attr('cid'));

        girder.confirm({
            text: 'Are you sure you want to delete the assetstore ' +
                  assetstore.get('name') + '?',
            yesText: 'Delete',
            confirmCallback: _.bind(function () {
                assetstore.on('g:deleted', function () {
                    girder.events.trigger('g:alert', {
                        icon: 'ok',
                        text: 'Assetstore deleted.',
                        type: 'success',
                        timeout: 4000
                    });

                    this.collection.remove(assetstore);
                    this.render();
                }, this).off('g:error').on('g:error', function (resp) {
                    girder.events.trigger('g:alert', {
                        icon: 'attention',
                        text: resp.responseJSON.message,
                        type: 'danger',
                        timeout: 4000
                    });
                }, this).destroy();
            }, this)
        });
    },
});

girder.router.route('assetstores', 'assetstores', function () {
    girder.events.trigger('g:navigateTo', girder.views.AssetstoresView);
});
<commit_msg>Fix failing style test<commit_after>/**
 * This view shows the admin console, which links to all available admin pages.
 */
girder.views.AssetstoresView = girder.View.extend({
    events: {
        'click .g-set-current': 'setCurrentAssetstore',
        'click .g-delete-assetstore': 'deleteAssetstore'
    },

    initialize: function () {

        // Fetch all of the current assetstores
        if (girder.currentUser && girder.currentUser.get('admin')) {
            this.collection = new girder.collections.AssetstoreCollection();
            this.collection.on('g:changed', function () {
                this.render();
            }, this).fetch();
        }
        else {
            this.render();
        }
        // This page should be re-rendered if the user logs in or out
        girder.events.on('g:login', this.render, this);
    },

    render: function () {
        if (!girder.currentUser || !girder.currentUser.get('admin')) {
            this.$el.text('Must be logged in as admin to view this page.');
            return;
        }
        this.$el.html(jade.templates.assetstores({
            assetstores: this.collection.models,
            types: girder.AssetstoreType
        }));
        this.newAssetstoreWidget = new girder.views.NewAssetstoreWidget({
            el: this.$('#g-new-assetstore-container')
        });
        this.newAssetstoreWidget
            .off().on('g:created', this.addAssetstore, this).render();

        _.each(this.$('.g-assetstore-capacity-chart'),
            this.capacityChart, this);

        return this;
    },

    addAssetstore: function (assetstore) {
        this.collection.add(assetstore);
        this.render();
    },

    /**
     * Renders the capacities of the assetstores in a pie chart using Chart.js.
     * @param el The canvas element to render in.
     */
    capacityChart: function (el) {
        var assetstore = this.collection.get($(el).attr('cid'));
        var capacity = assetstore.get('capacity');
        var data = [
            ['Free', capacity.free],
            ['Used', capacity.total - capacity.free]
        ];
        var plot = $(el).jqplot([data], {
            seriesDefaults: {
                renderer: $.jqplot.PieRenderer,
                rendererOptions: {
                    sliceMargin: 2,
                    shadow: false,
                    highlightMouseOver: false
                }
            },
            legend: {
                show: true,
                location: 'e',
                border: 'none'
            },
            grid: {
                background: '#fff',
                borderColor: '#fff',
                shadow: false
            }
        });
    },

    setCurrentAssetstore: function (evt) {
        var el = $(evt.currentTarget);
        var assetstore = this.collection.get(el.attr('cid'));
        assetstore.set({current: true});
        assetstore.off('g:saved').on('g:saved', function () {
            girder.events.trigger('g:alert', {
                icon: 'ok',
                text: 'Changed current assetstore.',
                type: 'success',
                timeout: 4000
            });
            this.collection.fetch({}, true);
        }, this).save();
    },

    deleteAssetstore: function (evt) {
        var el = $(evt.currentTarget);
        var assetstore = this.collection.get(el.attr('cid'));

        girder.confirm({
            text: 'Are you sure you want to delete the assetstore ' +
                  assetstore.get('name') + '?',
            yesText: 'Delete',
            confirmCallback: _.bind(function () {
                assetstore.on('g:deleted', function () {
                    girder.events.trigger('g:alert', {
                        icon: 'ok',
                        text: 'Assetstore deleted.',
                        type: 'success',
                        timeout: 4000
                    });

                    this.collection.remove(assetstore);
                    this.render();
                }, this).off('g:error').on('g:error', function (resp) {
                    girder.events.trigger('g:alert', {
                        icon: 'attention',
                        text: resp.responseJSON.message,
                        type: 'danger',
                        timeout: 4000
                    });
                }, this).destroy();
            }, this)
        });
    }
});

girder.router.route('assetstores', 'assetstores', function () {
    girder.events.trigger('g:navigateTo', girder.views.AssetstoresView);
});
<|endoftext|>


<commit_before>const mysqlAdapter = require('sails-mysql/lib/adapter.js'),
    _ = require('lodash'),
    adapterWrapper = _.clone(mysqlAdapter),
    TransactionConnectionPool = require('../services/TransactionConnectionPool'),
    functionsToWrap = require('../services/WaterlineWrappedFunctions'),
    Deferred = require('waterline/lib/waterline/query/deferred'),
    Promise = require('bluebird'),
    // waterline interception
    dql = require('waterline/lib/waterline/query/dql/index'),
    defaultMethods = require('waterline/lib/waterline/model/lib/defaultMethods'),
    basicFinders = require('waterline/lib/waterline/query/finders/basic'),
    aggregates = require('waterline/lib/waterline/query/aggregate');

_.extend(adapterWrapper, {
    identity: 'sails-mysql-atomic'
});

// Deferred extensions
Deferred.prototype.toPromiseWithTransactionId = function (functionName, transactionId) {
    if (!this._deferred) {
        // here we are starting by cloning the context so we can overwrite safely
        // our query methods to allow injecting the current transaction id in the adapter
        // so we can lookup the right connection to be able to rollback without
        // modifying the actual waterline collections. This will only work with the Promise syntax.
        // Promises are awesome :D
        this._context = _.cloneDeep(this._context);
        _.each(this._context.adapter.connections, c => c._adapter.transactionId = transactionId);

        // overwrite the _model method to place the transaction id in the created model
        // when we need to handle associations
        const originalModel = this._context._model;
        const context = this._context;
        context._model = function(values){
            const newModel = new originalModel(values);
            // overwrite the model save method to pass the current context
            newModel.save = function(options, cb) {
                return new defaultMethods.save(context, this, options, cb);
            };
            return newModel;
        };
        // overwrite the dql methods to remove lodash bind wrapper to allow the context
        // clone transporting the transaction id in the adapters
        _.each(_.keys(dql), key => {
            this._context[key] = dql[key];
            // we need to overwrite it in the waterline collections as well since it is used when creating
            // associations
            _.each(this._context.waterline.collections, collection => collection[key] = dql[key]);
        });

        // same thing for aggregates (createEach, findOrCreateEach)
        _.each(_.keys(aggregates), key => {
            this._context[key] = aggregates[key];
        });

        // overwrite the basic finders methods to remove lodash bind wrapper to allow the context
        // clone transporting the transaction id in the adapters
        _.each(_.keys(basicFinders), key => {
            _.each(this._context.waterline.collections, collection => collection[key] = basicFinders[key]);
        } );
        this._method = dql[functionName] || basicFinders[functionName];
        this._deferred = Promise.promisify(this.exec).bind(this)();
    }
    return this._deferred;
};

// replace the original function to pass the connection if available
_.each(functionsToWrap, functionName => {
    if (_.has(adapterWrapper, functionName)) {
        const originalFunction = adapterWrapper[functionName];
        adapterWrapper[functionName] = function () {
            const transactionId = this.transactionId;
            sails.log.silly(functionName + ':: transactionId: ' + transactionId);
            if (!transactionId) {
                return originalFunction.apply(adapterWrapper, arguments);
            }
            const args = [];
            for (let i = 0; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            const connection = TransactionConnectionPool.getConnectionById(transactionId);
            // push the explicit connection
            args.push(connection);
            return originalFunction.apply(adapterWrapper, args);
        };
    }
});

module.exports = adapterWrapper;<commit_msg>Add debug logs<commit_after>const mysqlAdapter = require('sails-mysql/lib/adapter.js'),
    _ = require('lodash'),
    adapterWrapper = _.clone(mysqlAdapter),
    TransactionConnectionPool = require('../services/TransactionConnectionPool'),
    functionsToWrap = require('../services/WaterlineWrappedFunctions'),
    Deferred = require('waterline/lib/waterline/query/deferred'),
    Promise = require('bluebird'),
    // waterline interception
    dql = require('waterline/lib/waterline/query/dql/index'),
    defaultMethods = require('waterline/lib/waterline/model/lib/defaultMethods'),
    basicFinders = require('waterline/lib/waterline/query/finders/basic'),
    aggregates = require('waterline/lib/waterline/query/aggregate');

_.extend(adapterWrapper, {
    identity: 'sails-mysql-atomic'
});

// Deferred extensions
Deferred.prototype.toPromiseWithTransactionId = function (functionName, transactionId) {
    if (!this._deferred) {
        // here we are starting by cloning the context so we can overwrite safely
        // our query methods to allow injecting the current transaction id in the adapter
        // so we can lookup the right connection to be able to rollback without
        // modifying the actual waterline collections. This will only work with the Promise syntax.
        // Promises are awesome :D
        this._context = _.cloneDeep(this._context);
        console.log(this._context.waterline.schema);
        _.each(this._context.adapter.connections, c => c._adapter.transactionId = transactionId);

        // overwrite the _model method to place the transaction id in the created model
        // when we need to handle associations
        const originalModel = this._context._model;
        const context = this._context;
        context._model = function(values){
            const newModel = new originalModel(values);
            // overwrite the model save method to pass the current context
            newModel.save = function(options, cb) {
                return new defaultMethods.save(context, this, options, cb);
            };
            return newModel;
        };
        // overwrite the dql methods to remove lodash bind wrapper to allow the context
        // clone transporting the transaction id in the adapters
        _.each(_.keys(dql), key => {
            this._context[key] = dql[key];
            // we need to overwrite it in the waterline collections as well since it is used when creating
            // associations
            _.each(this._context.waterline.collections, collection => collection[key] = dql[key]);
        });

        // same thing for aggregates (createEach, findOrCreateEach)
        _.each(_.keys(aggregates), key => {
            this._context[key] = aggregates[key];
        });

        // overwrite the basic finders methods to remove lodash bind wrapper to allow the context
        // clone transporting the transaction id in the adapters
        _.each(_.keys(basicFinders), key => {
            _.each(this._context.waterline.collections, collection => collection[key] = basicFinders[key]);
        } );
        this._method = dql[functionName] || basicFinders[functionName];
        this._deferred = Promise.promisify(this.exec).bind(this)();
    }
    return this._deferred;
};

// replace the original function to pass the connection if available
_.each(functionsToWrap, functionName => {
    if (_.has(adapterWrapper, functionName)) {
        const originalFunction = adapterWrapper[functionName];
        adapterWrapper[functionName] = function () {
            const transactionId = this.transactionId;
            sails.log.silly(functionName + ':: transactionId: ' + transactionId);
            if (!transactionId) {
                return originalFunction.apply(adapterWrapper, arguments);
            }
            const args = [];
            for (let i = 0; i < arguments.length; i++) {
                args.push(arguments[i]);
            }
            const connection = TransactionConnectionPool.getConnectionById(transactionId);
            // push the explicit connection
            args.push(connection);
            return originalFunction.apply(adapterWrapper, args);
        };
    }
});

module.exports = adapterWrapper;<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

'use strict';

const BananaSlugAbstractNodeMeasurer = require('./BananaSlugAbstractNodeMeasurer');
const BananaSlugAbstractNodePresenter = require('./BananaSlugAbstractNodePresenter');
const BananaSlugWebNodeMeasurer = require('./BananaSlugWebNodeMeasurer');
const BananaSlugWebNodePresenter = require('./BananaSlugWebNodePresenter');

import type {
  Agent,
  Measurement,
  Measurer,
  Presenter,
} from './BananaSlugTypes';

import type {ControlState} from '../../frontend/types.js';

const NODE_TYPE_COMPOSITE = 'Composite';

class BananaSlugBackendManager {
  _onMeasureNode: () => void;
  _measurer: Measurer;
  _presenter: Presenter;
  _isActive: boolean;

  constructor(agent: Agent) {
    this._onMeasureNode = this._onMeasureNode.bind(this);

    var useDOM = agent.capabilities.dom;

    this._measurer = useDOM ?
      new BananaSlugWebNodeMeasurer() :
      new BananaSlugAbstractNodeMeasurer();

    this._presenter = useDOM ?
      new BananaSlugWebNodePresenter() :
      new BananaSlugAbstractNodePresenter();

    this._isActive = false;
    agent.on('bananaslugchange', this._onBananaSlugChange.bind(this));
    agent.on('update', this._onUpdate.bind(this, agent));
    agent.on('shutdown', this._shutdown.bind(this));
  }

  _onUpdate(agent: Agent, obj: any) {
    if (
      !obj.publicInstance ||
      !obj.id ||
      obj.nodeType !== NODE_TYPE_COMPOSITE
    ) {
      return;
    }

    var node = agent.getNodeForID(obj.id);
    if (!node) {
      return;
    }

    this._measurer.request(node, this._onMeasureNode);
  }

  _onMeasureNode(measurement: Measurement): void {
    this._presenter.present(measurement);
  }

  _onBananaSlugChange(state: ControlState): void {
    this._presenter.setEnabled(state.enabled);
  }

  _shutdown(): void {
    this._presenter.setEnabled(false);
  }
}

function init(agent: Agent): BananaSlugBackendManager {
  return new BananaSlugBackendManager(agent);
}

module.exports = {
  init,
};
<commit_msg>Avoid unnecessary measurements (#409)<commit_after>/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

'use strict';

const BananaSlugAbstractNodeMeasurer = require('./BananaSlugAbstractNodeMeasurer');
const BananaSlugAbstractNodePresenter = require('./BananaSlugAbstractNodePresenter');
const BananaSlugWebNodeMeasurer = require('./BananaSlugWebNodeMeasurer');
const BananaSlugWebNodePresenter = require('./BananaSlugWebNodePresenter');

import type {
  Agent,
  Measurement,
  Measurer,
  Presenter,
} from './BananaSlugTypes';

import type {ControlState} from '../../frontend/types.js';

const NODE_TYPE_COMPOSITE = 'Composite';

class BananaSlugBackendManager {
  _onMeasureNode: () => void;
  _measurer: Measurer;
  _presenter: Presenter;
  _isActive: boolean;

  constructor(agent: Agent) {
    this._onMeasureNode = this._onMeasureNode.bind(this);

    var useDOM = agent.capabilities.dom;

    this._measurer = useDOM ?
      new BananaSlugWebNodeMeasurer() :
      new BananaSlugAbstractNodeMeasurer();

    this._presenter = useDOM ?
      new BananaSlugWebNodePresenter() :
      new BananaSlugAbstractNodePresenter();

    this._isActive = false;
    agent.on('bananaslugchange', this._onBananaSlugChange.bind(this));
    agent.on('update', this._onUpdate.bind(this, agent));
    agent.on('shutdown', this._shutdown.bind(this));
  }

  _onUpdate(agent: Agent, obj: any) {
    if (
      !this._isActive ||
      !obj.publicInstance ||
      !obj.id ||
      obj.nodeType !== NODE_TYPE_COMPOSITE
    ) {
      return;
    }

    var node = agent.getNodeForID(obj.id);
    if (!node) {
      return;
    }

    this._measurer.request(node, this._onMeasureNode);
  }

  _onMeasureNode(measurement: Measurement): void {
    this._presenter.present(measurement);
  }

  _onBananaSlugChange(state: ControlState): void {
    this._isActive = state.enabled;
    this._presenter.setEnabled(state.enabled);
  }

  _shutdown(): void {
    this._isActive = false;
    this._presenter.setEnabled(false);
  }
}

function init(agent: Agent): BananaSlugBackendManager {
  return new BananaSlugBackendManager(agent);
}

module.exports = {
  init,
};
<|endoftext|>