<commit_before>// dependencies ------------------------------------------------------------

import agave      from '../libs/agave';
import sanitize   from '../libs/sanitize';
import scitran    from '../libs/scitran';
import mongo      from '../libs/mongo';
import async      from 'async';
import config     from '../config';
import {ObjectID} from 'mongodb';
import crypto     from 'crypto';

let c = mongo.collections;

// models ------------------------------------------------------------------

let models = {
	job: {
		appId:      'string, required',
		datasetId:  'string, required',
		userId:     'string, required',
		parameters: 'object, required'
	}
}

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
export default {

	/**
	 * List Apps
	 */
	listApps(req, res, next) {
		agave.listApps((err, resp) => {
			if (err) {return next(err);}
			let apps = resp.body.result;
			async.each(apps, (app, cb) => {
				agave.getApp(app.id, (err, resp2) => {
					app.parameters = resp2.body.result.parameters;
					cb();
				});
			}, () => {
				res.send(resp.body.result);
			});
		});
	},

	/**
	 * Create Job
	 */
	create(req, res, next) {
		sanitize.req(req, models.job, (err, job) => {
			if (err) {return next(err);}
			scitran.downloadSymlinkDataset(job.datasetId, (err, hash) => {
				let parametersHash = crypto.createHash('md5').update(JSON.stringify(job.parameters)).digest('hex');
				let jobName = 'crn-automated-job'

				/* qap body*/
				// let body = {
				// 	name: jobName,
				// 	appId: job.appId,
				// 	batchQueue: "normal",
				// 	executionSystem: "slurm-stampede.tacc.utexas.edu",
				// 	maxRunTime: "00:20:00",
				// 	memoryPerNode: "8GB",
				// 	nodeCount: 1,
				// 	processorsPerNode: 16,
				// 	archive: true,
				// 	archiveSystem: "openfmri-storage",
				// 	archivePath: null,
				// 	inputs: {
				// 		bidsFile: "agave://openfmri-storage/ds003_downsampled.tar"
				// 	},
				// 	parameters: job.parameters,
				// 	notifications: [
				// 		{
				// 			url: config.url + ':' + config.port + '/api/v1/jobs/${JOB_ID}/results',
				// 			event:"*",
				// 			persistent:true
				// 		}
				// 	]
				// };

				let body = {
					"name": jobName,
					"appId": "mriqcp-0.1.0",
					"batchQueue": "normal",
					"executionSystem": "slurm-ls5.tacc.utexas.edu",
					"maxRunTime": "04:00:00",
					"memoryPerNode": "4GB",
					"nodeCount": 1,
					"processorsPerNode": 8,
					"archive": true,
					"archiveSystem": "openfmri-storage",
					"archivePath": null,
					"inputs": {
						"bidsFile": "agave://crn-dev-storage/" + hash
					},
					"parameters": {
					},
					"notifications": [
						{
							"url": config.url + ':' + config.port + '/api/v1/jobs/${JOB_ID}/results',
							"event":"*",
							"persistent":true
						}
					]
				};

				c.jobs.findOne({appId: job.appId, datasetHash: hash, parametersHash: parametersHash}, {}, (err, existingJob) => {
					if (err){return next(err);}
					if (existingJob) {
						let error = new Error("A job with the same dataset and parameters has already been run.");
						error.http_code = 409;
						return next(error);
					}
					agave.createJob(body, (err, resp) => {
						if (resp.body.status == 'error') {
							let error = new Error(resp.body.message);
							error.http_code = 400;
							return next(error);
						}
						c.jobs.insertOne({
							name:           jobName,
							appId:          job.appId,
							datasetId:      job.datasetId,
							datasetHash:    hash,
							userId:         job.userId,
							jobId:          resp.body.result.id,
							agave:          resp.body.result,
							parameters:     job.parameters,
							parametersHash: parametersHash
						}, () => {
							res.send(resp.body);
						});
					});
				});
			});
		});
	},

	/**
	 *  List Jobs
	 */
	listDatasetJobs(req, res, next) {
		let snapshot  = req.query.hasOwnProperty('snapshot') && req.query.snapshot == 'true';
		let datasetId = req.params.datasetId;
		let user      = req.user;

		scitran.getProject(datasetId, (err, resp) => {
			if (resp.body.code && resp.body.code == 404) {
				let error = new Error(resp.body.detail);
				error.http_code = 404;
				return next(error);
			}

			let hasAccess = !!resp.body.public;
			if (resp.body.permissions && !hasAccess) {
				for (let permission of resp.body.permissions) {
					if (permission._id == user) {hasAccess = true; break;}
				}
				if (!hasAccess) {
					let error = new Error('You do not have access to view jobs for this dataset.');
					error.http_code = 404;
					return next(error);
				}
			}

			c.jobs.find({datasetId: datasetId}).toArray((err, jobs) => {
				if (err) {return next(err);}
				res.send(jobs);
			});

		}, {snapshot});
	},

	/**
	 *	Results
	 */
	results(req, res, next) {
		let jobId = req.params.jobId;
		if (req.body.status === 'FINISHED') {
			agave.getJobOutput(req.body.id, (err, resp) => {
				c.jobs.updateOne({jobId}, {$set: {agave: req.body, results: resp.body.result}}, {}).then((err, result) => {
					if (err) {res.send(err);}
					else {res.send(result);}
				});
			});
		} else {
			c.jobs.updateOne({jobId}, {$set: {agave: req.body}}, {}).then((err, result) => {
				if (err) {res.send(err);}
				else {res.send(result);}
			});
		}
	},

	/**
	 * Get Download Ticket
	 */
	getDownloadTicket(req, res, next) {
		let jobId    = req.params.jobId,
			fileName = req.params.fileName;
		c.jobs.findOne({jobId}, {}, (err, job) => {
			if (err){return next(err);}
			if (!job) {
				let error = new Error("Could not find job.");
				error.http_code = 404;
				return next(error);
			}
			scitran.getProject(job.datasetId, (err, resp) => {
				let hasPermission;
				if (!req.user) {
					hasPermission = false;
				} else {
					for (let permission of resp.body.permissions) {
						if (req.user === permission._id) {
							hasPermission = true;
							break;
						}
					}
				}
				if (resp.body.public || hasPermission) {
					let ticket = {
						type: 'download',
						userId: req.user,
						jobId: jobId,
						fileName: fileName,
						created: new Date()
					};
					// Create and return token
					c.tickets.insertOne(ticket, (err, result) => {
						if (err) {return next(err);}
						c.tickets.ensureIndex({created: 1}, {expireAfterSeconds: 60}, (err, index) => {
							res.send(ticket);
						});
					});
				} else {
					let error = new Error("You do not have permission to access this file.");
					error.http_code = 403;
					return next(error);
				}
			}, {snapshot: true});
		});
	},

	/**
	 * Download Results
	 */
	downloadResults(req, res, next) {
		let ticket   = req.query.ticket,
			fileName = req.params.fileName,
			jobId    = req.params.jobId;

		if (!ticket) {
			let error = new Error("No download ticket query parameter found.");
			error.http_code = 400;
			return next(error);
		}

		c.tickets.findOne({_id: ObjectID(ticket), type: 'download', fileName: fileName, jobId: jobId}, {}, (err, result) => {
			if (err) {return next(err);}
			if (!result) {
				let error = new Error("Download ticket was not found or expired");
				error.http_code = 401;
				return next(error);
			}
			let path = 'https://api.tacc.utexas.edu/jobs/v2/' + req.params.jobId + '/outputs/media/out/' + req.params.fileName;

			agave.getFile(path, (err, resp) => {
				res.setHeader('Content-disposition', 'attachment; filename=' + fileName);
				res.setHeader('Content-type', resp.headers['content-type']);
				res.send(resp.body);
			});
		});

	},

	/**
	 * Delete Dataset Jobs
	 *
	 * Takes a dataset ID url parameter and deletes all jobs for that dataset.
	 */
	deleteDatasetJobs(req, res, next) {
		let datasetId = req.params.datasetId;

		scitran.getProject(datasetId, (err, resp) => {
			if (resp.statusCode == 400) {
				let error = new Error("Bad request");
				error.http_code = 400;
				return next(error);
			}
			if (resp.statusCode == 404) {
				let error = new Error("No dataset found");
				error.http_code = 404;
				return next(error);
			}

			let hasPermission;
			if (!req.user) {
				hasPermission = false;
			} else {
				for (let permission of resp.body.permissions) {
					if (req.user === permission._id && permission.access === 'admin') {
						hasPermission = true;
						break;
					}
				}
			}
			if (!resp.body.public && hasPermission) {
				c.jobs.deleteMany({datasetId}, [], (err, doc) => {
					if (err) {return next(err);}
					res.send({message: doc.result.n + " job(s) have been deleted for dataset " + datasetId});
				});
			} else {
				let message = resp.body.public ? "You don't have permission to delete results from public datasets" : "You don't have permission to delete jobs from this dataset.";
				let error = new Error(message);
				error.http_code = 403;
				return next(error);
			}
		});
	}

}
<commit_msg>added condition to allow for apps without parameters<commit_after>// dependencies ------------------------------------------------------------

import agave      from '../libs/agave';
import sanitize   from '../libs/sanitize';
import scitran    from '../libs/scitran';
import mongo      from '../libs/mongo';
import async      from 'async';
import config     from '../config';
import {ObjectID} from 'mongodb';
import crypto     from 'crypto';

let c = mongo.collections;

// models ------------------------------------------------------------------

let models = {
	job: {
		appId:      'string, required',
		datasetId:  'string, required',
		userId:     'string, required',
		parameters: 'object, required'
	}
}

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
export default {

	/**
	 * List Apps
	 */
	listApps(req, res, next) {
		agave.listApps((err, resp) => {
			if (err) {return next(err);}
			let apps = resp.body.result;
			async.each(apps, (app, cb) => {
				agave.getApp(app.id, (err, resp2) => {
				    if (resp2.body && resp2.body.result && resp2.body.result.parameters) {
					app.parameters = resp2.body.result.parameters;
				    }
					cb();
				});
			}, () => {
				res.send(resp.body.result);
			});
		});
	},

	/**
	 * Create Job
	 */
	create(req, res, next) {
		sanitize.req(req, models.job, (err, job) => {
			if (err) {return next(err);}
			scitran.downloadSymlinkDataset(job.datasetId, (err, hash) => {
				let parametersHash = crypto.createHash('md5').update(JSON.stringify(job.parameters)).digest('hex');
				let jobName = 'crn-automated-job'

				/* qap body*/
				// let body = {
				// 	name: jobName,
				// 	appId: job.appId,
				// 	batchQueue: "normal",
				// 	executionSystem: "slurm-stampede.tacc.utexas.edu",
				// 	maxRunTime: "00:20:00",
				// 	memoryPerNode: "8GB",
				// 	nodeCount: 1,
				// 	processorsPerNode: 16,
				// 	archive: true,
				// 	archiveSystem: "openfmri-storage",
				// 	archivePath: null,
				// 	inputs: {
				// 		bidsFile: "agave://openfmri-storage/ds003_downsampled.tar"
				// 	},
				// 	parameters: job.parameters,
				// 	notifications: [
				// 		{
				// 			url: config.url + ':' + config.port + '/api/v1/jobs/${JOB_ID}/results',
				// 			event:"*",
				// 			persistent:true
				// 		}
				// 	]
				// };

				let body = {
					"name": jobName,
					"appId": "mriqcp-0.1.0",
					"batchQueue": "normal",
					"executionSystem": "slurm-ls5.tacc.utexas.edu",
					"maxRunTime": "04:00:00",
					"memoryPerNode": "4GB",
					"nodeCount": 1,
					"processorsPerNode": 8,
					"archive": true,
					"archiveSystem": "openfmri-storage",
					"archivePath": null,
					"inputs": {
						"bidsFile": "agave://crn-dev-storage/" + hash
					},
					"parameters": {
					},
					"notifications": [
						{
							"url": config.url + ':' + config.port + '/api/v1/jobs/${JOB_ID}/results',
							"event":"*",
							"persistent":true
						}
					]
				};

				c.jobs.findOne({appId: job.appId, datasetHash: hash, parametersHash: parametersHash}, {}, (err, existingJob) => {
					if (err){return next(err);}
					if (existingJob) {
						let error = new Error("A job with the same dataset and parameters has already been run.");
						error.http_code = 409;
						return next(error);
					}
					agave.createJob(body, (err, resp) => {
						if (resp.body.status == 'error') {
							let error = new Error(resp.body.message);
							error.http_code = 400;
							return next(error);
						}
						c.jobs.insertOne({
							name:           jobName,
							appId:          job.appId,
							datasetId:      job.datasetId,
							datasetHash:    hash,
							userId:         job.userId,
							jobId:          resp.body.result.id,
							agave:          resp.body.result,
							parameters:     job.parameters,
							parametersHash: parametersHash
						}, () => {
							res.send(resp.body);
						});
					});
				});
			});
		});
	},

	/**
	 *  List Jobs
	 */
	listDatasetJobs(req, res, next) {
		let snapshot  = req.query.hasOwnProperty('snapshot') && req.query.snapshot == 'true';
		let datasetId = req.params.datasetId;
		let user      = req.user;

		scitran.getProject(datasetId, (err, resp) => {
			if (resp.body.code && resp.body.code == 404) {
				let error = new Error(resp.body.detail);
				error.http_code = 404;
				return next(error);
			}

			let hasAccess = !!resp.body.public;
			if (resp.body.permissions && !hasAccess) {
				for (let permission of resp.body.permissions) {
					if (permission._id == user) {hasAccess = true; break;}
				}
				if (!hasAccess) {
					let error = new Error('You do not have access to view jobs for this dataset.');
					error.http_code = 404;
					return next(error);
				}
			}

			c.jobs.find({datasetId: datasetId}).toArray((err, jobs) => {
				if (err) {return next(err);}
				res.send(jobs);
			});

		}, {snapshot});
	},

	/**
	 *	Results
	 */
	results(req, res, next) {
		let jobId = req.params.jobId;
		if (req.body.status === 'FINISHED') {
			agave.getJobOutput(req.body.id, (err, resp) => {
				c.jobs.updateOne({jobId}, {$set: {agave: req.body, results: resp.body.result}}, {}).then((err, result) => {
					if (err) {res.send(err);}
					else {res.send(result);}
				});
			});
		} else {
			c.jobs.updateOne({jobId}, {$set: {agave: req.body}}, {}).then((err, result) => {
				if (err) {res.send(err);}
				else {res.send(result);}
			});
		}
	},

	/**
	 * Get Download Ticket
	 */
	getDownloadTicket(req, res, next) {
		let jobId    = req.params.jobId,
			fileName = req.params.fileName;
		c.jobs.findOne({jobId}, {}, (err, job) => {
			if (err){return next(err);}
			if (!job) {
				let error = new Error("Could not find job.");
				error.http_code = 404;
				return next(error);
			}
			scitran.getProject(job.datasetId, (err, resp) => {
				let hasPermission;
				if (!req.user) {
					hasPermission = false;
				} else {
					for (let permission of resp.body.permissions) {
						if (req.user === permission._id) {
							hasPermission = true;
							break;
						}
					}
				}
				if (resp.body.public || hasPermission) {
					let ticket = {
						type: 'download',
						userId: req.user,
						jobId: jobId,
						fileName: fileName,
						created: new Date()
					};
					// Create and return token
					c.tickets.insertOne(ticket, (err, result) => {
						if (err) {return next(err);}
						c.tickets.ensureIndex({created: 1}, {expireAfterSeconds: 60}, (err, index) => {
							res.send(ticket);
						});
					});
				} else {
					let error = new Error("You do not have permission to access this file.");
					error.http_code = 403;
					return next(error);
				}
			}, {snapshot: true});
		});
	},

	/**
	 * Download Results
	 */
	downloadResults(req, res, next) {
		let ticket   = req.query.ticket,
			fileName = req.params.fileName,
			jobId    = req.params.jobId;

		if (!ticket) {
			let error = new Error("No download ticket query parameter found.");
			error.http_code = 400;
			return next(error);
		}

		c.tickets.findOne({_id: ObjectID(ticket), type: 'download', fileName: fileName, jobId: jobId}, {}, (err, result) => {
			if (err) {return next(err);}
			if (!result) {
				let error = new Error("Download ticket was not found or expired");
				error.http_code = 401;
				return next(error);
			}
			let path = 'https://api.tacc.utexas.edu/jobs/v2/' + req.params.jobId + '/outputs/media/out/' + req.params.fileName;

			agave.getFile(path, (err, resp) => {
				res.setHeader('Content-disposition', 'attachment; filename=' + fileName);
				res.setHeader('Content-type', resp.headers['content-type']);
				res.send(resp.body);
			});
		});

	},

	/**
	 * Delete Dataset Jobs
	 *
	 * Takes a dataset ID url parameter and deletes all jobs for that dataset.
	 */
	deleteDatasetJobs(req, res, next) {
		let datasetId = req.params.datasetId;

		scitran.getProject(datasetId, (err, resp) => {
			if (resp.statusCode == 400) {
				let error = new Error("Bad request");
				error.http_code = 400;
				return next(error);
			}
			if (resp.statusCode == 404) {
				let error = new Error("No dataset found");
				error.http_code = 404;
				return next(error);
			}

			let hasPermission;
			if (!req.user) {
				hasPermission = false;
			} else {
				for (let permission of resp.body.permissions) {
					if (req.user === permission._id && permission.access === 'admin') {
						hasPermission = true;
						break;
					}
				}
			}
			if (!resp.body.public && hasPermission) {
				c.jobs.deleteMany({datasetId}, [], (err, doc) => {
					if (err) {return next(err);}
					res.send({message: doc.result.n + " job(s) have been deleted for dataset " + datasetId});
				});
			} else {
				let message = resp.body.public ? "You don't have permission to delete results from public datasets" : "You don't have permission to delete jobs from this dataset.";
				let error = new Error(message);
				error.http_code = 403;
				return next(error);
			}
		});
	}

}
<|endoftext|>


<commit_before>"use strict";

var program = require("commander"),
    pkg = require("../package.json"),
    request = require("request"),
    crypto = require("crypto"),
    MailParser = require("mailparser").MailParser;

function randomBase64(len) {
  return new Buffer(crypto.randomBytes(len)).toString("base64");
}

function mailgunHash(timestamp, token) {
  var hmac = crypto.createHmac("sha256", program.key);
  hmac.update(timestamp + token);
  return hmac.digest("hex");
}

exports.argv = function(argv) {
  program
    .version(pkg.version)
    .option("-k, --key <key>", "encryption key (required)")
    .option("-d, --dest <url>", "url to post")
    .parse(argv);

  if (!program.key) {
    program.help();
  }

  var mailparser = new MailParser();

  mailparser.on("end", function(mail) {
    var formData = {
      from: mail.headers.from,
      subject: mail.subject,
      "body-plain": mail.text
    };
    formData.timestamp = (new Date()).getTime();
    formData.token     = randomBase64(50);
    formData.signature = mailgunHash(formData.timestamp, formData.token);

    if (program.dest) {
      request.post({
        uri: program.dest,
        form: formData
      }, function() {
        console.log("OK");
      });
    } else {
      console.log(JSON.stringify(formData));
    }
    process.exit();
  });

  var stdin = process.openStdin();
  stdin.on("data", function(chunk) {
    mailparser.write(chunk);
  });
  stdin.on("end", function() {
    mailparser.end();
  });
}
<commit_msg>fixed that process exit before post<commit_after>"use strict";

var program = require("commander"),
    pkg = require("../package.json"),
    request = require("request"),
    crypto = require("crypto"),
    MailParser = require("mailparser").MailParser;

function randomBase64(len) {
  return new Buffer(crypto.randomBytes(len)).toString("base64");
}

function mailgunHash(timestamp, token) {
  var hmac = crypto.createHmac("sha256", program.key);
  hmac.update(timestamp + token);
  return hmac.digest("hex");
}

exports.argv = function(argv) {
  program
    .version(pkg.version)
    .option("-k, --key <key>", "encryption key (required)")
    .option("-d, --dest <url>", "url to post")
    .parse(argv);

  if (!program.key) {
    program.help();
  }

  var mailparser = new MailParser();

  mailparser.on("end", function(mail) {
    var formData = {
      from: mail.headers.from,
      subject: mail.subject,
      "body-plain": mail.text
    };
    formData.timestamp = (new Date()).getTime();
    formData.token     = randomBase64(50);
    formData.signature = mailgunHash(formData.timestamp, formData.token);

    if (program.dest) {
      request.post({
        uri: program.dest,
        form: formData
      }, function(error, response, body) {
        console.log("OK");
        process.exit();
      });
    } else {
      console.log(JSON.stringify(formData));
      process.exit();
    }
  });

  var stdin = process.openStdin();
  stdin.on("data", function(chunk) {
    mailparser.write(chunk);
  });
  stdin.on("end", function() {
    mailparser.end();
  });
}
<|endoftext|>


<commit_before>var request = require('request');
var qs = require('qs');

var youtube = function(){

    var self = this;

    /**
     * API v3 Url
     * @type {string}
     */
    self.url = 'https://www.googleapis.com/youtube/v3/';

    /**
     * params
     * https://developers.google.com/youtube/v3/docs/search/list
     * @type {Object}
     */
    self.params = {};

    self.parts = [];

    /**
     * Set private key to class
     * @param key
     */
    self.setKey = function( key ){
        self.addParam( 'key', key );
    }

    /**
     *
     * @param name
     */
    self.addPart = function( name ){
        self.parts.push( name );
    }

    /**
     *
     * Optional parameters
     * https://developers.google.com/youtube/v3/docs/search/list
     *
     * @param key
     * @param value
     */
    self.addParam = function( key, value ){
        self.params[key] = value;
    }

    /**
     *
     * @param path
     * @returns {string}
     */
    self.getUrl = function( path ){
        return self.url + path + '?' + qs.stringify( self.params );
    }

    /**
     *
     * @returns {string}
     */
    self.getParts = function(){
        return self.parts.join(',');
    }

    /**
     * Simple http request
     * @param url
     * @param callback
     */
    self.request = function( url, callback ){
        request( url, function (error, response, body) {
            if( error ){
                console.log( error );
            }
            else{
                if( response.statusCode == 200 ){
                    var data = JSON.parse(body);
                    callback( data );
                }
                else{
                    var errorResponse = {
                        error : {
                            message: 'Bad request.'
                        }
                    }
                    callback( errorResponse );
                }
            }
        });
    }

    /**
     * Video data from ID
     * @param id
     * @param callback
     */
    self.getById = function( id, callback ){
        self.parts = [];

        self.addPart('snippet');
        self.addPart('contentDetails');
        self.addPart('statistics');
        self.addPart('status');

        self.addParam( 'part', self.getParts() );
        self.addParam( 'id', id );

        self.request( self.getUrl('videos'), function( response ){
            callback( response );
        });
    }

    /**
     * Videos data from query
     * @param query
     * @param maxResults
     * @param callback
     */
    self.search = function( query, maxResults, callback ){
        self.parts = [];

        self.addPart( 'snippet' );

        self.addParam( 'part', self.getParts() );
        self.addParam( 'q', query );
        self.addParam( 'maxResults', maxResults );

        self.request( self.getUrl('search'), function( response ){
            if( response.error ){
                callback( response );
            }
            else{
                callback( response );
            }
        });
    }

}

module.exports = new youtube();
<commit_msg>best practices on code style<commit_after>var request = require('request');
var queryString = require('querystring');

var youtube = function() {

  var self = this;

  /**
  * API v3 Url
  * @type {string}
  */
  self.url = 'https://www.googleapis.com/youtube/v3/';

  /**
  * params
  * https://developers.google.com/youtube/v3/docs/search/list
  * @type {Object}
  */
  self.params = {};

  self.parts = [];

  /**
  * Set private key to class
  * @param {string} key
  */
  self.setKey = function(key) {
    self.addParam('key', key);
  }

  /**
  *
  * @param {string} name
  */
  self.addPart = function(name) {
    self.parts.push(name);
  }

    /**
     *
     * Optional parameters
     * https://developers.google.com/youtube/v3/docs/search/list
     *
     * @param {string} key
     * @param {string} value
     */
  self.addParam = function(key, value) {
    self.params[key] = value;
  }

  /**
  *
  * @param {string} path
  * @returns {string}
  */
  self.getUrl = function(path) {
    return self.url + path + '?' + queryString.stringify(self.params);
  }

  /**
  *
  * @returns {string}
  */
  self.getParts = function() {
    return self.parts.join(',');
  }

  /**
  * Simple http request
  * @param {string} url
  * @param {string} callback
  */
  self.request = function(url, callback) {
    request(url, function(error, response, body) {
      if (error) {
        console.log(error);
      }
      else {
        if (response.statusCode == 200) {
          var data = JSON.parse(body);
          callback(data);
        }
        else {
          var errorResponse = {
            error : {
              message: 'Bad request.'
            }
          }
          callback(errorResponse);
        }
      }
    });
  }

  /**
  * Video data from ID
  * @param {string} id
  * @param {function} callback
  */
  self.getById = function(id, callback) {
    self.parts = [];

    self.addPart('snippet');
    self.addPart('contentDetails');
    self.addPart('statistics');
    self.addPart('status');

    self.addParam('part', self.getParts());
    self.addParam('id', id);

    self.request(self.getUrl('videos'), function(response) {
      callback(response);
    });
  }

  /**
  * Videos data from query
  * @param {string} query
  * @param {int} maxResults
  * @param {function} callback
  */
  self.search = function(query, maxResults, callback) {
    self.parts = [];

    self.addPart('snippet');

    self.addParam('part', self.getParts());
    self.addParam('q', query);
    self.addParam('maxResults', maxResults);

    self.request(self.getUrl('search'), function(response) {
      if (response.error) {
        callback(response);
      }
      else {
        callback(response);
      }
    });
  }
}

module.exports = new youtube();
<|endoftext|>


<commit_before>var L = require('leaflet'),
	Nominatim = require('./geocoders/nominatim').class;

module.exports = {
	class: L.Control.extend({
		options: {
			showResultIcons: false,
			collapsed: true,
			expand: 'click',
			position: 'topright',
			placeholder: 'Search...',
			errorMessage: 'Nothing found.',
			suggestMinLength: 3,
			suggestTimeout: 250
		},

		_callbackId: 0,

		initialize: function (options) {
			L.Util.setOptions(this, options);
			if (!this.options.geocoder) {
				this.options.geocoder = new Nominatim();
			}
		},

		onAdd: function (map) {
			var className = 'leaflet-control-geocoder',
			    container = L.DomUtil.create('div', className + ' leaflet-bar'),
			    icon = L.DomUtil.create('button', 'leaflet-control-geocoder-icon', container),
			    form = this._form = L.DomUtil.create('div', className + '-form', container),
			    input;

			icon.innerHTML = ' ';
			icon.type = 'button';
			this._map = map;
			this._container = container;
			input = this._input = L.DomUtil.create('input');
			input.type = 'text';
			input.placeholder = this.options.placeholder;

			L.DomEvent.addListener(input, 'keydown', this._keydown, this);
			L.DomEvent.addListener(input, 'blur', function() {
				if (this.options.collapsed) {
					this._collapse();
				}
			}, this);

			this._errorElement = document.createElement('div');
			this._errorElement.className = className + '-form-no-error';
			this._errorElement.innerHTML = this.options.errorMessage;

			this._alts = L.DomUtil.create('ul', className + '-alternatives leaflet-control-geocoder-alternatives-minimized');

			form.appendChild(input);
			this._container.appendChild(this._errorElement);
			container.appendChild(this._alts);

			if (this.options.collapsed) {
				if (this.options.expand === 'click') {
					L.DomEvent.addListener(icon, 'click', function(e) {
						// TODO: touch
						if (e.button === 0 && e.detail !== 2) {
							this._toggle();
						}
					}, this);
				} else {
					L.DomEvent.addListener(icon, 'mouseover', this._expand, this);
					L.DomEvent.addListener(icon, 'mouseout', this._collapse, this);
					this._map.on('movestart', this._collapse, this);
				}
			} else {
				L.DomEvent.addListener(icon, 'click', function(e) {
					this._geocode(e);
				}, this);
				this._expand();
			}

			L.DomEvent.disableClickPropagation(container);

			return container;
		},

		_geocodeResult: function (results, suggest) {
			L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-throbber');
			if (!suggest && results.length === 1) {
				this._geocodeResultSelected(results[0]);
			} else if (results.length > 0) {
				this._alts.innerHTML = '';
				this._results = results;
				L.DomUtil.removeClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
				for (var i = 0; i < results.length; i++) {
					this._alts.appendChild(this._createAlt(results[i], i));
				}
			} else {
				L.DomUtil.addClass(this._errorElement, 'leaflet-control-geocoder-error');
			}
		},

		markGeocode: function(result) {
			this._map.fitBounds(result.bbox);

			if (this._geocodeMarker) {
				this._map.removeLayer(this._geocodeMarker);
			}

			this._geocodeMarker = new L.Marker(result.center)
				.bindPopup(result.html || result.name)
				.addTo(this._map)
				.openPopup();

			return this;
		},

		_geocode: function(suggest) {
			this._lastGeocode = this._input.value;
			L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-throbber');
			if (!suggest) {
				this._clearResults();
			}
			this.options.geocoder.geocode(this._input.value, function(results) {
				this._geocodeResult(results, suggest);
			}, this);
		},

		_geocodeResultSelected: function(result) {
			if (this.options.collapsed) {
				this._collapse();
			} else {
				this._clearResults();
			}
			this.markGeocode(result);
		},

		_toggle: function() {
			if (this._container.className.indexOf('leaflet-control-geocoder-expanded') >= 0) {
				this._collapse();
			} else {
				this._expand();
			}
		},

		_expand: function () {
			L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-expanded');
			this._input.select();
		},

		_collapse: function () {
			this._container.className = this._container.className.replace(' leaflet-control-geocoder-expanded', '');
			L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
			L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
		},

		_clearResults: function () {
			L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
			this._selection = null;
			L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
		},

		_createAlt: function(result, index) {
			var li = L.DomUtil.create('li', ''),
				a = L.DomUtil.create('a', '', li),
			    icon = this.options.showResultIcons && result.icon ? L.DomUtil.create('img', '', a) : null,
			    text = result.html ? undefined : document.createTextNode(result.name),
			    clickHandler = function clickHandler(e) {
					L.DomEvent.preventDefault(e);
					this._geocodeResultSelected(result);
				};

			if (icon) {
				icon.src = result.icon;
			}

			li.setAttribute('data-result-index', index);

			if (result.html) {
				a.innerHTML = a.innerHTML + result.html;
			} else {
				a.appendChild(text);
			}

			L.DomEvent.addListener(li, 'click', clickHandler, this);

			return li;
		},

		_keydown: function(e) {
			var _this = this,
			    select = function select(dir) {
					if (_this._selection) {
						L.DomUtil.removeClass(_this._selection, 'leaflet-control-geocoder-selected');
						_this._selection = _this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];
					}
					if (!_this._selection) {
						_this._selection = _this._alts[dir > 0 ? 'firstChild' : 'lastChild'];
					}

					if (_this._selection) {
						L.DomUtil.addClass(_this._selection, 'leaflet-control-geocoder-selected');
					}
				};

			switch (e.keyCode) {
			// Escape
			case 27:
				if (this.options.collapsed) {
					this._collapse();
				}
				break;
			// Up
			case 38:
				select(-1);
				L.DomEvent.preventDefault(e);
				break;
			// Up
			case 40:
				select(1);
				L.DomEvent.preventDefault(e);
				break;
			// Enter
			case 13:
				if (this._selection) {
					var index = parseInt(this._selection.getAttribute('data-result-index'), 10);
					this._geocodeResultSelected(this._results[index]);
					this._clearResults();
					L.DomEvent.preventDefault(e);
				} else {
					this._geocode();
				}
				break;
			default:
				var v = this._input.value;
				if (this.options.geocoder.suggest && v !== this._lastGeocode) {
					clearTimeout(this._suggestTimeout);
					if (v.length >= this.options.suggestMinLength) {
						this._suggestTimeout = setTimeout(L.bind(function() {
							this._geocode(true);
						}, this), this.options.suggestTimeout);
					} else {
						this._clearResults();
					}
				}
			}
		}
	}),
	factory: function(options) {
		return new L.Control.Geocoder(options);
	}
};
<commit_msg>Refactor onAdd<commit_after>var L = require('leaflet'),
	Nominatim = require('./geocoders/nominatim').class;

module.exports = {
	class: L.Control.extend({
		options: {
			showResultIcons: false,
			collapsed: true,
			expand: 'click',
			position: 'topright',
			placeholder: 'Search...',
			errorMessage: 'Nothing found.',
			suggestMinLength: 3,
			suggestTimeout: 250
		},

		_callbackId: 0,

		initialize: function (options) {
			L.Util.setOptions(this, options);
			if (!this.options.geocoder) {
				this.options.geocoder = new Nominatim();
			}
		},

		onAdd: function (map) {
			var className = 'leaflet-control-geocoder',
			    container = L.DomUtil.create('div', className + ' leaflet-bar'),
			    icon = L.DomUtil.create('button', className + '-icon', container),
			    form = this._form = L.DomUtil.create('div', className + '-form', container),
			    input;

			this._map = map;
			this._container = container;

			icon.innerHTML = ' ';
			icon.type = 'button';

			input = this._input = L.DomUtil.create('input', '', form);
			input.type = 'text';
			input.placeholder = this.options.placeholder;

			this._errorElement = L.DomUtil.create('div', className + '-form-no-error', container);
			this._errorElement.innerHTML = this.options.errorMessage;

			this._alts = L.DomUtil.create('ul',
				className + '-alternatives leaflet-control-geocoder-alternatives-minimized',
				container);

			L.DomEvent.addListener(input, 'keydown', this._keydown, this);
			L.DomEvent.addListener(input, 'blur', function() {
				if (this.options.collapsed) {
					this._collapse();
				}
			}, this);

			if (this.options.collapsed) {
				if (this.options.expand === 'click') {
					L.DomEvent.addListener(icon, 'click', function(e) {
						// TODO: touch
						if (e.button === 0 && e.detail !== 2) {
							this._toggle();
						}
					}, this);
				} else {
					L.DomEvent.addListener(icon, 'mouseover', this._expand, this);
					L.DomEvent.addListener(icon, 'mouseout', this._collapse, this);
					this._map.on('movestart', this._collapse, this);
				}
			} else {
				L.DomEvent.addListener(icon, 'click', function(e) {
					this._geocode(e);
				}, this);
				this._expand();
			}

			L.DomEvent.disableClickPropagation(container);

			return container;
		},

		_geocodeResult: function (results, suggest) {
			L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-throbber');
			if (!suggest && results.length === 1) {
				this._geocodeResultSelected(results[0]);
			} else if (results.length > 0) {
				this._alts.innerHTML = '';
				this._results = results;
				L.DomUtil.removeClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
				for (var i = 0; i < results.length; i++) {
					this._alts.appendChild(this._createAlt(results[i], i));
				}
			} else {
				L.DomUtil.addClass(this._errorElement, 'leaflet-control-geocoder-error');
			}
		},

		markGeocode: function(result) {
			this._map.fitBounds(result.bbox);

			if (this._geocodeMarker) {
				this._map.removeLayer(this._geocodeMarker);
			}

			this._geocodeMarker = new L.Marker(result.center)
				.bindPopup(result.html || result.name)
				.addTo(this._map)
				.openPopup();

			return this;
		},

		_geocode: function(suggest) {
			this._lastGeocode = this._input.value;
			L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-throbber');
			if (!suggest) {
				this._clearResults();
			}
			this.options.geocoder.geocode(this._input.value, function(results) {
				this._geocodeResult(results, suggest);
			}, this);
		},

		_geocodeResultSelected: function(result) {
			if (this.options.collapsed) {
				this._collapse();
			} else {
				this._clearResults();
			}
			this.markGeocode(result);
		},

		_toggle: function() {
			if (this._container.className.indexOf('leaflet-control-geocoder-expanded') >= 0) {
				this._collapse();
			} else {
				this._expand();
			}
		},

		_expand: function () {
			L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-expanded');
			this._input.select();
		},

		_collapse: function () {
			this._container.className = this._container.className.replace(' leaflet-control-geocoder-expanded', '');
			L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
			L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
		},

		_clearResults: function () {
			L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
			this._selection = null;
			L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
		},

		_createAlt: function(result, index) {
			var li = L.DomUtil.create('li', ''),
				a = L.DomUtil.create('a', '', li),
			    icon = this.options.showResultIcons && result.icon ? L.DomUtil.create('img', '', a) : null,
			    text = result.html ? undefined : document.createTextNode(result.name),
			    clickHandler = function clickHandler(e) {
					L.DomEvent.preventDefault(e);
					this._geocodeResultSelected(result);
				};

			if (icon) {
				icon.src = result.icon;
			}

			li.setAttribute('data-result-index', index);

			if (result.html) {
				a.innerHTML = a.innerHTML + result.html;
			} else {
				a.appendChild(text);
			}

			L.DomEvent.addListener(li, 'click', clickHandler, this);

			return li;
		},

		_keydown: function(e) {
			var _this = this,
			    select = function select(dir) {
					if (_this._selection) {
						L.DomUtil.removeClass(_this._selection, 'leaflet-control-geocoder-selected');
						_this._selection = _this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];
					}
					if (!_this._selection) {
						_this._selection = _this._alts[dir > 0 ? 'firstChild' : 'lastChild'];
					}

					if (_this._selection) {
						L.DomUtil.addClass(_this._selection, 'leaflet-control-geocoder-selected');
					}
				};

			switch (e.keyCode) {
			// Escape
			case 27:
				if (this.options.collapsed) {
					this._collapse();
				}
				break;
			// Up
			case 38:
				select(-1);
				L.DomEvent.preventDefault(e);
				break;
			// Up
			case 40:
				select(1);
				L.DomEvent.preventDefault(e);
				break;
			// Enter
			case 13:
				if (this._selection) {
					var index = parseInt(this._selection.getAttribute('data-result-index'), 10);
					this._geocodeResultSelected(this._results[index]);
					this._clearResults();
					L.DomEvent.preventDefault(e);
				} else {
					this._geocode();
				}
				break;
			default:
				var v = this._input.value;
				if (this.options.geocoder.suggest && v !== this._lastGeocode) {
					clearTimeout(this._suggestTimeout);
					if (v.length >= this.options.suggestMinLength) {
						this._suggestTimeout = setTimeout(L.bind(function() {
							this._geocode(true);
						}, this), this.options.suggestTimeout);
					} else {
						this._clearResults();
					}
				}
			}
		}
	}),
	factory: function(options) {
		return new L.Control.Geocoder(options);
	}
};
<|endoftext|>


<commit_before>import frontMatter from 'front-matter';
import walkHtml from 'hastml';
import { decode as decodeEntities } from 'he';
import HighlightJS from 'highlight.js';
import MarkdownIt from './jsx-friendly-markdown-it';

import formatImport from './formatters/import';
import formatModule from './formatters/module';
import formatStatic from './formatters/static';
import formatEscape from './formatters/js-escape';
import StringReplacementCache from './string-replacement-cache';

const ASSIGNMENT_EXPRESSION_PREFIX = 'mclAssignmentBeginI';
const ASSIGNMENT_EXPRESSION_SUFFIX = 'IEnd';
const ASSIGNMENT_EXPRESSION_REGEXP = (
  // Assignment Expression IDs are 55 lower-case characters long
  `${ASSIGNMENT_EXPRESSION_PREFIX}[a-z]{55}${ASSIGNMENT_EXPRESSION_SUFFIX}`
);
const ASSIGNMENT_EXPRESSION_COMMENT_REGEXP = (
  `{/\\*(${ASSIGNMENT_EXPRESSION_REGEXP})\\*/}`
);

const DEFAULT_CONFIGURATION = {
  implicitlyImportReact: true,
  passElementProps: false,
  markdownItPlugins: []
};

export default (source, config) => {
  config = Object.assign({}, DEFAULT_CONFIGURATION, config);

  const invalidStatics = ['propTypes'];
  const imports = [];

  // Import React and PropTypes unless we've been asked otherwise
  if (config.implicitlyImportReact) {
    imports.push(formatImport('React', 'react'));
    imports.push(formatImport('PropTypes', 'prop-types'));
  }

  // Pull out imports & front-matter
  const { body: markdown, attributes: { imports: importMap, ...staticAttributes } } = frontMatter(source);

  // Add additional imports
  if (importMap) {
    Object.keys(importMap).forEach((name) => {
      imports.push(formatImport(name, importMap[name]));
    });
  }

  // Disallow passing `defaultProps` if we're passing our own
  if (config.passElementProps) {
    invalidStatics.push('defaultProps');
  }

  // Add additional statics
  const statics = Object.keys(staticAttributes).map((attribute) => {
    if (invalidStatics.indexOf(attribute) !== -1) {
      throw new Error(`You can't supply a \`${attribute}\` static! That name is reserved.`);
    }

    return formatStatic(attribute, staticAttributes[attribute]);
  });

  // Hold onto JSX properties and assignment expressions before converting
  const markdownTagIndexes = [];

  // Find all opening or void HTML tags
  walkHtml(
    markdown,
    (match, tagFragment, offset, string, tag) => {
      // Once we get a tag which is closing
      if (typeof tag.closeIndex === 'number') {
        // Push its start and end coordinates into our list
        if (typeof tag.contentIndex === 'number') {
          markdownTagIndexes.push([tag.openIndex, tag.contentIndex]);
        } else {
          markdownTagIndexes.push([tag.openIndex, tag.closeIndex]);
        }
      }
    }
  );

  let offsetForPropertyReplacements = 0;
  let markdownSansJsxProperties = markdown;

  // Then, within each HTML tag we found, replace any assignment expressions
  const jsxPropertyCache = new StringReplacementCache(
    /[\w]+={[^}]*}\s*}?|{\s*\.\.\.[^}]*}/g
  );

  markdownTagIndexes.forEach(([start, end]) => {
    const startIndex = start + offsetForPropertyReplacements;
    const endIndex = end + offsetForPropertyReplacements;

    const tagWithNoReplacements = markdownSansJsxProperties.slice(startIndex, endIndex);
    const tagWithPropertyReplacements = jsxPropertyCache.load(tagWithNoReplacements);

    markdownSansJsxProperties = markdownSansJsxProperties.slice(0, startIndex) + tagWithPropertyReplacements + markdownSansJsxProperties.slice(endIndex);

    offsetForPropertyReplacements += tagWithPropertyReplacements.length - tagWithNoReplacements.length;
  });

  // Replace all remaining double-brace assignment expressions with comments
  const assignmentExpressionCache = new StringReplacementCache(
    /{({\s*(?:<.*?>|.*?)\s*})}/g,
    (match, value) => value,
    (identityHash) => `${ASSIGNMENT_EXPRESSION_PREFIX}${identityHash}${ASSIGNMENT_EXPRESSION_SUFFIX}`
  );

  const markdownSansAssignments = assignmentExpressionCache.load(markdownSansJsxProperties);

  // Configure Markdown renderer, highlight code snippets, and post-process
  let renderer = new MarkdownIt()
    .configure('commonmark')
    .enable(['smartquotes'])
    .set({
      // We need explicit line breaks
      breaks: true,
      typographer: config.typographer,
      highlight(code, languageHint) {
        let highlightedContent;

        HighlightJS.configure({ tabReplace: '  ' });

        // Try highlighting with a given hint
        if (languageHint && HighlightJS.getLanguage(languageHint)) {
          try {
            highlightedContent = HighlightJS.highlight(languageHint, code).value;
          } catch (err) {} // eslint-disable-line no-empty
        }

        // Highlight without a hint
        if (!highlightedContent) {
          try {
            highlightedContent = HighlightJS.highlightAuto(code).value;
          } catch (err) {} // eslint-disable-line no-empty
        }

        return highlightedContent
          .replace(/\n/g, '
');
      }
    });

  // Load MarkdownIt plugins
  if (config.markdownItPlugins && Array.isArray(config.markdownItPlugins)) {
    renderer = config.markdownItPlugins
      .reduce(
        (markdownRenderer, pluginDefinition) => (
          markdownRenderer.use(...(
            Array.isArray(pluginDefinition)
              ? pluginDefinition
              : [pluginDefinition]
          ))
        ),
        renderer
      );
  }

  // Render markdown to HTML, and replace assignment expressions with comments
  const html = (
    renderer.render(markdownSansAssignments) || ''
  ).replace(
    new RegExp(ASSIGNMENT_EXPRESSION_REGEXP, 'g'),
    ''
  );

  // Collect all the HTML tags and their positions
  const htmlTags = [];

  walkHtml(
    html,
    (match, tagFragment, offset, string, tag) => {
      if (htmlTags.indexOf(tag) === -1) {
        htmlTags.push(tag);
      }
    }
  );

  const htmlOffsets = [0];

  htmlTags.forEach((tag) => {
    const { state, openIndex, contentIndex, closeIndex, closingIndex } = tag;

    if (typeof openIndex === 'number') {
      htmlOffsets.push(openIndex);
    }

    if (typeof contentIndex === 'number') {
      // contentIndex + 1 because contentIndex if the offset of '>'
      htmlOffsets.push(contentIndex + 1);
    }

    if (typeof closingIndex === 'number') {
      htmlOffsets.push(closingIndex);
    }

    if (typeof closeIndex === 'number') {
      if (state === 'open') {
        if (html[closeIndex] === '-') {
          htmlOffsets.push(closeIndex + 3);
        } else {
          htmlOffsets.push(closeIndex + 2);
        }
      } else {
        htmlOffsets.push(closeIndex + 1);
      }
    }
  });

  // Here, we collect all the positions at which SGML tags begin or end
  let jsx = htmlOffsets
    .sort((first, second) => first - second)
    .filter((number, index, array) => !index || number !== array[index - 1])
    .reduce(
      (acc, item, index, array) => (
        acc.concat([html.slice(item, array[index + 1])])
      ),
      []
    )
    .map((fragment) => {
      // Then we check, for each of them, whether they are a tag or a text node
      if (fragment[0] === '<' || fragment[fragment.length - 1] === '>') {
        // If they're tags, we check whether they're a comment,
        if (fragment.slice(0, 4) === ''
  ).replace(
    new RegExp(ASSIGNMENT_EXPRESSION_REGEXP, 'g'),
    ''
  );

  // Collect all the HTML tags and their positions
  const htmlOffsets = [0];

  walkHtml(
    html,
    (match, tagFragment, offset) => {
      if (tagFragment[0] === '<') {
        htmlOffsets.push(offset);
      } else { // ∴ tagFragment[tagFragment.length - 1] === '>'
        htmlOffsets.push(offset + tagFragment.length);
      }
    }
  );

  // Here, we collect all the positions at which SGML tags begin or end
  let jsx = htmlOffsets
    .reduce(
      (acc, item, index, array) => (
        acc.concat([html.slice(item, array[index + 1])])
      ),
      []
    )
    .map((fragment) => {
      // Then we check, for each of them, whether they are a tag or a text node
      if (fragment[0] === '<' || fragment[fragment.length - 1] === '>') {
        // If they're tags, we check whether they're a comment,
        if (fragment.slice(0, 4) === '


<commit_before>var _ = require('lodash');
var util = require('util');
var request = require('request');
var moment = require('moment');
var EventEmitter = require('events').EventEmitter;
var ripple = require('ripple-lib');
var sjcl = ripple.sjcl;
var check = require('check-types');
var Promise = require('bluebird')

/* --------------------------------- CONSTS --------------------------------- */

var DEBUG = true;

var REQUEST_STATUS = {
  QUEUED: 1,
  REQUESTING: 2
};

var DEFAULT_PORT = 51235

var IPP_PATTERN = /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\:([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])\b/;

/* --------------------------------- HELPERS -------------------------------- */

function abortIfNot(expression, message) {
  if (!expression) {
    if (!DEBUG) {
      throw new Error(message);
    } else {
      console.error(message);
      process.exit();
    }
  }
}

function crawlUrl(domainOrIp) {
  return 'https://' + withDefaultPort(domainOrIp) + '/crawl';
}

function withDefaultPort(domainOrIp) {
  return domainOrIp.indexOf(':') !== -1 ? domainOrIp :
                                          domainOrIp + ':' + DEFAULT_PORT;
}

function normalizePubKey(pubKeyStr) {
  if (pubKeyStr.length > 50 && pubKeyStr[0] == 'n') {
    return pubKeyStr;
  }

  var bits = sjcl.codec.base64.toBits(pubKeyStr);
  var bytes = sjcl.codec.bytes.fromBits(bits);
  return ripple.Base.encode_check(ripple.Base.VER_NODE_PUBLIC, bytes);
}

/*
* deals with a variety of (ip, port) possibilities that occur
* in rippled responses and normalizes them to the format 'ip:port'
*/
function normalizeIpp(ip, port) {
  if (ip) {
    var split = ip.split(':'),
        splitIp = split[0],
        splitPort = split[1];

    out_ip = splitIp
    out_port = port || splitPort || DEFAULT_PORT
    var ipp = out_ip + ':' + out_port
  } else {
    throw new Error("ip is undefined")
  }

  return ipp
}

/* --------------------------------- CRAWLER -------------------------------- */

function Crawler(maxRequests, logger) {
  EventEmitter.call(this);

  // maxRequests checks
  maxRequests = maxRequests ? maxRequests : 30;
  check.assert.number(maxRequests, "Invalid max requests");
  if (maxRequests < 1) {
    throw new Error("Invalid max requests");
  }

  // logger checks
  logger = logger ? logger : console
  if (!logger.log || !logger.error) {
    throw new Error("Invalid logger");
  }
  if (typeof logger.log != "function" || typeof logger.error != "function") {
    throw new TypeError("log and error must be functions");
  }

  this.maxRequests = maxRequests;
  this.currentRequests = 0; // active requests
  this.rawResponses = {}; // {$ip_and_port : $normalised_response}
  this.queued = {}; // {$ip_and_port : REQUEST_STATUS.*}
  this.errors = {}; // {$ip_and_port : $error_code_int}
  this.peersData = {}; // {b58_normed(pubKey) : {...}}
  this.logger = logger;
}

util.inherits(Crawler, EventEmitter);

Crawler.prototype.getCrawl = function(entryIp) {
  var self = this;
  return new Promise(function(resolve, reject) {
    if (entryIp === undefined) {
      throw new Error("Invalid ip address")
    }
    if (entryIp.split(' ').length != 1 || !IPP_PATTERN.test(entryIp)) {
      throw new Error("Invalid ip address (perhaps port missing)")
    }
    self.once('done', function(response) {
      return resolve(response)
    }).enter(entryIp)
  })
}

/*
* Enter at ip to start crawl
*/
Crawler.prototype.enter = function(ip) {
  this.startTime = moment().format();
  this.entryIP = withDefaultPort(ip);
  this.crawl(withDefaultPort(ip), 0);
};

/**
* @param {String} ipp - ip and port to crawl
* @param {Number} hops - from initial entryPoint
*/
Crawler.prototype.crawl = function(ipp, hops) {
  var self = this;
  self.queued[ipp] = REQUEST_STATUS.REQUESTING;

  self.crawlOne(ipp, function(error, response, body) {
    self.dequeue(ipp);

    if (error) {
      self.logger.error(ipp, 'has error:', error);
      self.errors[ipp] = error;
    } else {
      // save raw body
      self.rawResponses[ipp] = body;

      // Normalize body and loop over each normalized peer
      body.overlay.active.forEach(function(p) {
        try {
          self.enqueueIfNeeded(normalizeIpp(p.ip, p.port));
        } catch (error) {
          self.logger.error(p.public_key, 'has error:', error);
        }
      });
    }

    // Crawl peers
    if (!self.requestMore(hops)) {
      self.endTime = moment().format();
      self.emit('done', { start:    self.startTime,
                          end:      self.endTime,
                          entry:    self.entryIP,
                          data:     self.rawResponses,
                          errors:   self.errors
                        });
    }
  });
};

/*
* Crawls over one node at ipp and retreives json
*/
Crawler.prototype.crawlOne = function(ipp, cb) {
  var self;
  this.currentRequests++;
  self = this;
  self.crawlRequest(ipp, function(err, response, json) {
    self.currentRequests--;
    self.emit('request', err, response, json);
    cb(err, response, json);
  });
};

/*
* Actually sends the request to retreive the json
*/
Crawler.prototype.crawlRequest = function(ip, onResponse) {
  var self = this;
  var options = { url: crawlUrl(ip),
                  timeout: 5000,
                  rejectUnauthorized: false,
                  requestCert: true,
                  agent: false };
  request(options, function(err, response, body) {
    onResponse(err, response, body ? JSON.parse(body) : null);
  });
}

Crawler.prototype.requestMore = function(hops) {
  var self = this;
  var ipps = Object.keys(self.queued);

  ipps.forEach(function(queuedIpp) {
    if (self.currentRequests < self.maxRequests) {
      if (self.queued[queuedIpp] === REQUEST_STATUS.QUEUED) {
        self.crawl(queuedIpp, hops + 1);
      }
    } else {
      return false;
    }
  });

  return ipps.length !== 0;
}

/*
* Enqueue if this node hasn't already been crawled
*/
Crawler.prototype.enqueueIfNeeded = function(ipp) {
  if (ipp) {
    if ((this.rawResponses[ipp] === undefined) &&
        (this.queued[ipp] === undefined) &&
        (this.errors[ipp] === undefined)) {
      this.enqueue(ipp);
    }
  }
}

Crawler.prototype.enqueue = function(ipp) {
  abortIfNot(this.queued[ipp] === undefined, 'queued already');
  this.queued[ipp] = REQUEST_STATUS.QUEUED;
};

Crawler.prototype.dequeue = function(ipp) {
  abortIfNot(this.queued[ipp] !== undefined, 'not queued already');
  delete this.queued[ipp];
};

exports.Crawler = Crawler;
exports.normalizeIpp = normalizeIpp;
exports.normalizePubKey = normalizePubKey;

<commit_msg>change src/crawler.js returned json to match new strucuture described in readme<commit_after>var _ = require('lodash');
var util = require('util');
var request = require('request');
var moment = require('moment');
var EventEmitter = require('events').EventEmitter;
var ripple = require('ripple-lib');
var sjcl = ripple.sjcl;
var check = require('check-types');
var Promise = require('bluebird')

/* --------------------------------- CONSTS --------------------------------- */

var DEBUG = true;

var REQUEST_STATUS = {
  QUEUED: 1,
  REQUESTING: 2
};

var DEFAULT_PORT = 51235

var IPP_PATTERN = /\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\:([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])\b/;

/* --------------------------------- HELPERS -------------------------------- */

function abortIfNot(expression, message) {
  if (!expression) {
    if (!DEBUG) {
      throw new Error(message);
    } else {
      console.error(message);
      process.exit();
    }
  }
}

function crawlUrl(domainOrIp) {
  return 'https://' + withDefaultPort(domainOrIp) + '/crawl';
}

function withDefaultPort(domainOrIp) {
  return domainOrIp.indexOf(':') !== -1 ? domainOrIp :
                                          domainOrIp + ':' + DEFAULT_PORT;
}

function normalizePubKey(pubKeyStr) {
  if (pubKeyStr.length > 50 && pubKeyStr[0] == 'n') {
    return pubKeyStr;
  }

  var bits = sjcl.codec.base64.toBits(pubKeyStr);
  var bytes = sjcl.codec.bytes.fromBits(bits);
  return ripple.Base.encode_check(ripple.Base.VER_NODE_PUBLIC, bytes);
}

/*
* deals with a variety of (ip, port) possibilities that occur
* in rippled responses and normalizes them to the format 'ip:port'
*/
function normalizeIpp(ip, port) {
  if (ip) {
    var split = ip.split(':'),
        splitIp = split[0],
        splitPort = split[1];

    out_ip = splitIp
    out_port = port || splitPort || DEFAULT_PORT
    var ipp = out_ip + ':' + out_port
  } else {
    throw new Error("ip is undefined")
  }

  return ipp
}

/* --------------------------------- CRAWLER -------------------------------- */

function Crawler(maxRequests, logger) {
  EventEmitter.call(this);

  // maxRequests checks
  maxRequests = maxRequests ? maxRequests : 30;
  check.assert.number(maxRequests, "Invalid max requests");
  if (maxRequests < 1) {
    throw new Error("Invalid max requests");
  }

  // logger checks
  logger = logger ? logger : console
  if (!logger.log || !logger.error) {
    throw new Error("Invalid logger");
  }
  if (typeof logger.log != "function" || typeof logger.error != "function") {
    throw new TypeError("log and error must be functions");
  }

  this.maxRequests = maxRequests;
  this.currentRequests = 0; // active requests
  this.queued = {}; // {$ip_and_port : REQUEST_STATUS.*}
  this.done = {}; // {$ip_and_port : 1}
  this.rawResponses = []; // [{$ip_and_port : $raw_response}]
  this.errors = []; // {$ip_and_port/public_key : $error_object}
  this.logger = logger;
}

util.inherits(Crawler, EventEmitter);

Crawler.prototype.getCrawl = function(entryIp) {
  var self = this;
  return new Promise(function(resolve, reject) {
    if (entryIp === undefined) {
      throw new Error("Invalid ip address")
    }
    if (entryIp.split(' ').length != 1 || !IPP_PATTERN.test(entryIp)) {
      throw new Error("Invalid ip address (perhaps port missing)")
    }
    self.once('done', function(response) {
      return resolve(response)
    }).enter(entryIp)
  })
}

/*
* Enter at ip to start crawl
*/
Crawler.prototype.enter = function(ip) {
  this.startTime = moment().format();
  this.entryIP = withDefaultPort(ip);
  this.crawl(withDefaultPort(ip), 0);
};

/**
* @param {String} ipp - ip and port to crawl
* @param {Number} hops - from initial entryPoint
*/
Crawler.prototype.crawl = function(ipp, hops) {
  var self = this;
  self.queued[ipp] = REQUEST_STATUS.REQUESTING;

  self.crawlOne(ipp, function(error, response, body) {
    self.dequeue(ipp);

    if (error) {
      // save error
      var err = {}
      err[ipp] = error;
      self.errors.push(err);

      // log error
      self.logger.error(ipp, 'has error:', error);
    } else {

      // mark ipp as done (received response)
      self.done[ipp] = 1;

      // save raw body
      var resp = {};
      resp[ipp] = body;
      self.rawResponses.push(resp);

      // Normalize body and loop over each normalized peer
      body.overlay.active.forEach(function(p) {
        try {
          self.enqueueIfNeeded(normalizeIpp(p.ip, p.port));
        } catch (error) {
          self.logger.error(p.public_key, 'has error:', error);
        }
      });
    }

    // Crawl peers
    if (!self.requestMore(hops)) {
      self.endTime = moment().format();
      self.emit('done', { start:    self.startTime,
                          end:      self.endTime,
                          entry:    self.entryIP,
                          data:     self.rawResponses,
                          errors:   self.errors
                        });
    }
  });
};

/*
* Crawls over one node at ipp and retreives json
*/
Crawler.prototype.crawlOne = function(ipp, cb) {
  var self;
  this.currentRequests++;
  self = this;
  self.crawlRequest(ipp, function(err, response, json) {
    self.currentRequests--;
    self.emit('request', err, response, json);
    cb(err, response, json);
  });
};

/*
* Actually sends the request to retreive the json
*/
Crawler.prototype.crawlRequest = function(ip, onResponse) {
  var self = this;
  var options = { url: crawlUrl(ip),
                  timeout: 5000,
                  rejectUnauthorized: false,
                  requestCert: true,
                  agent: false };
  request(options, function(err, response, body) {
    onResponse(err, response, body ? JSON.parse(body) : null);
  });
}

Crawler.prototype.requestMore = function(hops) {
  var self = this;
  var ipps = Object.keys(self.queued);

  ipps.forEach(function(queuedIpp) {
    if (self.currentRequests < self.maxRequests) {
      if (self.queued[queuedIpp] === REQUEST_STATUS.QUEUED) {
        self.crawl(queuedIpp, hops + 1);
      }
    } else {
      return false;
    }
  });

  return ipps.length !== 0;
}

/*
* Enqueue if this node hasn't already been crawled
*/
Crawler.prototype.enqueueIfNeeded = function(ipp) {
  if (ipp) {
    if ((this.done[ipp] === undefined) &&
        (this.queued[ipp] === undefined) &&
        (this.errors[ipp] === undefined)) {
      this.enqueue(ipp);
    }
  }
}

Crawler.prototype.enqueue = function(ipp) {
  abortIfNot(this.queued[ipp] === undefined, 'queued already');
  this.queued[ipp] = REQUEST_STATUS.QUEUED;
};

Crawler.prototype.dequeue = function(ipp) {
  abortIfNot(this.queued[ipp] !== undefined, 'not queued already');
  delete this.queued[ipp];
};

exports.Crawler = Crawler;
exports.normalizeIpp = normalizeIpp;
exports.normalizePubKey = normalizePubKey;

<|endoftext|>


<commit_before>var static = require('node-static'),
    router = new require('routes').Router(),
    routil = require('routil'),
    isDefined = require('./thinair').isDefined,
    pd = require('pd'),
    qsObjects = require('qs-objects');

var file = new(static.Server)('./public');

var Router = {
    // registers a route
    add: function(uri, fn) {
        router.addRoute(uri, fn);
    },

    // match a route and execute its related function
    match: function(req, res) {
        // matching a route
        var route = router.match(req.url);

        // if a route is matched, executing the reponse function
        if (route) {
            try {
                if (req.method == 'POST') {
                    // Converting query string to objects
                    var buffer = '';

                    req.on('data', function(chunk) {
                        buffer += chunk.toString();
                    });

                    req.on('end', function() {
                        var body = qsObjects(buffer);

                        // merging body inside req
                        req = pd.extend(Object.create(req), { body: body });

                        route.fn(req, res, route.params, route.splats);
                    });
                } else {
                    route.fn(req, res, route.params, route.splats);
                }
            } catch (e) {
                // If something wrong happens, shoot the error stack.
                if (process.env.ENVIRONMENT === 'DEV') {
                    // TODOTB: Maybe make some kind of custom page for errors.
                    routil.sendHtml(res, '
' + e.stack + '
');
                } else {
                    routil.errorPage(req, res, 500);
                }
            }
        } else {
            // if no route is matched, try to find a static file to serve
            file.serve(req, res, function (e, res) {
                // If the file wasn't found, send 404
                if (e && (e.status === 404)) {
                    routil.errorPage(req, res, 404);
                }
            });
        }
    },

    // gets a requested action, and handles its parameters
    getAction: function(controller, action, parameters) {
        var that = this;

        return function(req, res, params, splats) {
            var controllerObject = that.controllers[controller];

            // TODOTB: Re-implement this.
            // if (isDefined(parameters)) {
            //     if (isDefined(parameters.checkIfAuthorized)) {
            //         sessionsHelper.checkIfAuthorized(req, res);
            //     }
            // }

            return controllerObject[action](req, res, params, splats);
        };
    }
};

module.exports = Router;<commit_msg>reintroduced 404<commit_after>var static = require('node-static'),
    router = new require('routes').Router(),
    routil = require('routil'),
    isDefined = require('./thinair').isDefined,
    pd = require('pd'),
    qsObjects = require('qs-objects');

var file = new(static.Server)('./public');

var Router = {
    // registers a route
    add: function(uri, fn) {
        router.addRoute(uri, fn);
    },

    // match a route and execute its related function
    match: function(req, res) {
        // matching a route
        var route = router.match(req.url);

        // if a route is matched, executing the reponse function
        if (route) {
            try {
                if (req.method == 'POST') {
                    // Converting query string to objects
                    var buffer = '';

                    req.on('data', function(chunk) {
                        buffer += chunk.toString();
                    });

                    req.on('end', function() {
                        var body = qsObjects(buffer);

                        // merging body inside req
                        req = pd.extend(Object.create(req), { body: body });

                        route.fn(req, res, route.params, route.splats);
                    });
                } else {
                    route.fn(req, res, route.params, route.splats);
                }
            } catch (e) {
                // If something wrong happens, shoot the error stack.
                if (process.env.ENVIRONMENT === 'DEV') {
                    // TODOTB: Maybe make some kind of custom page for errors.
                    routil.sendHtml(res, '
' + e.stack + '
');
                } else {
                    routil.errorPage(req, res, 500);
                }
            }
        } else {
            var thatReq = req,
                thatRes = res;

            // if no route is matched, try to find a static file to serve
            file.serve(req, res, function (e, res) {
                // If the file wasn't found, send 404
                if (e && (e.status === 404)) {
                    routil.errorPage(thatReq, thatRes, 404);
                }
            });
        }
    },

    // gets a requested action, and handles its parameters
    getAction: function(controller, action, parameters) {
        var that = this;

        return function(req, res, params, splats) {
            var controllerObject = that.controllers[controller];

            // TODOTB: Re-implement this.
            // if (isDefined(parameters)) {
            //     if (isDefined(parameters.checkIfAuthorized)) {
            //         sessionsHelper.checkIfAuthorized(req, res);
            //     }
            // }

            return controllerObject[action](req, res, params, splats);
        };
    }
};

module.exports = Router;<|endoftext|>


<commit_before>/* global ObjectId */

// Our general requirements
const mongoose = require('mongoose');
const path = require("path");
const config = require("config");
const JXPHelper = require('jxp-helper');
const modeldir = require("./modeldir");

// Set up our jxp-helper so that we call call the API from within the API (if we've set config.apikey)
const jxp_settings = {};
if (config.apikey) jxp_settings.apikey = config.apikey;
if (config.server) jxp_settings.server = config.server;
if (jxp_settings.apikey && jxp_settings.server) global.jxphelper = new JXPHelper(jxp_settings);

// Set some global types
global.ObjectId = mongoose.Schema.Types.ObjectId;
global.Mixed = mongoose.Schema.Types.Mixed;
// Add this to the top of your model to avoid eslint warnings: /* global ObjectId Mixed */

const model_dir = modeldir.findModelDir(path.dirname(process.argv[1]));

const getModelFileFromRef = ref => {
    return path.join(model_dir, `${String(ref).toLowerCase()}_model`);
}

class Schema extends mongoose.Schema {
    constructor(definition, opts) {
        // Set default options
        opts = Object.assign({
            timestamps: true,
            toJSON: { virtuals: true },
            toObject: { virtuals: true }
        }, opts);
        // Set default defiitions
        definition = Object.assign({
            _deleted: { type: Boolean, default: false, index: true },
            _owner_id: { type: ObjectId, link: "User", map_to: "_owner" }
        }, definition);
        // construct our parent
        super(definition, opts);
        // Some properties
        this.opts = opts;
        this.definition = definition;
        // Action!
        this.setPerms();
        this.generateLinks();
    }

    setPerms() {
        // Create the perms
        this.set("_perms", Object.assign({
            admin: "", // CRUD = Create, Retrieve, Update and Delete
            owner: "",
            user: "",
            all: "" // Unauthenticated users will be able to read from test, but that is all
        }, this.opts.perms));
    }

    generateLinks() {
        // Find the links in our definitions and create virtuals for non-destructive populating
        // Example: link_id: { type: ObjectId, link: "link", }
        // Example with a custom key: link_id: { type: ObjectId, link: "link", map_to: "custom_name" }
        for (let key of Object.keys(this.definition)) {
            let def = this.definition[key];
            let is_array = false;
            if (def[0]) {
                def = def[0];
                is_array = true;
            }
            if (!def.link) continue;
            const virtual_name = def.map_to || def.virtual || String(def.link).toLowerCase();
            require(getModelFileFromRef(def.link));
            this.virtual(virtual_name, {
                ref: def.link,
                localField: key,
                foreignField: "_id",
                justOne: def.justOne || !is_array,
                options: Object.assign({}, def.options)
            })
        }
    }
}

// Now we don't even need to import mongoose into our models for type
Schema.Types = mongoose.Schema.Types;

// Slightly self-referential, try not to think about it
Schema.model = mongoose.model;

module.exports = Schema;<commit_msg>- updated_by field - Only load required files once<commit_after>/* global ObjectId */

// Our general requirements
const mongoose = require('mongoose');
const path = require("path");
const config = require("config");
const JXPHelper = require('jxp-helper');
const modeldir = require("./modeldir");

// Set up our jxp-helper so that we call call the API from within the API (if we've set config.apikey)
const jxp_settings = {};
if (config.apikey) jxp_settings.apikey = config.apikey;
if (config.server) jxp_settings.server = config.server;
if (jxp_settings.apikey && jxp_settings.server) global.jxphelper = new JXPHelper(jxp_settings);

// Set some global types
global.ObjectId = mongoose.Schema.Types.ObjectId;
global.Mixed = mongoose.Schema.Types.Mixed;
// Add this to the top of your model to avoid eslint warnings: /* global ObjectId Mixed */

const model_dir = modeldir.findModelDir(path.dirname(process.argv[1]));

const getModelFileFromRef = ref => {
    return path.join(model_dir, `${String(ref).toLowerCase()}_model`);
}

class Schema extends mongoose.Schema {
    constructor(definition, opts) {
        // Set default options
        opts = Object.assign({
            timestamps: true,
            toJSON: { virtuals: true },
            toObject: { virtuals: true }
        }, opts);
        // Set default defiitions
        definition = Object.assign({
            _deleted: { type: Boolean, default: false, index: true },
            _owner_id: { type: ObjectId, link: "User", map_to: "_owner" },
            _updated_by_id: { type: ObjectId, link: "User", map_to: "_updated_by" },
        }, definition);
        // construct our parent
        super(definition, opts);
        // Some properties
        this.opts = opts;
        this.definition = definition;
        // Action!
        this.setPerms();
        this.generateLinks();
    }

    setPerms() {
        // Create the perms
        this.set("_perms", Object.assign({
            admin: "", // CRUD = Create, Retrieve, Update and Delete
            owner: "",
            user: "",
            all: "" // Unauthenticated users will be able to read from test, but that is all
        }, this.opts.perms));
    }

    generateLinks() {
        let loaded_files = [];
        // Find the links in our definitions and create virtuals for non-destructive populating
        // Example: link_id: { type: ObjectId, link: "link", }
        // Example with a custom key: link_id: { type: ObjectId, link: "link", map_to: "custom_name" }
        for (let key of Object.keys(this.definition)) {
            let def = this.definition[key];
            let is_array = false;
            if (def[0]) {
                def = def[0];
                is_array = true;
            }
            if (!def.link) continue;
            const virtual_name = def.map_to || def.virtual || String(def.link).toLowerCase();
            if (!loaded_files.includes(def.link)) {
                require(getModelFileFromRef(def.link));
                loaded_files.push(def.link);
            }
            this.virtual(virtual_name, {
                ref: def.link,
                localField: key,
                foreignField: "_id",
                justOne: def.justOne || !is_array,
                options: Object.assign({}, def.options)
            })
        }
    }
}

// Now we don't even need to import mongoose into our models for type
Schema.Types = mongoose.Schema.Types;

// Slightly self-referential, try not to think about it
Schema.model = mongoose.model;

module.exports = Schema;<|endoftext|>


<commit_before>/**
 * Main diagram class.
 *
 * @copyright   copyright (c) 2015 by Harald Lapp
 * @author      Harald Lapp <harald@octris.org>
 */

;(function(flowdesigner) {
    var wire = flowdesigner.wire;
    var node = flowdesigner.node;

    /*
     * zoom inspired by StableZoom
     * https://github.com/mberth/PanAndZoom/blob/master/app/scripts/pan_and_zoom.coffee
     */
    function zoom(old_zoom, delta, c, p) {
        var factor = 1.05;
        var new_zoom = (delta < 0
                        ? old_zoom * factor
                        : (delta > 0
                            ? old_zoom / factor
                            : old_zoom));

        return {
            zoom: new_zoom,
            offset: p.subtract(p.subtract(c).multiply(old_zoom / new_zoom)).subtract(c)
        };
    }

    /**
     * Constructor.
     *
     * @param   mixed       canvas              Canvas selector.
     * @param   object      options             Optional options.
     */
    function diagram(canvas, options)
    {
        this.canvas = paper.setup(canvas);
        this.options = $.extend({'raster': 0}, options || {});
        this.nodes = {};
        this.scopes = {};

        this.layers = {
            'wires': new this.canvas.Layer(),
            'nodes': new this.canvas.Layer(),
            'draw': new this.canvas.Layer()
        };

        this.wire = new wire(this);

        // disable context menu
        $('#' + canvas).bind('contextmenu', function() {
            return false;
        });

        // zoom
        $('#' + canvas).mousewheel(function(event) {
            var pos = paper.view.viewToProject(new paper.Point(event.offsetX, event.offsetY));
            var ret = zoom(paper.view.zoom, event.deltaY, paper.view.center, pos);

            paper.view.zoom = ret.zoom;
            paper.view.center = paper.view.center.add(ret.offset);

            event.preventDefault();

            paper.view.draw();
        });

        // pan
        (function() {
            var tool = new paper.Tool();
            var drag = false;
            var point = {x: 0, y: 0};

            tool.onMouseDrag = function(event) {
                if (drag) {
                    var delta = {
                        x: point.x - event.event.offsetX,
                        y: point.y - event.event.offsetY
                    };

                    point = {x: event.event.offsetX, y: event.event.offsetY};

                    paper.view.center = paper.view.center.add(new paper.Point(delta.x, delta.y));

                    event.stopPropagation();
                }
            }
            tool.onMouseUp = function(event) {
                drag = false;
            }

            $('#' + canvas).on({
                mousedown: function(event) {
                    if (event.shiftKey) {
                        tool.activate();

                        drag = true;
                        point = {x: event.offsetX, y: event.offsetY};
                    }
                }
            });
        })();
    }

    /**
     * Define a connector scope.
     *
     * @param   string      name                Name of scope.
     * @param   object      settings            Scope settings.
     */
    diagram.prototype.defineScope = function(name, settings)
    {
        this.scopes[name] = settings;
    }

    /**
     * Test if scope is available.
     *
     * @param   string      name                Name of scope.
     * @return  bool                            Returns true if scope is available.
     */
    diagram.prototype.hasScope = function(name)
    {
        return (name in this.scopes);
    }

    /**
     * Return scope settings.
     *
     * @param   string      name                Name of scope.
     * @return  object                          Scope settings.
     */
    diagram.prototype.getScope = function(name)
    {
        return this.scopes[name];
    }

    /**
     * Test if node of specified Id is available.
     *
     * @param   string      id                  Id of node.
     * @return  bool                            Returns true if node is available.
     */
    diagram.prototype.hasNode = function(id)
    {
        return (id in this.nodes);
    }

    /**
     * Return instance of node.
     *
     * @param   string      id                  Id of node to return.
     * @return  flowdesigner.node               Instance of node.
     */
    diagram.prototype.getNode = function(id)
    {
        return this.nodes[id];
    }

    /**
     * Return and activate layer of specified name. Returns the canvas layer, if no layer name
     * is specified.
     *
     * @param   string      name                Name of layer to get.
     * @return  node                            Layer node.
     */
    diagram.prototype.getLayer = function(name)
    {
        var layer = (typeof name !== 'undefined'
                        ? this.layers[name]
                        : this.canvas);

        layer.activate();

        return layer;
    }

    /**
     * Export nodes and wires as json data structure.
     *
     * @return  object                          Defined nodes and wires.
     */
    diagram.prototype.exportJson = function()
    {
        var data = {
            'nodes': [],
            'wires': []
        };

        for (var i in this.nodes) {
            data.nodes.push(this.nodes[i].getData());
        }

        data.wires = this.wire.exportWires();

        return data;
    }

    /**
     * Import json data structure of nodes and wires.
     *
     * @param   object              data        Data structure to import.
     */
    diagram.prototype.importJson = function(data)
    {
        data.nodes.forEach(function(node) {
            this.addNode()
        });

        data.wires.forEach(function(wire) {
            this.addWire(wire);
        }, this);
    }

    /**
     * Add an instance of a node to the diagram.
     *
     * @param   flowdesigner.node   node        Instance of node.
     * @return  object                          Instance of created node.
     */
    diagram.prototype.addNode = function(node)
    {
        if (!(node instanceof flowdesigner.node)) {
            throw new Error('Invalid parameter specified');
        }

        var id = node.getId();
        var me = this;

        this.nodes[id] = node;
    }

    /**
     * Add a single wire to diagram.
     *
     * @param   object      wire                Wire to add.
     */
    diagram.prototype.addWire = function(wire)
    {
        this.wire.addWire(wire.source, wire.target);
    }

    /**
     * Remove node of the specified ID.
     *
     * @param   string      id                  ID of node to remove.
     */
    diagram.prototype.removeNode = function(id)
    {
        var node = this.nodes[id];
        node.destroy();

        delete this.nodes[id];
    }

    /**
     * Remove multiple nodes.
     *
     * @param   array       ids                 Array of IDs of nodes to remove.
     */
    diagram.prototype.removeNodes = function(ids)
    {
        ids.forEach(function(id) {
            this.removeNode(id);
        }, this);
    }

    /**
     * Remove all nodes from diagram.
     */
    diagram.prototype.removeAllNodes = function()
    {
        this.removeNodes(Object.keys(this.nodes));
    }

    flowdesigner.diagram = diagram;
})(flowdesigner);
<commit_msg>bugfix<commit_after>/**
 * Main diagram class.
 *
 * @copyright   copyright (c) 2015 by Harald Lapp
 * @author      Harald Lapp <harald@octris.org>
 */

;(function(flowdesigner) {
    var wire = flowdesigner.wire;
    var node = flowdesigner.node;

    /*
     * zoom inspired by StableZoom
     * https://github.com/mberth/PanAndZoom/blob/master/app/scripts/pan_and_zoom.coffee
     */
    function zoom(old_zoom, delta, c, p) {
        var factor = 1.05;
        var new_zoom = (delta < 0
                        ? old_zoom * factor
                        : (delta > 0
                            ? old_zoom / factor
                            : old_zoom));

        return {
            zoom: new_zoom,
            offset: p.subtract(p.subtract(c).multiply(old_zoom / new_zoom)).subtract(c)
        };
    }

    /**
     * Constructor.
     *
     * @param   mixed       canvas              Canvas selector.
     * @param   object      options             Optional options.
     */
    function diagram(canvas, options)
    {
        this.canvas = paper.setup(canvas);
        this.options = $.extend({'raster': 0}, options || {});
        this.nodes = {};
        this.scopes = {};

        this.layers = {
            'wires': new this.canvas.Layer(),
            'nodes': new this.canvas.Layer(),
            'draw': new this.canvas.Layer()
        };

        this.wire = new wire(this);

        // disable context menu
        $('#' + canvas).bind('contextmenu', function() {
            return false;
        });

        // zoom
        $('#' + canvas).mousewheel(function(event) {
            var pos = paper.view.viewToProject(new paper.Point(event.offsetX, event.offsetY));
            var ret = zoom(paper.view.zoom, event.deltaY, paper.view.center, pos);

            paper.view.zoom = ret.zoom;
            paper.view.center = paper.view.center.add(ret.offset);

            event.preventDefault();

            paper.view.draw();
        });

        // pan
        (function() {
            var tool = new paper.Tool();
            var drag = false;
            var point = {x: 0, y: 0};

            tool.onMouseDrag = function(event) {
                if (drag) {
                    var delta = {
                        x: point.x - event.event.offsetX,
                        y: point.y - event.event.offsetY
                    };

                    point = {x: event.event.offsetX, y: event.event.offsetY};

                    paper.view.center = paper.view.center.add(new paper.Point(delta.x, delta.y));

                    event.stopPropagation();
                }
            }
            tool.onMouseUp = function(event) {
                drag = false;
            }

            $('#' + canvas).on({
                mousedown: function(event) {
                    if (event.shiftKey) {
                        tool.activate();

                        drag = true;
                        point = {x: event.offsetX, y: event.offsetY};
                    }
                }
            });
        })();
    }

    /**
     * Define a connector scope.
     *
     * @param   string      name                Name of scope.
     * @param   object      settings            Scope settings.
     */
    diagram.prototype.defineScope = function(name, settings)
    {
        this.scopes[name] = settings;
    }

    /**
     * Test if scope is available.
     *
     * @param   string      name                Name of scope.
     * @return  bool                            Returns true if scope is available.
     */
    diagram.prototype.hasScope = function(name)
    {
        return (name in this.scopes);
    }

    /**
     * Return scope settings.
     *
     * @param   string      name                Name of scope.
     * @return  object                          Scope settings.
     */
    diagram.prototype.getScope = function(name)
    {
        return this.scopes[name];
    }

    /**
     * Test if node of specified Id is available.
     *
     * @param   string      id                  Id of node.
     * @return  bool                            Returns true if node is available.
     */
    diagram.prototype.hasNode = function(id)
    {
        return (id in this.nodes);
    }

    /**
     * Return instance of node.
     *
     * @param   string      id                  Id of node to return.
     * @return  flowdesigner.node               Instance of node.
     */
    diagram.prototype.getNode = function(id)
    {
        return this.nodes[id];
    }

    /**
     * Return and activate layer of specified name. Returns the canvas layer, if no layer name
     * is specified.
     *
     * @param   string      name                Name of layer to get.
     * @return  node                            Layer node.
     */
    diagram.prototype.getLayer = function(name)
    {
        var layer = (typeof name !== 'undefined'
                        ? this.layers[name]
                        : this.canvas);

        layer.activate();

        return layer;
    }

    /**
     * Export nodes and wires as json data structure.
     *
     * @return  object                          Defined nodes and wires.
     */
    diagram.prototype.exportJson = function()
    {
        var data = {
            'nodes': [],
            'wires': []
        };

        for (var i in this.nodes) {
            data.nodes.push(this.nodes[i].getSettings());
        }

        data.wires = this.wire.exportWires();

        return data;
    }

    /**
     * Import json data structure of nodes and wires.
     *
     * @param   object              data        Data structure to import.
     */
    diagram.prototype.importJson = function(data)
    {
        data.nodes.forEach(function(node) {
            this.addNode()
        });

        data.wires.forEach(function(wire) {
            this.addWire(wire);
        }, this);
    }

    /**
     * Add an instance of a node to the diagram.
     *
     * @param   flowdesigner.node   node        Instance of node.
     * @return  object                          Instance of created node.
     */
    diagram.prototype.addNode = function(node)
    {
        if (!(node instanceof flowdesigner.node)) {
            throw new Error('Invalid parameter specified');
        }

        var id = node.getId();
        var me = this;

        this.nodes[id] = node;
    }

    /**
     * Add a single wire to diagram.
     *
     * @param   object      wire                Wire to add.
     */
    diagram.prototype.addWire = function(wire)
    {
        this.wire.addWire(wire.source, wire.target);
    }

    /**
     * Remove node of the specified ID.
     *
     * @param   string      id                  ID of node to remove.
     */
    diagram.prototype.removeNode = function(id)
    {
        var node = this.nodes[id];
        node.destroy();

        delete this.nodes[id];
    }

    /**
     * Remove multiple nodes.
     *
     * @param   array       ids                 Array of IDs of nodes to remove.
     */
    diagram.prototype.removeNodes = function(ids)
    {
        ids.forEach(function(id) {
            this.removeNode(id);
        }, this);
    }

    /**
     * Remove all nodes from diagram.
     */
    diagram.prototype.removeAllNodes = function()
    {
        this.removeNodes(Object.keys(this.nodes));
    }

    flowdesigner.diagram = diagram;
})(flowdesigner);
<|endoftext|>


<commit_before>const Promise = require('promise')
const ProgressBar = require('./progress-bar')
const callDisplayOverRpc = require('./display-util').callDisplayOverRpc

class Display {
  constructor(deviceId, displayRpcPort, mainDisplayTab, networkInfoDisplayTab, logCalls = true) {
    this.deviceId = deviceId
    console.assert(deviceId, "missing deviceId param")

    this.displayRpcPort = displayRpcPort
    console.assert(displayRpcPort, "missing displayRpcPort param")

    this.mainDisplayTab = mainDisplayTab
    console.assert(mainDisplayTab, "missing mainDisplayTab param")

    this.networkInfoDisplayTab = networkInfoDisplayTab
    console.assert(networkInfoDisplayTab, "missing networkInfoDisplayTab param")

    this.logCalls = logCalls

    this.mainTabColumn = 9

    const progressBarRow = 1
    this.progressBar = new ProgressBar(this.displayRpcPort, progressBarRow, this.mainTabColumn, "", this.logCalls)
  }

  showNetworkOk() {
    this._writeLineOnMainTab(0, "Network")
    this._writeLineOnMainTab(1, "OK")

    this._call("setTexts", [["Network OK"], this.networkInfoDisplayTab ])

    this.showDeviceId()
  }

  showNetworkError(err) {
    this._writeLineOnMainTab(0, "Network")
    this._writeLineOnMainTab(1, "ERROR")

    this._call("setTexts", [["Network ERROR"], this.networkInfoDisplayTab ])
    this._call("writeText", [err.message, 0, 1, true, this.networkInfoDisplayTab ])

    this.showDeviceId()
  }

  showUpdateError(err) {
    this._writeLineOnMainTab(0, "Update")
    this._writeLineOnMainTab(1, "ERROR")

    this._call("setTexts", [["Update ERROR"], this.networkInfoDisplayTab ])
    this._call("writeText", [err.message, 0, 1, true, this.networkInfoDisplayTab ])

    this.showDeviceId()
  }

  showUpdatingProgressBar() {
    console.log("showUpdatingProgressBar")
    this._writeLineOnMainTab(0, "Update")
    this.progressBar.start()
  }

  hideUpdatingProgressBar() {
    console.log("hideUpdatingProgressBar")
    this._writeLineOnMainTab(0, "Update")
    this._writeLineOnMainTab(1, "OK")
    this.progressBar.stop()
  }

  showDeviceId() {
    const deviceIdUpperCase = this.deviceId.toUpperCase()
    const firstHalf = deviceIdUpperCase.substr(0, 5)
    const secondHalf = deviceIdUpperCase.substr(5)

    this._writeLineOnMainTab(5, 'Device:')
    this._writeLineOnMainTab(6, firstHalf)
    this._writeLineOnMainTab(7, secondHalf)
  }

  _writeLineOnMainTab(row, text) {
    const col = this.mainTabColumn
    const rowLength = 16 - col
    //Add blank spaces to the rest of the row to override anything else there
    while (text.length < rowLength) {
      text = text + " "
    }
    this._call("writeText", [text, col, row, false, this.mainDisplayTab])
  }

  _call(method, args) {
    callDisplayOverRpc(this.displayRpcPort, method, args, this.logCalls)
      .catch((err) => {
        console.log("Failed to call display over RPC (probably temporary): " + err)
      })
  }

}

module.exports = Display<commit_msg>UI tweaks<commit_after>const Promise = require('promise')
const ProgressBar = require('./progress-bar')
const callDisplayOverRpc = require('./display-util').callDisplayOverRpc

class Display {
  constructor(deviceId, displayRpcPort, mainDisplayTab, networkInfoDisplayTab, logCalls = true) {
    this.deviceId = deviceId
    console.assert(deviceId, "missing deviceId param")

    this.displayRpcPort = displayRpcPort
    console.assert(displayRpcPort, "missing displayRpcPort param")

    this.mainDisplayTab = mainDisplayTab
    console.assert(mainDisplayTab, "missing mainDisplayTab param")

    this.networkInfoDisplayTab = networkInfoDisplayTab
    console.assert(networkInfoDisplayTab, "missing networkInfoDisplayTab param")

    this.logCalls = logCalls

    this.mainTabColumn = 9

    const progressBarRow = 1
    this.progressBar = new ProgressBar(this.displayRpcPort, progressBarRow, this.mainTabColumn, "", this.logCalls)
  }

  showNetworkOk() {
    this._writeLineOnMainTab(0, "Network")
    this._writeLineOnMainTab(1, "OK :)")

    this._call("setTexts", [["Network OK :)"], this.networkInfoDisplayTab ])

    this.showDeviceId()
  }

  showNetworkError(err) {
    this._writeLineOnMainTab(0, "NETWORK")
    this._writeLineOnMainTab(1, "ERROR!")

    this._call("setTexts", [["NETWORK ERROR :("], this.networkInfoDisplayTab ])
    this._call("writeText", [err.message, 0, 1, true, this.networkInfoDisplayTab ])

    this.showDeviceId()
  }

  showUpdateError(err) {
    this._writeLineOnMainTab(0, "UPDATE")
    this._writeLineOnMainTab(1, "ERROR!")

    this._call("setTexts", [["UPDATE ERROR :("], this.networkInfoDisplayTab ])
    this._call("writeText", [err.message, 0, 1, true, this.networkInfoDisplayTab ])

    this.showDeviceId()
  }

  showUpdatingProgressBar() {
    this._writeLineOnMainTab(0, "Doing")
    this._writeLineOnMainTab(1, "update")
    this._writeLineOnMainTab(2, ".....")
  }

  hideUpdatingProgressBar() {
    this._writeLineOnMainTab(0, "Update")
    this._writeLineOnMainTab(1, "done :)")
    this._writeLineOnMainTab(2, "")
  }

  showDeviceId() {
    const deviceIdUpperCase = this.deviceId.toUpperCase()
    const firstHalf = deviceIdUpperCase.substr(0, 5)
    const secondHalf = deviceIdUpperCase.substr(5)

    this._writeLineOnMainTab(5, 'Device:')
    this._writeLineOnMainTab(6, firstHalf)
    this._writeLineOnMainTab(7, secondHalf)
  }

  _writeLineOnMainTab(row, text) {
    const col = this.mainTabColumn
    const rowLength = 16 - col
    //Add blank spaces to the rest of the row to override anything else there
    while (text.length < rowLength) {
      text = text + " "
    }
    this._call("writeText", [text, col, row, false, this.mainDisplayTab])
  }

  _call(method, args) {
    callDisplayOverRpc(this.displayRpcPort, method, args, this.logCalls)
      .catch((err) => {
        console.log("Failed to call display over RPC (probably temporary): " + err)
      })
  }

}

module.exports = Display<|endoftext|>