<commit_before>const yapi = require('../yapi.js');
const baseModel = require('./base.js');

class projectModel extends baseModel {
  getName() {
    return 'project';
  }

  constructor(){
    super()
    this.handleEnvNullData = this.handleEnvNullData.bind(this)
  }

  getSchema() {
    return {
      uid: { type: Number, required: true },
      name: { type: String, required: true },
      basepath: { type: String },
      switch_notice: { type: Boolean, default: true },
      desc: String,
      group_id: { type: Number, required: true },
      project_type: { type: String, required: true, enum: ['public', 'private'] },
      members: [
        {
          uid: Number,
          role: { type: String, enum: ['owner', 'dev'] },
          username: String,
          email: String,
          email_notice: { type: Boolean, default: true }
        }
      ],
      env: [{ name: String, domain: String, header: Array, global: [{
        name: String,
        value: String
      }] }],
      icon: String,
      color: String,
      add_time: Number,
      up_time: Number,
      pre_script: String,
      after_script: String,
      project_mock_script: String,
      is_mock_open: { type: Boolean, default: false },
      strice: { type: Boolean, default: false },
      is_json5: { type: Boolean, default: true },
      tag: [{name: String, desc: String}]
    };
  }

  updateMember(data) {
    return this.model.update(
      {
        'members.uid': data.uid
      },
      {
        $set: {
          'members.$.username': data.username,
          'members.$.email': data.email
        }
      }
    );
  }

  save(data) {
    let m = new this.model(data);
    return m.save();
  }

  handleEnvNullData(data){
    data = data.toObject();
    data.toObject = ()=> data;
    let isFix = false;
    data.env = data.env.map(item=>{
      item.global = item.global.filter(g=>{
        if(!g || typeof g !== 'undefined'){
          isFix = true;
          return false;
        }
        return true;
      })
      return item;
    })
    if(isFix){
      this.model.update(
        {
          _id: data._id

        },
        {
          $set: { env: data.env }
        },
        { runValidators: true }
      );
    }
    return data;
  }

  get(id) {
    return this.model
      .findOne({
        _id: id
      })
      .exec().then(this.handleEnvNullData)
  }

  getByEnv(id) {
    return this.model
      .findOne({
        _id: id
      })
      .select('env')
      .exec().then(this.handleEnvNullData);
  }

  getProjectWithAuth(group_id, uid) {
    return this.model.countDocuments({
      group_id: group_id,
      'members.uid': uid
    });
  }

  getBaseInfo(id, select) {
    select =
      select ||
      '_id uid name basepath switch_notice desc group_id project_type env icon color add_time up_time pre_script after_script project_mock_script is_mock_open strice is_json5 tag';
    return this.model
      .findOne({
        _id: id
      })
      .select(select)
      .exec().then(this.handleEnvNullData);
  }

  getByDomain(domain) {
    return this.model
      .find({
        prd_host: domain
      })
      .exec().then(this.handleEnvNullData);
  }

  checkNameRepeat(name, groupid) {
    return this.model.countDocuments({
      name: name,
      group_id: groupid
    });
  }

  checkDomainRepeat(domain, basepath) {
    return this.model.countDocuments({
      prd_host: domain,
      basepath: basepath
    });
  }

  list(group_id) {
    let params = { group_id: group_id };
    return this.model
      .find(params)
      .select(
        '_id uid name basepath switch_notice desc group_id project_type color icon env add_time up_time'
      )
      .sort({ _id: -1 })
      .exec();
  }

  // 获取项目数量统计
  getProjectListCount() {
    return this.model.countDocuments();
  }

  countWithPublic(group_id) {
    let params = { group_id: group_id, project_type: 'public' };
    return this.model.countDocuments(params);
  }

  listWithPaging(group_id, page, limit) {
    page = parseInt(page);
    limit = parseInt(limit);
    return this.model
      .find({
        group_id: group_id
      })
      .sort({ _id: -1 })
      .skip((page - 1) * limit)
      .limit(limit)
      .exec();
  }

  listCount(group_id) {
    return this.model.countDocuments({
      group_id: group_id
    });
  }

  countByGroupId(group_id) {
    return this.model.countDocuments({
      group_id: group_id
    });
  }

  del(id) {
    return this.model.remove({
      _id: id
    });
  }

  delByGroupid(groupId) {
    return this.model.remove({
      group_id: groupId
    });
  }

  up(id, data) {
    data.up_time = yapi.commons.time();
    return this.model.update(
      {
        _id: id
      },
      data,
      { runValidators: true }
    );
  }

  addMember(id, data) {
    return this.model.update(
      {
        _id: id
      },
      {
        // $push: { members: data }
        $push: { members: { $each: data } }
      }
    );
  }

  delMember(id, uid) {
    return this.model.update(
      {
        _id: id
      },
      {
        $pull: { members: { uid: uid } }
      }
    );
  }

  checkMemberRepeat(id, uid) {
    return this.model.countDocuments({
      _id: id,
      'members.uid': uid
    });
  }

  changeMemberRole(id, uid, role) {
    return this.model.update(
      {
        _id: id,
        'members.uid': uid
      },
      {
        $set: { 'members.$.role': role }
      }
    );
  }

  changeMemberEmailNotice(id, uid, notice) {
    return this.model.update(
      {
        _id: id,
        'members.uid': uid
      },
      {
        $set: { 'members.$.email_notice': notice }
      }
    );
  }

  search(keyword) {
    return this.model
      .find({
        name: new RegExp(keyword, 'ig')
      })
      .limit(10);
  }
}

module.exports = projectModel;
<commit_msg>fix: 没有判断 env 是否存在导致的 bug<commit_after>const yapi = require('../yapi.js');
const baseModel = require('./base.js');

class projectModel extends baseModel {
  getName() {
    return 'project';
  }

  constructor(){
    super()
    this.handleEnvNullData = this.handleEnvNullData.bind(this)
  }

  getSchema() {
    return {
      uid: { type: Number, required: true },
      name: { type: String, required: true },
      basepath: { type: String },
      switch_notice: { type: Boolean, default: true },
      desc: String,
      group_id: { type: Number, required: true },
      project_type: { type: String, required: true, enum: ['public', 'private'] },
      members: [
        {
          uid: Number,
          role: { type: String, enum: ['owner', 'dev'] },
          username: String,
          email: String,
          email_notice: { type: Boolean, default: true }
        }
      ],
      env: [{ name: String, domain: String, header: Array, global: [{
        name: String,
        value: String
      }] }],
      icon: String,
      color: String,
      add_time: Number,
      up_time: Number,
      pre_script: String,
      after_script: String,
      project_mock_script: String,
      is_mock_open: { type: Boolean, default: false },
      strice: { type: Boolean, default: false },
      is_json5: { type: Boolean, default: true },
      tag: [{name: String, desc: String}]
    };
  }

  updateMember(data) {
    return this.model.update(
      {
        'members.uid': data.uid
      },
      {
        $set: {
          'members.$.username': data.username,
          'members.$.email': data.email
        }
      }
    );
  }

  save(data) {
    let m = new this.model(data);
    return m.save();
  }

  handleEnvNullData(data){
    data = data.toObject();
    data.toObject = ()=> data;
    let isFix = false;
    if(Array.isArray(data.env)){
      data.env = data.env.map(item=>{
        item.global = item.global.filter(g=>{
          if(!g || typeof g !== 'undefined'){
            isFix = true;
            return false;
          }
          return true;
        })
        return item;
      })
    }

    if(isFix){
      this.model.update(
        {
          _id: data._id

        },
        {
          $set: { env: data.env }
        },
        { runValidators: true }
      );
    }
    return data;
  }

  get(id) {
    return this.model
      .findOne({
        _id: id
      })
      .exec().then(this.handleEnvNullData)
  }

  getByEnv(id) {
    return this.model
      .findOne({
        _id: id
      })
      .select('env')
      .exec().then(this.handleEnvNullData);
  }

  getProjectWithAuth(group_id, uid) {
    return this.model.countDocuments({
      group_id: group_id,
      'members.uid': uid
    });
  }

  getBaseInfo(id, select) {
    select =
      select ||
      '_id uid name basepath switch_notice desc group_id project_type env icon color add_time up_time pre_script after_script project_mock_script is_mock_open strice is_json5 tag';
    return this.model
      .findOne({
        _id: id
      })
      .select(select)
      .exec().then(this.handleEnvNullData);
  }

  getByDomain(domain) {
    return this.model
      .find({
        prd_host: domain
      })
      .exec().then(this.handleEnvNullData);
  }

  checkNameRepeat(name, groupid) {
    return this.model.countDocuments({
      name: name,
      group_id: groupid
    });
  }

  checkDomainRepeat(domain, basepath) {
    return this.model.countDocuments({
      prd_host: domain,
      basepath: basepath
    });
  }

  list(group_id) {
    let params = { group_id: group_id };
    return this.model
      .find(params)
      .select(
        '_id uid name basepath switch_notice desc group_id project_type color icon env add_time up_time'
      )
      .sort({ _id: -1 })
      .exec();
  }

  // 获取项目数量统计
  getProjectListCount() {
    return this.model.countDocuments();
  }

  countWithPublic(group_id) {
    let params = { group_id: group_id, project_type: 'public' };
    return this.model.countDocuments(params);
  }

  listWithPaging(group_id, page, limit) {
    page = parseInt(page);
    limit = parseInt(limit);
    return this.model
      .find({
        group_id: group_id
      })
      .sort({ _id: -1 })
      .skip((page - 1) * limit)
      .limit(limit)
      .exec();
  }

  listCount(group_id) {
    return this.model.countDocuments({
      group_id: group_id
    });
  }

  countByGroupId(group_id) {
    return this.model.countDocuments({
      group_id: group_id
    });
  }

  del(id) {
    return this.model.remove({
      _id: id
    });
  }

  delByGroupid(groupId) {
    return this.model.remove({
      group_id: groupId
    });
  }

  up(id, data) {
    data.up_time = yapi.commons.time();
    return this.model.update(
      {
        _id: id
      },
      data,
      { runValidators: true }
    );
  }

  addMember(id, data) {
    return this.model.update(
      {
        _id: id
      },
      {
        // $push: { members: data }
        $push: { members: { $each: data } }
      }
    );
  }

  delMember(id, uid) {
    return this.model.update(
      {
        _id: id
      },
      {
        $pull: { members: { uid: uid } }
      }
    );
  }

  checkMemberRepeat(id, uid) {
    return this.model.countDocuments({
      _id: id,
      'members.uid': uid
    });
  }

  changeMemberRole(id, uid, role) {
    return this.model.update(
      {
        _id: id,
        'members.uid': uid
      },
      {
        $set: { 'members.$.role': role }
      }
    );
  }

  changeMemberEmailNotice(id, uid, notice) {
    return this.model.update(
      {
        _id: id,
        'members.uid': uid
      },
      {
        $set: { 'members.$.email_notice': notice }
      }
    );
  }

  search(keyword) {
    return this.model
      .find({
        name: new RegExp(keyword, 'ig')
      })
      .limit(10);
  }
}

module.exports = projectModel;
<|endoftext|>


<commit_before>var async = require('async');
var db = require('./db');
var users = db.get('users');
var newGithub = require('./new_github');

/**
 * Make the first user follow the second.
 *
 * @param user GH API instance
 * @param other String login username
 */
function follow(user, other, cb) {
  // First check if following
  user.user.getFollowUser({
    user: other
  }, function(isFollowErr, isFollowRes) {
    var isFollowing = isFollowRes.meta.status.indexOf('204') === 0; // Weird API

    // If following, return false
    if (isFollowing) {
      return cb(null, false);
    }

    // Follow the user
    user.user.followUser({
      user: other
    }, function(followErr, followRes) {
      if (followErr) return cb(followErr, false);
      return cb(null, true);
    });

  });
}

/**
 * Make a user unfollow someone. Both params must be GH API instances.
 *
 * @param user GH API instance
 * @param other String login username
 */
function unfollow(user, other, cb) {
  // First check if not following
  user.user.getFollowUser({
    user: other
  }, function(isFollowErr, isFollowRes) {
    var notFollowing = isFollowRes.meta.status.indexOf('204') !== 0; // Weird API

    // If following, return false
    if (notFollowing) {
      return cb(null, false);
    }

    // Unollow the user
    user.user.unfollowUser({
      user: other
    }, function(unfollowErr, unfollowRes) {
      if (unfollowErr) return cb(unfollowErr, false);
      return cb(null, true);
    });

  });
}

/**
 * Adds an amount of followers to a user.
 *
 * @param amount - Use '-1' to add as many as possible.
 */
function addFollowers(login, amount, cb) {

  users.findOne({
    login: login
  }, function(err, user) {
    // CB null if user wasn't found
    if (!user) {
      return cb(null, null);
    }

    var gh = newGithub(user.token);
    var sum = 0;

    users.find({}, function(err, docs) {

      async.eachSeries(docs, function(other, fin) {
        var ugh = newGithub(other.token);

        // Ignore if same as user
        if (other.login === user.login) return fin();

        // Follow each other
        async.waterfall([
          function(done) {
            follow(gh, other.login, done);
          },
          function(res, done) {
            // Terminate if they didn't follow
            if (!res) return done();

            follow(ugh, user.login, function(err, didFollow) {
              if (!didFollow) {
                return unfollow(gh, other.login, done);
              }
              done();
            });
          }
        ], function(err) {
          fin();
        });

      }, function() {
        cb(null, {
          follows: sum
        });
      });
    });

  });

};

module.exports = addFollowers;
<commit_msg>Add isFollowing method<commit_after>var async = require('async');
var db = require('./db');
var users = db.get('users');
var newGithub = require('./new_github');

/**
 * Check if a user is following another user.
 *
 * @param user a GH API instance
 * @param other The other user
 */
function isFollowing(user, other, cb) {
  user.user.getFollowUser({
    user: other
  }, function(err, res) {
    var ret = res.meta.status.indexOf('204') === 0; // Weird API
    cb(err, ret);
  });
}

/**
 * Make the first user follow the second.
 *
 * @param user GH API instance
 * @param other String login username
 */
function follow(user, other, cb) {
  isFollowing(user, other, function(err, following) {

    // If following, return false
    if (following) {
      return cb(null, false);
    }

    // Follow the user
    user.user.followUser({
      user: other
    }, function(followErr, followRes) {
      if (followErr) return cb(followErr, false);
      return cb(null, true);
    });

  });
}

/**
 * Make a user unfollow someone. Both params must be GH API instances.
 *
 * @param user GH API instance
 * @param other String login username
 */
function unfollow(user, other, cb) {
  isFollowing(user, other, function(err, following) {
    // If following, return false
    if (!following) {
      return cb(null, false);
    }

    // Unollow the user
    user.user.unfollowUser({
      user: other
    }, function(unfollowErr, unfollowRes) {
      if (unfollowErr) return cb(unfollowErr, false);
      return cb(null, true);
    });

  });
}

/**
 * Adds an amount of followers to a user.
 *
 * @param amount - Use '-1' to add as many as possible.
 */
function addFollowers(login, amount, cb) {

  users.findOne({
    login: login
  }, function(err, user) {
    // CB null if user wasn't found
    if (!user) {
      return cb(null, null);
    }

    var gh = newGithub(user.token);
    var sum = 0;

    users.find({}, function(err, docs) {

      async.eachSeries(docs, function(other, fin) {
        var ugh = newGithub(other.token);

        // Ignore if same as user
        if (other.login === user.login) return fin();

        // Follow each other
        async.waterfall([
          function(done) {
            follow(gh, other.login, done);
          },
          function(res, done) {
            // Terminate if they didn't follow
            if (!res) return done();

            follow(ugh, user.login, function(err, didFollow) {
              if (!didFollow) {
                return unfollow(gh, other.login, done);
              }
              done();
            });
          }
        ], function(err) {
          fin();
        });

      }, function() {
        cb(null, {
          follows: sum
        });
      });
    });

  });

};

module.exports = addFollowers;
<|endoftext|>


<commit_before>(function (root, factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define([], factory)
	} else if (typeof module === 'object' && module.exports) {
		// Node. Does not work with strict CommonJS, but
		// only CommonJS-like environments that support module.exports,
		// like Node.
		module.exports = factory()
	} else {
		// Browser globals (root is window)
		root.getify = factory()
  }
}(this, function () {
	"use strict"

	function getPath(obj, path = []) {
		return (defaultValue) => {
			let index = 0
      let hasOwn = true
      const length = path.length

      while (obj != null && index < length) {
        const key = path[index++]

        // If the current key is one the getify operator symbols, call
        // the operator function to get the result
        if (typeof operators[key] === 'function') {
          return operators[key](obj, path.slice(index), defaultValue)
        }

        hasOwn = obj.hasOwnProperty(key)
        obj = obj[key]
      }
      return (index === length && hasOwn) ? obj : defaultValue;
		}
	}

	function getify(obj) {
		let path = []

		function handler(path) {
			return {
				get: function(target, key){
					// If the prototype request it, we return it, since
					// not returning it causes issues while outputting stuff
					// to console, for example
					if (obj[key] && obj[key].isPrototypeOf(obj)) {
						return obj[key]
					}

					if (key === Symbol.toStringTag) {
						return 'Getify Object'
					}

					const newPath = [...path, key]
					return getProxy(getPath(obj, newPath), newPath)
				}
			}
		}

		function getProxy(obj, path) {
			return new Proxy(obj, handler(path))
		}

		// For consistency's sake, we return the root object as a function
		return getProxy(getPath(obj), path)
	}

	// Create utility symbols
	getify.all = Symbol("getify.all")
	getify.first = Symbol("getify.first")
	getify.last = Symbol("getify.last")

	const operators = {
		[getify.all]: (obj, path, defaultValue) => {
      return Object.keys(obj).map(key => getPath(obj[key], path)(defaultValue))
    },
		[getify.first]: (obj, path, defaultValue) => {
      const firstKey = Object.keys(obj)[0]
      return getPath(obj[firstKey], path)(defaultValue)
    },
		[getify.last]: (obj, path, defaultValue) => {
			const keys = Object.keys(obj)
			const lastKey = keys[keys.length - 1]
      return getPath(obj[lastKey], path)(defaultValue)
		}
	}

	return getify
}));
<commit_msg>Minor performance tweaks<commit_after>(function (root, factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define([], factory)
	} else if (typeof module === 'object' && module.exports) {
		// Node. Does not work with strict CommonJS, but
		// only CommonJS-like environments that support module.exports,
		// like Node.
		module.exports = factory()
	} else {
		// Browser globals (root is window)
		root.getify = factory()
  }
}(this, function () {
	"use strict"

	function getPath(obj, path = []) {
    const length = path.length
    let hasOwn = true
    let key

		return (defaultValue) => {
      let index = 0
      while (obj != null && index < length) {
        key = path[index++]

        // If the current key is one the getify operator symbols, call
        // the operator function to get the result
        if (operators[key]) {
          return operators[key](obj, path.slice(index), defaultValue)
        }

        hasOwn = obj.hasOwnProperty(key)
        obj = obj[key]
      }

      return (hasOwn && index === length) ? obj : defaultValue;
		}
	}

	function getify(obj) {

		function handler(path) {
			return {
				get: function(target, key){
					// If the prototype request it, we return it, since
					// not returning it causes issues while outputting stuff
					// to console, for example
					if (obj[key] && obj[key].isPrototypeOf(obj)) {
						return obj[key]
					}

					const newPath = [...path, key]
          return new Proxy(getPath(obj, newPath), handler(newPath))
				}
			}
		}

		// For consistency's sake, we return the root object as a function
    return new Proxy(getPath(obj), handler([]))
	}

	// Create utility symbols
	getify.all = Symbol("getify.all")
	getify.first = Symbol("getify.first")
	getify.last = Symbol("getify.last")

	const operators = {
		[getify.all]: (obj, path, defaultValue) => {
      return Object.keys(obj).map(key => getPath(obj[key], path)(defaultValue))
    },
		[getify.first]: (obj, path, defaultValue) => {
      const firstKey = Object.keys(obj)[0]
      return getPath(obj[firstKey], path)(defaultValue)
    },
		[getify.last]: (obj, path, defaultValue) => {
			const keys = Object.keys(obj)
			const lastKey = keys[keys.length - 1]
      return getPath(obj[lastKey], path)(defaultValue)
		}
	}

	return getify
}));
<|endoftext|>


<commit_before>// Copyright (c) 2013 John Granström
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var spawn = require('child_process').spawn,
	util = require('util'),
	fs = require('fs'),
	misc = require('./misc'),
	EventEmitter = require('events').EventEmitter,
	CommandPool = require('./commandpool').CommandPool
	Signals = require('./command').Signals;

// Create a new Go-object for the specified .go-file.
// Will also intialize Go-object if second parameter is true.
//
// Throws error if no path provided to .go-file.
util.inherits(Go, EventEmitter);
exports.Go = Go;
function Go(options, callback) {
	if(options === undefined || options === null) {
		misc.raiseError('No options provided.')
	}
	if(options.path == undefined || options.path == null) {
		misc.raiseError('No path provided to .go-file.');
	}

	misc.mergeDefaultOptions(options, {
		maxCommandsRunning: 10,
		defaultCommandTimeoutSec: 5,
		cwd: process.cwd(),
	});
	this.options = options;

	this.goFile = options.path;
	this.proc = null;
	this.initialized = false; // true when Go has been initialized, back to false when Go closes
	this.closePending = false; // true when close() has been called and no more commands should be planned
	this.terminatePending = false; // true when terminate() has been called
	this.commandPool = new CommandPool(this)

	if(options.initAtOnce) {
		this.init(callback);
	}
}

// Initialize by launching go process and prepare for commands.
// Do as early as possible to avoid delay when executing first command.
//
// callback has parameters (err)
Go.prototype.init = function(callback) {
	var self = this;
	fs.exists(this.goFile, function(exists) {
		if(!exists) {
			misc.callbackIfAvailable(callback, misc.getError('.go-file not found for given path.'));
			return;
		}

		if (self.goFile.slice(-3).toLowerCase() === '.go') {
            // Spawn go process within current working directory
            self.proc = spawn('go', ['run', self.goFile], { cwd: self.options.cwd, env: process.env });
		} else {
			// Spawn go compiled file
            self.proc = spawn( self.goFile, [], { cwd: self.options.cwd, env: process.env });
        }

		// Setup handlers
		self.proc.stdout.on('data', function(data){
			handleStdout(self, data);
		});
		self.proc.stderr.on('data', function(data){
			handleErr(self, data, false);
		});
		self.proc.on('close', function(){
			handleClose(self);
		});

		// Init complete
		self.initialized = true;
		misc.callbackIfAvailable(callback, null);
	});
}

// Gracefully close Go by sending termination signal after all executing commands
// has ended their execution.
// Returns true if close has been started, or false if Go is not initialized or if it
// already has a close pending.
Go.prototype.close = function() {
	if(this.initialized && !this.closePending && !this.terminatePending) {
		this.closePending = true;
		// Send prioritized termination signal
		this.commandPool.planOnIdle(Signals.Termination, true);

		return true;
	} else {
		return false;
	}
}

// Hard terminate by sending termination on all commands and termination signal to Go
// Returns true if termination has been started, or false if Go is not initialized or if it
// already has a termination pending.
Go.prototype.terminate = function() {
	return terminate(this, true);
}

// Create and execute or queue a command of JSON data
// Will not queue command if Go is not initialized or has been closed (or close pending)
// Takes parameters:
// 		data (required) - actual command JSON
//		callback (required) - the callback to call with possible result when execution ends
//		options (optional) - overrides default execution options
// Returns true if the command was planned for execution, otherwise false.
Go.prototype.execute = function(data, callback, options) {
	if(this.initialized && !this.closePending && !this.terminatePending) {
		// Important to not leave go in an infinite loop eatig cpu
		try { // Contain outer exceptions and close go before rethrowing exception.
			this.commandPool.planExecution(this.commandPool.createCommand(data, callback), false, options);
		} catch (e) {
			handleErr(this, e, false);
		}
		return true; // Return true since the command has been planned for execution
	} else {
		return false; // The command wasn't planned for execution, return false
	}
}

// Receive data from go-module
function handleStdout(go, data) {
	// Response may be several command responses separated by new lines
	data.toString().split("\n").forEach(function(resp) {
		// Discard empty lines
		if(resp.length > 0) {
			// Parse each command response with a event-loop in between to avoid blocking
			process.nextTick(function(){parseResponse(go, resp)});
		}
	});
}

// Parse a _single_ command response as JSON and handle it
// If parsing fails a internal error event will be emitted with the response data
function parseResponse(go, resp) {
	var parsed;
	try {
		parsed = JSON.parse(resp);
	} catch (e) {
		handleErr(go, resp, true);
		return;
	}

	// Important to not leave go in an infinite loop eatig cpu
	try { // Contain outer exceptions and close go before rethrowing exception.
		go.commandPool.handleResponse(parsed) // Handle response outside throw to avoid catching those exceptions
	} catch (e) {
		handleErr(go, e, false);
	}
}

// Emit error event on go instance, pass through raw error data
// Errors may either be internal parser errors or external errors received from stderr
function handleErr(go, data, parser) {
	if(!parser) { // If external error, terminate all commands
		terminate(go, false);
	}

	if(go.listeners('error').length > 0) { // Only emit event if there are listeners
		process.nextTick(function() {
			// Emit any event on next tick
			go.emit('error', {parser: parser, data: data});
		});
	}
}

// Go panic and process ends causes calls to this
// Emit close event on go instance
function handleClose(go) {
	// If process closes we set initialized to false to avoid invalid close() or execute()
	go.initialized = false;
	if(go.listeners('close').length > 0) { // Only emit event if there are listeners
		go.emit('close');
	}
}

// Terminate by sending termination on all commands.
// If called with true it will also directly try to send a termination signal to go
function terminate(go, withSignal) {
	if(go.initialized && !go.terminatePending) {
		go.terminatePending = true;

		// Do the actual termination asynchronously
		// Callbacks will be each terminated command or nothing
		process.nextTick(function(){
			// Tell command pool to kill all commands
			go.commandPool.terminate();

			// Send signal after command pool termination, otherwise it would
			// be removed by terminate()
			if(withSignal) {
				go.commandPool.planExecution(Signals.Termination, true);
			}
		});

		return true;
	} else {
		return false;
	}
}<commit_msg>change spaces into tabs to stay in original convention<commit_after>// Copyright (c) 2013 John Granström
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

var spawn = require('child_process').spawn,
	util = require('util'),
	fs = require('fs'),
	misc = require('./misc'),
	EventEmitter = require('events').EventEmitter,
	CommandPool = require('./commandpool').CommandPool
	Signals = require('./command').Signals;

// Create a new Go-object for the specified .go-file.
// Will also intialize Go-object if second parameter is true.
//
// Throws error if no path provided to .go-file.
util.inherits(Go, EventEmitter);
exports.Go = Go;
function Go(options, callback) {
	if(options === undefined || options === null) {
		misc.raiseError('No options provided.')
	}
	if(options.path == undefined || options.path == null) {
		misc.raiseError('No path provided to .go-file.');
	}

	misc.mergeDefaultOptions(options, {
		maxCommandsRunning: 10,
		defaultCommandTimeoutSec: 5,
		cwd: process.cwd(),
	});
	this.options = options;

	this.goFile = options.path;
	this.proc = null;
	this.initialized = false; // true when Go has been initialized, back to false when Go closes
	this.closePending = false; // true when close() has been called and no more commands should be planned
	this.terminatePending = false; // true when terminate() has been called
	this.commandPool = new CommandPool(this)

	if(options.initAtOnce) {
		this.init(callback);
	}
}

// Initialize by launching go process and prepare for commands.
// Do as early as possible to avoid delay when executing first command.
//
// callback has parameters (err)
Go.prototype.init = function(callback) {
	var self = this;
	fs.exists(this.goFile, function(exists) {
		if(!exists) {
			misc.callbackIfAvailable(callback, misc.getError('.go-file not found for given path.'));
			return;
		}

		// simple extension check to detect if its a un compiles .go file
		if (self.goFile.slice(-3).toLowerCase() === '.go') {
			// Spawn go process within current working directory
			self.proc = spawn('go', ['run', self.goFile], { cwd: self.options.cwd, env: process.env });
		} else {
			// Spawn go compiled file
			self.proc = spawn( self.goFile, [], { cwd: self.options.cwd, env: process.env });
		}

		// Setup handlers
		self.proc.stdout.on('data', function(data){
			handleStdout(self, data);
		});
		self.proc.stderr.on('data', function(data){
			handleErr(self, data, false);
		});
		self.proc.on('close', function(){
			handleClose(self);
		});

		// Init complete
		self.initialized = true;
		misc.callbackIfAvailable(callback, null);
	});
}

// Gracefully close Go by sending termination signal after all executing commands
// has ended their execution.
// Returns true if close has been started, or false if Go is not initialized or if it
// already has a close pending.
Go.prototype.close = function() {
	if(this.initialized && !this.closePending && !this.terminatePending) {
		this.closePending = true;
		// Send prioritized termination signal
		this.commandPool.planOnIdle(Signals.Termination, true);

		return true;
	} else {
		return false;
	}
}

// Hard terminate by sending termination on all commands and termination signal to Go
// Returns true if termination has been started, or false if Go is not initialized or if it
// already has a termination pending.
Go.prototype.terminate = function() {
	return terminate(this, true);
}

// Create and execute or queue a command of JSON data
// Will not queue command if Go is not initialized or has been closed (or close pending)
// Takes parameters:
// 		data (required) - actual command JSON
//		callback (required) - the callback to call with possible result when execution ends
//		options (optional) - overrides default execution options
// Returns true if the command was planned for execution, otherwise false.
Go.prototype.execute = function(data, callback, options) {
	if(this.initialized && !this.closePending && !this.terminatePending) {
		// Important to not leave go in an infinite loop eatig cpu
		try { // Contain outer exceptions and close go before rethrowing exception.
			this.commandPool.planExecution(this.commandPool.createCommand(data, callback), false, options);
		} catch (e) {
			handleErr(this, e, false);
		}
		return true; // Return true since the command has been planned for execution
	} else {
		return false; // The command wasn't planned for execution, return false
	}
}

// Receive data from go-module
function handleStdout(go, data) {
	// Response may be several command responses separated by new lines
	data.toString().split("\n").forEach(function(resp) {
		// Discard empty lines
		if(resp.length > 0) {
			// Parse each command response with a event-loop in between to avoid blocking
			process.nextTick(function(){parseResponse(go, resp)});
		}
	});
}

// Parse a _single_ command response as JSON and handle it
// If parsing fails a internal error event will be emitted with the response data
function parseResponse(go, resp) {
	var parsed;
	try {
		parsed = JSON.parse(resp);
	} catch (e) {
		handleErr(go, resp, true);
		return;
	}

	// Important to not leave go in an infinite loop eatig cpu
	try { // Contain outer exceptions and close go before rethrowing exception.
		go.commandPool.handleResponse(parsed) // Handle response outside throw to avoid catching those exceptions
	} catch (e) {
		handleErr(go, e, false);
	}
}

// Emit error event on go instance, pass through raw error data
// Errors may either be internal parser errors or external errors received from stderr
function handleErr(go, data, parser) {
	if(!parser) { // If external error, terminate all commands
		terminate(go, false);
	}

	if(go.listeners('error').length > 0) { // Only emit event if there are listeners
		process.nextTick(function() {
			// Emit any event on next tick
			go.emit('error', {parser: parser, data: data});
		});
	}
}

// Go panic and process ends causes calls to this
// Emit close event on go instance
function handleClose(go) {
	// If process closes we set initialized to false to avoid invalid close() or execute()
	go.initialized = false;
	if(go.listeners('close').length > 0) { // Only emit event if there are listeners
		go.emit('close');
	}
}

// Terminate by sending termination on all commands.
// If called with true it will also directly try to send a termination signal to go
function terminate(go, withSignal) {
	if(go.initialized && !go.terminatePending) {
		go.terminatePending = true;

		// Do the actual termination asynchronously
		// Callbacks will be each terminated command or nothing
		process.nextTick(function(){
			// Tell command pool to kill all commands
			go.commandPool.terminate();

			// Send signal after command pool termination, otherwise it would
			// be removed by terminate()
			if(withSignal) {
				go.commandPool.planExecution(Signals.Termination, true);
			}
		});

		return true;
	} else {
		return false;
	}
}<|endoftext|>


<commit_before>var fs          = require('fs')
  , path        = require('path')
  , crypto      = require('crypto')
  , Stream      = require('stream').Stream
  , inherits    = require('util').inherits
  , async       = require('async')
  , _           = require('underscore')
  , streamspeed = require('streamspeed')
  , queue       = require('./queue')
  , util        = require('./util')
  , exists      = fs.exists || path.exists
  ;

/**
 * defaults
 * @const
 */
var MAX_FILES_OPENED = 250;

/**
 * Hashes a buffer.
 *
 * @param (Buffer) buffer
 * @return (Buffer)
 */
function sha1(buffer) {
  return crypto.createHash('sha1').update(buffer).digest();
}

/**
 * Rounds a number to given decimal place
 *
 * @param (number) num
 * @param (number) dec
 * @return (number)
 */
function round(num, dec) {
  var pow = Math.pow(10, dec);
  return Math.round(num * pow) / pow;
}

/**
 * Allows arrays of objects with a `path` property to be
 * sorted alphabetically
 *
 * @param (Object) obj
 * @return (string)
 */
 function sortByPath(obj) {
   return obj.path.join();
 }

/**
 * Calculates piece hashes of file list
 *
 * @param (string) dir Directory where files are.
 * @param (Array.string) list Array of files to hash.
 * @param (number) pieceLength How many bytes long each piece will be.
 * @param options
 * @param options.maxFiles Max number of files to open during hashing.
 * @param options.stopOnErr Hashing stops if encounters an error.
 * @constructor
 * @extends Stream
 * @api private
 */
var Hasher = module.exports = function(dir, list, pieceLength, options) {
  if (options == null) options = {};
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  // set defaults
  this.maxFiles  = options.maxFiles || MAX_FILES_OPENED;
  this.stopOnErr = options.stopOnErr || false;

  // init arrays that will be used every time during hashing
  this.dir             = dir;
  this.pieceLength     = pieceLength;
  this.files           = [];
  this.pieces          = [];
  this.ready           = false;
  this.hashing         = false;
  this.paused          = false;
  this.stopped         = false;

  var self = this;

  if (self.stopOnErr) {
    self.once('error', function(err) {
      self.err = err;
    });
  }

  exists(dir, function(exists) {
    if (!exists) {
      return self.emit('error', new Error(dir + ' does not exist'));
    }

    // Check if list containts objects or strings.
    if (typeof list[0] === 'string') {

      // create queue to handle having many files opened
      var statqueue = queue(fs.stat, self.maxFiles);
      self._getFilesStats(statqueue, list, readTasks);

    // in the case of objects, a file list from a torrent file was given.
    } else {
      self.files = list;
      process.nextTick(readTasks);
    }

    function readTasks(err) {
      // sort files alphabetically
      self.files     = _.sortBy(self.files, sortByPath);

      if (err) return self.emit('error', err);

      // if files tree traversed with no errors,
      // add read tasks to filetasks
      self._CreateFileTasks();

      // ready to start hashing
      self.emit('ready');
      self.ready = true;
      self._start();
    }
  });
};

inherits(Hasher, Stream);

/**
 * Create filetasks array
 */
Hasher.prototype._CreateFileTasks = function() {
  var self = this;

  // variables to help calculate piece position
  var totalSize, piece, offset;
  totalSize = piece = offset = 0;

  // Create tasks to figure out what pieces belong to exactly
  // what files and what part of the files
  self.filetasks = [];
  self.files.forEach(function(task) {

    self.filetasks.push({
      length : task.length
    , path   : task.path
      // piece this task will write to
    , piece    : piece
      // piece buffer offset to start writing at
    , offset   : offset
    });

    // add file length to total size
    totalSize += task.length;

    // get piece next task will write to and its offset
    piece = Math.floor(totalSize / self.pieceLength);
    offset = totalSize - (self.pieceLength * piece);
  });

  self.totalSize = totalSize;
  self.pieces = piece + 1;
  // take note of the length of the last piece
  // the rest of the pieces will be of self.pieceLength length
  self.lastPieceLength = totalSize - (self.pieceLength * piece);
};

/**
 * Starts the actual hashing process
 */
Hasher.prototype._start = function() {
  var self = this;
  if (!self.ready) {
    return self.emit('error', new Error('Cannot start before being ready'));
  }
  self.hashing = true;
  self.stopped = false;

  // create piece objects
  var pieces = [];
  for (var i = 0; i < self.pieces; i++) {
    pieces[i] = {
      buffer: null
    , length: self.pieceLength
    , bytesWritten: 0
    };
  }
  pieces[pieces.length - 1].length = self.lastPieceLength;

  // keep track of progress
  var totalRead = 0;

  // keep track of reading speed
  self.streamGroup = new streamspeed.Group();
  var lastPercent = 0
    , speed = 0
    , avg = 0
  self.streamGroup.on('readspeed', function(s, a) {
    speed = s;
    avg   = a;
  });

  var filesqueue = queue(function(task, callback) {
    if (self.err || self.stopped) return callback();

    // keep track how much has been read
    var readSoFar   = 0
      , pieceIndex = task.piece

    // open file for reading
    var file = path.join(self.dir, path.join.apply(null, task.path));
    var rs = fs.createReadStream(file);
    self.streamGroup.watch(rs);

    rs.on('open', function() {
      self.emit('open', file);
    });

    rs.on('data', function(data) {
      // recalculate the index of current piece based on readSoFar
      pieceIndex = task.piece + Math.floor((task.offset + readSoFar) / self.pieceLength);

      // write data to piece buffer
      var targetStart = (task.offset + readSoFar) % self.pieceLength;
      var sourceWritten = self._writePiece(
          pieces, pieceIndex, targetStart,
          data, 0, data.length, file, readSoFar);

      while (sourceWritten < data.length) {
        pieceIndex += 1;
        sourceWritten += self._writePiece(
            pieces, pieceIndex, 0,
            data, sourceWritten, data.length, file, readSoFar);
      }

      // update amount of bytes written to buffer
      readSoFar += data.length;
      totalRead += data.length;

      // emit hash event with piece info and progress
      var percent =
        round(totalRead / self.totalSize * 100, 2);
      if (lastPercent !== percent) {
        self.emit('progress', percent, speed, avg);
        lastPercent = percent;
      }

    });

    // close this file when there is an error or finished reading
    rs.on('error', function(err) {
      self.emit('error', err);
      callback();
    });

    rs.on('end', function() {
      callback();
    });
  }, self.maxFiles);

  // when all file queues are finished,
  // join all the pieces
  filesqueue.drain = function() {
    self.hashing = false;
    self.emit('end');
  };

  // start queueing jobs
  self.filetasks.forEach(function(filetask) {
    filesqueue.push(filetask);
  });
};

Hasher.prototype._writePiece = function(pieces, index, offset,
                                       data, start, end,
                                       file, readSoFar) {
  var piece = pieces[index];
  if (!piece) return;
  if (piece.buffer === null) {
    piece.buffer = new Buffer(piece.length);
  }

  // write data to piece buffer and update bytes written
  var size = Math.min(end - start, piece.length - offset);
  piece.bytesWritten += size;
  data.copy(piece.buffer, offset, start, start + size);

  // check if piece buffer is full
  if (piece.bytesWritten == piece.length) {
    pieces[index] = null;
    var buf = new Buffer(sha1(piece.buffer), 'binary');
    this.emit('hash', index, buf, file, readSoFar, size);
  }

  return size;
};

/**
 * Recursively traverses files to call fs.stat on them.
 * Will add all files in folders if any are encountered.
 *
 * @param (Array.string) list List of files and/or directories.
 * @param (function(err)) callback
 */
Hasher.prototype._getFilesStats = function(queue, list, callback) {
  var self = this;
  var funs = [];

  list.forEach(function(file, i) {
    funs[i] = function(callback) {
      self._getFileStat(queue, file, callback);
    };
  });

  // read all file stats in parallel
  async.parallel(funs, callback);
};

/**
 * Stat is needed to know file size.
 *
 * @param (string) dir
 * @param (Array.string) list
 */
Hasher.prototype._getFileStat = function(queue, file, callback) {
  var self = this;
  var filepath = path.join(self.dir, file);

  queue.push(filepath, function(err, stat) {
    if (err) return callback(err);

    // if this is a directory, add all files in it to torrent files
    if (stat.isDirectory()) {
      fs.readdir(filepath, function(err, dirfiles) {
        if (err) return callback(err);

        dirfiles = dirfiles.map(function(dirfile) {
          return path.join(file, dirfile);
        });
        // call _getFilesStats again
        self._getFilesStats(queue, dirfiles, callback);
      });

    } else if (stat.isFile()) {

      self.files.push({
        length: stat.size
      , path: util.splitPath(file)
      });
      callback(null);
    }
  });
};

/**
 * Allows pausing of the hashing process by storing functions from the
 * file and read queues that will not get called back until resume()
 * is called.
 */
Hasher.prototype.pause = function() {
  if (this.paused || !this.hashing) return false;
  this.paused = true;

  this.streamGroup.getStreams().forEach(function(rs) {
    rs.pause();
  });

  return true;
};

/**
 * Resumes hashing
 */
Hasher.prototype.resume = function() {
  if (!this.paused) return false;
  this.paused = false;

  this.streamGroup.getStreams().forEach(function(rs) {
    rs.resume();
  });

  return true;
};

/**
 * Continues hashing if paused or pauses if not
 */
Hasher.prototype.toggle = function() {
  return this.paused ? this.resume() : this.pause();
};

/**
 * Stops hashing completely.
 * Closes file descriptors and does not emit any more events.
 */
Hasher.prototype.destroy = function() {
  if (!this.hashing) return false;
  this.stopped = true;
  this.paused = false;

  this.streamGroup.getStreams().forEach(function(rs) {
    rs.destroy();
  });

  return true;
};
<commit_msg>beginning to refactor hasher<commit_after>var fs          = require('fs')
  , path        = require('path')
  , crypto      = require('crypto')
  , Stream      = require('stream').Stream
  , inherits    = require('util').inherits
  , async       = require('async')
  , _           = require('underscore')
  , streamspeed = require('streamspeed')
  , queue       = require('./queue')
  , util        = require('./util')
  , exists      = fs.exists || path.exists
  ;

/**
 * defaults
 * @const
 */
var MAX_FILES_OPENED = 250;
var PIECE_LENGTH =  262144; // 256 KB

/**
 * Hashes a buffer.
 *
 * @param (Buffer) buffer
 * @return (Buffer)
 */
function sha1(buffer) {
  return crypto.createHash('sha1').update(buffer).digest();
}

/**
 * Rounds a number to given decimal place
 *
 * @param (number) num
 * @param (number) dec
 * @return (number)
 */
function round(num, dec) {
  var pow = Math.pow(10, dec);
  return Math.round(num * pow) / pow;
}

/**
 * Allows arrays of objects with a `path` property to be
 * sorted alphabetically
 *
 * @param (Object) obj
 * @return (string)
 */
 function sortByPath(obj) {
   return obj.path.join();
 }

/**
 * Calculates piece hashes of file list
 *
 * @param options
 * @param options.pieceLength How many bytes long each hashed piece will be.
 * @param options.list List of relative files to hash.
 * @param options.dir Directory where files to be hashed are.
 * @param options.maxFiles Max number of files to open during hashing.
 * @param options.stopOnErr Hashing stops if encounters an error.
 * @constructor
 * @extends Stream
 */
var Hasher = module.exports = function(options) {
  if (options == null) options = {};
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  // set defaults
  this.pieceLength = options.pieceLength || PIECE_LENGTH;
  this.maxFiles    = options.maxFiles || MAX_FILES_OPENED;
  this.stopOnErr   = options.stopOnErr || false;

  // init arrays that will be used every time during hashing
  this.dir         = dir;
  this.files       = [];
  this.pieces      = [];
  this.ready       = false;
  this.hashing     = false;
  this.paused      = false;
  this.stopped     = false;

  var self = this;

  if (self.stopOnErr) {
    self.once('error', function(err) {
      self.err = err;
    });
  }

  exists(dir, function(exists) {
    if (!exists) {
      return self.emit('error', new Error(dir + ' does not exist'));
    }

    // Check if list containts objects or strings.
    if (typeof list[0] === 'string') {

      // create queue to handle having many files opened
      var statqueue = queue(fs.stat, self.maxFiles);
      self._getFilesStats(statqueue, list, readTasks);

    // in the case of objects, a file list from a torrent file was given.
    } else {
      self.files = list;
      process.nextTick(readTasks);
    }

    function readTasks(err) {
      // sort files alphabetically
      self.files = _.sortBy(self.files, sortByPath);

      if (err) return self.emit('error', err);

      // if files tree traversed with no errors,
      // add read tasks to filetasks
      self._CreateFileTasks();

      // ready to start hashing
      self.ready = true;
      self.emit('ready');
      self._start();
    }
  });
};

inherits(Hasher, Stream);

/**
 * Create filetasks array
 */
Hasher.prototype._CreateFileTasks = function() {
  var self = this;

  // variables to help calculate piece position
  var totalSize, piece, offset;
  totalSize = piece = offset = 0;

  // Create tasks to figure out what pieces belong to exactly
  // what files and what part of the files
  self.filetasks = [];
  self.files.forEach(function(task) {

    self.filetasks.push({
      length : task.length
    , path   : task.path
      // piece this task will write to
    , piece    : piece
      // piece buffer offset to start writing at
    , offset   : offset
    });

    // add file length to total size
    totalSize += task.length;

    // get piece next task will write to and its offset
    piece = Math.floor(totalSize / self.pieceLength);
    offset = totalSize - (self.pieceLength * piece);
  });

  self.totalSize = totalSize;
  self.pieces = piece + 1;
  // take note of the length of the last piece
  // the rest of the pieces will be of self.pieceLength length
  self.lastPieceLength = totalSize - (self.pieceLength * piece);
};

/**
 * Starts the actual hashing process
 */
Hasher.prototype._start = function() {
  var self = this;
  if (!self.ready) {
    return self.emit('error', new Error('Cannot start before being ready'));
  }
  self.hashing = true;
  self.stopped = false;

  // create piece objects
  var pieces = [];
  for (var i = 0; i < self.pieces; i++) {
    pieces[i] = {
      buffer: null
    , length: self.pieceLength
    , bytesWritten: 0
    };
  }
  pieces[pieces.length - 1].length = self.lastPieceLength;

  // keep track of progress
  var totalRead = 0;

  // keep track of reading speed
  self.streamGroup = new streamspeed.Group();
  var lastPercent = 0
    , speed = 0
    , avg = 0
    ;

  self.streamGroup.on('readspeed', function(s, a) {
    speed = s;
    avg   = a;
  });

  var filesqueue = queue(function(task, callback) {
    if (self.err || self.stopped) return callback();

    // keep track how much has been read
    var readSoFar  = 0
      , pieceIndex = task.piece
      ;

    // open file for reading
    var file = path.join(self.dir, path.join.apply(null, task.path));
    var rs = fs.createReadStream(file);
    self.streamGroup.watch(rs);

    rs.on('open', function() {
      self.emit('open', file);
    });

    rs.on('data', function(data) {
      // recalculate the index of current piece based on readSoFar
      pieceIndex = task.piece + Math.floor((task.offset + readSoFar) / self.pieceLength);

      // write data to piece buffer
      var targetStart = (task.offset + readSoFar) % self.pieceLength;
      var sourceWritten = self._writePiece(
          pieces, pieceIndex, targetStart,
          data, 0, data.length, file, readSoFar);

      while (sourceWritten < data.length) {
        pieceIndex += 1;
        sourceWritten += self._writePiece(
            pieces, pieceIndex, 0,
            data, sourceWritten, data.length, file, readSoFar);
      }

      // update amount of bytes written to buffer
      readSoFar += data.length;
      totalRead += data.length;

      // emit hash event with piece info and progress
      var percent =
        round(totalRead / self.totalSize * 100, 2);
      if (lastPercent !== percent) {
        self.emit('progress', percent, speed, avg);
        lastPercent = percent;
      }

    });

    // close this file when there is an error or finished reading
    rs.on('error', function(err) {
      self.emit('error', err);
      callback();
    });

    rs.on('end', function() {
      callback();
    });
  }, self.maxFiles);

  // when all file queues are finished,
  // join all the pieces
  filesqueue.drain = function() {
    self.hashing = false;
    self.emit('end');
  };

  // start queueing jobs
  self.filetasks.forEach(function(filetask) {
    filesqueue.push(filetask);
  });
};

Hasher.prototype._writePiece = function(pieces, index, offset,
                                       data, start, end,
                                       file, readSoFar) {
  var piece = pieces[index];
  if (!piece) return;
  if (piece.buffer === null) {
    piece.buffer = new Buffer(piece.length);
  }

  // write data to piece buffer and update bytes written
  var size = Math.min(end - start, piece.length - offset);
  piece.bytesWritten += size;
  data.copy(piece.buffer, offset, start, start + size);

  // check if piece buffer is full
  if (piece.bytesWritten === piece.length) {
    pieces[index] = null;
    var buf = new Buffer(sha1(piece.buffer), 'binary');
    this.emit('hash', index, buf, file, readSoFar, size);
  }

  return size;
};

/**
 * Recursively traverses files to call fs.stat on them.
 * Will add all files in folders if any are encountered.
 *
 * @param (Array.string) list List of files and/or directories.
 * @param (function(err)) callback
 */
Hasher.prototype._getFilesStats = function(queue, list, callback) {
  var self = this;
  var funs = [];

  list.forEach(function(file, i) {
    funs[i] = function(callback) {
      self._getFileStat(queue, file, callback);
    };
  });

  // read all file stats in parallel
  async.parallel(funs, callback);
};

/**
 * Stat is needed to know file size.
 *
 * @param (string) dir
 * @param (Array.string) list
 */
Hasher.prototype._getFileStat = function(queue, file, callback) {
  var self = this;
  var filepath = path.join(self.dir, file);

  queue.push(filepath, function(err, stat) {
    if (err) return callback(err);

    // if this is a directory, add all files in it to torrent files
    if (stat.isDirectory()) {
      fs.readdir(filepath, function(err, dirfiles) {
        if (err) return callback(err);

        dirfiles = dirfiles.map(function(dirfile) {
          return path.join(file, dirfile);
        });
        // call _getFilesStats again
        self._getFilesStats(queue, dirfiles, callback);
      });

    } else if (stat.isFile()) {

      self.files.push({
        length: stat.size
      , path: util.splitPath(file)
      });
      callback(null);
    }
  });
};

/**
 * Allows pausing of the hashing process by storing functions from the
 * file and read queues that will not get called back until resume()
 * is called.
 */
Hasher.prototype.pause = function() {
  if (this.paused || !this.hashing) return false;
  this.paused = true;

  this.streamGroup.getStreams().forEach(function(rs) {
    rs.pause();
  });

  return true;
};

/**
 * Resumes hashing
 */
Hasher.prototype.resume = function() {
  if (!this.paused) return false;
  this.paused = false;

  this.streamGroup.getStreams().forEach(function(rs) {
    rs.resume();
  });

  return true;
};

/**
 * Continues hashing if paused or pauses if not
 */
Hasher.prototype.toggle = function() {
  return this.paused ? this.resume() : this.pause();
};

/**
 * Stops hashing completely.
 * Closes file descriptors and does not emit any more events.
 */
Hasher.prototype.destroy = function() {
  if (!this.hashing) return false;
  this.stopped = true;
  this.paused = false;

  this.streamGroup.getStreams().forEach(function(rs) {
    rs.destroy();
  });

  return true;
};
<|endoftext|>


<commit_before>var fs           = require('fs')
  , path         = require('path')
  , crypto       = require('crypto')
  , EventEmitter = require('events').EventEmitter
  , inherits     = require('util').inherits
  , async        = require('async')
  , _            = require('underscore')
  , queue        = require('./queue')
  , util         = require('./util')

/**
 * defaults
 * @const
 */
var MAX_FILES_OPENED = 250;

/**
 * Hashes a buffer.
 * @param (Buffer) buffer
 * @return (Buffer)
 */
function sha1(buffer) {
  return crypto.createHash('sha1').update(buffer).digest();
}

/**
 * Rounds a number to given decimal place
 * @param (number) num
 * @param (number) dec
 * @return (number)
 */
function round(num, dec) {
  var pow = Math.pow(10, dec);
  return Math.round(num * pow) / pow;
}

/**
 * Allows arrays of objects with a `path` property to be
 * sorted alphabetically
 * @param (Object) obj
 * @return (string)
 */
 function sortByPath(obj) {
   return obj.path.join();
 }

/**
 * Calculates piece hashes of file list
 * @param (string) dir Directory where files are.
 * @param (Array.string) list Array of files to hash.
 * @param (number) pieceLength How many bytes long each piece will be.
 * @param options
 * @param options.maxFiles Max number of files to open during hashing.
 * @param options.maxBytes Max number of bytes to allocate during hashing.
 * @param options.stopOnErr Hashing stops if encounters an error.
 * @constructor
 * @extends EventEmitter
 * @api private
 */
var Hasher = module.exports = function(dir, list, pieceLength, options) {
  if (options == null) options = {};
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  // set defaults
  this.maxFiles  = options.maxFiles || MAX_FILES_OPENED;
  this.stopOnErr = options.stopOnErr || false;

  // init arrays that will be used every time during hashing
  this.dir             = dir;
  this.pieceLength     = pieceLength;
  this.files           = [];
  this.pieces          = [];
  this.ready           = false;
  this.hashing         = false;
  this.paused          = false;
  this.stopped         = false;

  var self = this;

  if (self.stopOnErr) {
    self.once('error', function(err) {
      self.err = err;
    });
  }

  path.exists(dir, function(exists) {
    if (!exists) {
      return self.emit('error', new Error(dir + ' does not exist'));
    }

    // Check if list containts objects or strings.
    if (typeof list[0] === 'string') {

      // create queue to handle having many files opened
      var statqueue = queue(fs.stat, self.maxFiles);
      self._getFilesStats(statqueue, list, readTasks);

    // in the case of objects, a file list from a torrent file was given.
    } else {
      self.files = list;
      process.nextTick(readTasks);
    }

    function readTasks(err) {
      // sort files alphabetically
      self.files     = _.sortBy(self.files, sortByPath);

      if (err) return self.emit('error', err);

      // if files tree traversed with no errors,
      // add read tasks to filetasks
      self._CreateFileTasks();

      // ready to start hashing
      self.emit('ready');
      self.ready = true;
      self.start();
    }
  });
};

inherits(Hasher, EventEmitter);

/**
 * Create filetasks array
 */
Hasher.prototype._CreateFileTasks = function() {
  var self = this;

  // variables to help calculate piece position
  var totalSize, piece, offset;
  totalSize = piece = offset = 0;

  // Create tasks to figure out what pieces belong to exactly
  // what files and what part of the files
  self.filetasks = [];
  self.files.forEach(function(task) {

    self.filetasks.push({
      length : task.length
    , path   : task.path
      // piece this task will write to
    , piece    : piece
      // piece buffer offset to start writing at
    , offset   : offset
    });

    // add file length to total size
    totalSize += task.length;

    // get piece next task will write to and its offset
    piece = Math.floor(totalSize / self.pieceLength);
    offset = totalSize - (self.pieceLength * piece);
  });

  self.totalSize = totalSize;
  self.pieces = piece + 1;
  // take note of the length of the last piece
  // the rest of the pieces will be of self.pieceLength length
  self.lastPieceLength = totalSize - (self.pieceLength * piece);
};

/**
 * Starts the actual hashing process
 */
Hasher.prototype.start = function() {
  var self = this;
  if (!self.ready) {
    return self.emit('error', new Error('Cannot start before being ready'));
  }
  self.hashing = true;
  self.stopped = false;

  // create piece objects
  var pieces = [];
  for (var i = 0; i < self.pieces; i++) {
    pieces[i] = {
      buffer: null
    , length: self.pieceLength
    };
  }
  pieces[pieces.length - 1].length = self.lastPieceLength;

  // keep track of progress
  var totalRead = 0;

  self.streams = [];
  var filesqueue = queue(function(task, callback) {
    // keep track how much has been read
    var readSoFar   = 0
      , pieceIndex = task.piece

    // open file for reading
    var file = path.join(self.dir, path.join.apply(null, task.path));
    var rs = fs.createReadStream(file);
    self.streams.push(rs);

    rs.on('open', function() {
      self.emit('open', file);
    });

    rs.on('data', function(data) {
      // write data to piece buffer
      var targetStart = (task.offset + readSoFar) % self.pieceLength;
      var sourceEnd =
        Math.min(data.length, self.pieceLength - task.offset);
      sourceEnd = self._writePiece(pieces, pieceIndex, targetStart,
                      data, 0, sourceEnd, file, readSoFar);
      if (sourceEnd > 0) {
        self._writePiece(pieces, pieceIndex + 1, 0,
                        data, sourceEnd, data.length,
                        file, readSoFar);
      }

      // update amount of bytes written to buffer
      readSoFar += data.length;
      totalRead += data.length;
      pieceIndex = task.piece + Math.floor(readSoFar / self.pieceLength);

      // emit hash event with piece info and progress
      var percent =
        round(totalRead / self.totalSize * 100, 2);
      self.emit('progress', percent);

    });

    rs.on('error', function(err) {
      self.streams.splice(self.streams.indexOf(rs), 1);
      self.emit('error', err);
    });

    // close this file when finished reading
    rs.on('end', function() {
      self.streams.splice(self.streams.indexOf(rs), 1);
      callback();
    });
  }, self.maxFiles);

  // when all file queues are finished,
  // join all the pieces
  filesqueue.drain = function() {
    self.hashing = false;
    self.emit('end');
  };

  // start queueing jobs
  self.filetasks.forEach(function(filetask) {
    filesqueue.push(filetask);
  });
};

Hasher.prototype._writePiece = function(pieces, index, offset,
                                       data, start, end,
                                       file, readSoFar) {
  var piece = pieces[index];
  if (!piece) return;
  if (piece.buffer === null) {
    piece.buffer = new Buffer(piece.length);
  }

  // write data to piece buffer and update bytes written
  var size = end - start;
  var diff = piece.length -= size;
  if (diff < 0) {
    end += diff;
    size += diff;
  }
  data.copy(piece.buffer, offset, start, end);

  // check if piece buffer is full
  if (diff <= 0) {
    pieces[index] = null;
    var buf = new Buffer(sha1(piece.buffer), 'binary');
    this.emit('hash', index, buf, file, readSoFar, size);
    return end;
  }

  return 0;
}

/**
 * Recursively traverses files to call fs.stat on them.
 * Will add all files in folders if any are encountered.
 * @param (Array.string) list List of files and/or directories.
 * @param (function(err)) callback
 */
Hasher.prototype._getFilesStats = function(queue, list, callback) {
  var self = this;
  var funs = [];

  list.forEach(function(file, i) {
    funs[i] = function(callback) {
      self._getFileStat(queue, file, callback);
    };
  });

  // read all file stats in parallel
  async.parallel(funs, callback);
 }

/**
 * stat is needed to know file size.
 * @param (string) dir
 * @param (Array.string) list
 */
Hasher.prototype._getFileStat = function(queue, file, callback) {
  var self = this;
  var filepath = path.join(self.dir, file);

  queue.push(filepath, function(err, stat) {
    if (err) return callback(err);

    // if this is a directory, add all files in it to torrent files
    if (stat.isDirectory()) {
      fs.readdir(filepath, function(err, dirfiles) {
        if (err) return callback(err);

        dirfiles = dirfiles.map(function(dirfile) {
          return path.join(file, dirfile);
        });
        // call _getFilesStats again
        self._getFilesStats(queue, dirfiles, callback);
      });

    } else if (stat.isFile()) {

      self.files.push({
        length: stat.size
      , path: util.splitPath(file)
      });
      callback(null);
    }
  });
 }

 /**
 * Allows pausing of the hashing process by storing functions from the
 * file and read queues that will not get called back until resume()
 * is called.
 */
Hasher.prototype.pause = function() {
  if (this.paused || !this.hashing) return;
  this.paused = true;

  this.streams.forEach(function(rs) {
    rs.pause();
  });
};

 /**
 * Resumes hashing
 */
Hasher.prototype.resume = function() {
  if (!this.paused) return;
  this.paused = false;

  this.streams.forEach(function(rs) {
    rs.resume();
  });
};

/**
 * Stops hashing completely.
 * Closes file descriptors and does not emit any more events.
 */
Hasher.prototype.destroy = function() {
  if (!this.hashing) return;
  this.stopped = true;

  this.streams.forEach(function(rs) {
    rs.destroy();
  });
};

/**
 * Aliases destroy.
 */
 Hasher.prototype.stop = Hasher.prototype.destroy;
<commit_msg>better destroy handling<commit_after>var fs           = require('fs')
  , path         = require('path')
  , crypto       = require('crypto')
  , EventEmitter = require('events').EventEmitter
  , inherits     = require('util').inherits
  , async        = require('async')
  , _            = require('underscore')
  , queue        = require('./queue')
  , util         = require('./util')

/**
 * defaults
 * @const
 */
var MAX_FILES_OPENED = 250;

/**
 * Hashes a buffer.
 * @param (Buffer) buffer
 * @return (Buffer)
 */
function sha1(buffer) {
  return crypto.createHash('sha1').update(buffer).digest();
}

/**
 * Rounds a number to given decimal place
 * @param (number) num
 * @param (number) dec
 * @return (number)
 */
function round(num, dec) {
  var pow = Math.pow(10, dec);
  return Math.round(num * pow) / pow;
}

/**
 * Allows arrays of objects with a `path` property to be
 * sorted alphabetically
 * @param (Object) obj
 * @return (string)
 */
 function sortByPath(obj) {
   return obj.path.join();
 }

/**
 * Calculates piece hashes of file list
 * @param (string) dir Directory where files are.
 * @param (Array.string) list Array of files to hash.
 * @param (number) pieceLength How many bytes long each piece will be.
 * @param options
 * @param options.maxFiles Max number of files to open during hashing.
 * @param options.maxBytes Max number of bytes to allocate during hashing.
 * @param options.stopOnErr Hashing stops if encounters an error.
 * @constructor
 * @extends EventEmitter
 * @api private
 */
var Hasher = module.exports = function(dir, list, pieceLength, options) {
  if (options == null) options = {};
  if (typeof options === 'function') {
    callback = options;
    options = {};
  }

  // set defaults
  this.maxFiles  = options.maxFiles || MAX_FILES_OPENED;
  this.stopOnErr = options.stopOnErr || false;

  // init arrays that will be used every time during hashing
  this.dir             = dir;
  this.pieceLength     = pieceLength;
  this.files           = [];
  this.pieces          = [];
  this.ready           = false;
  this.hashing         = false;
  this.paused          = false;
  this.stopped         = false;

  var self = this;

  if (self.stopOnErr) {
    self.once('error', function(err) {
      self.err = err;
    });
  }

  path.exists(dir, function(exists) {
    if (!exists) {
      return self.emit('error', new Error(dir + ' does not exist'));
    }

    // Check if list containts objects or strings.
    if (typeof list[0] === 'string') {

      // create queue to handle having many files opened
      var statqueue = queue(fs.stat, self.maxFiles);
      self._getFilesStats(statqueue, list, readTasks);

    // in the case of objects, a file list from a torrent file was given.
    } else {
      self.files = list;
      process.nextTick(readTasks);
    }

    function readTasks(err) {
      // sort files alphabetically
      self.files     = _.sortBy(self.files, sortByPath);

      if (err) return self.emit('error', err);

      // if files tree traversed with no errors,
      // add read tasks to filetasks
      self._CreateFileTasks();

      // ready to start hashing
      self.emit('ready');
      self.ready = true;
      self.start();
    }
  });
};

inherits(Hasher, EventEmitter);

/**
 * Create filetasks array
 */
Hasher.prototype._CreateFileTasks = function() {
  var self = this;

  // variables to help calculate piece position
  var totalSize, piece, offset;
  totalSize = piece = offset = 0;

  // Create tasks to figure out what pieces belong to exactly
  // what files and what part of the files
  self.filetasks = [];
  self.files.forEach(function(task) {

    self.filetasks.push({
      length : task.length
    , path   : task.path
      // piece this task will write to
    , piece    : piece
      // piece buffer offset to start writing at
    , offset   : offset
    });

    // add file length to total size
    totalSize += task.length;

    // get piece next task will write to and its offset
    piece = Math.floor(totalSize / self.pieceLength);
    offset = totalSize - (self.pieceLength * piece);
  });

  self.totalSize = totalSize;
  self.pieces = piece + 1;
  // take note of the length of the last piece
  // the rest of the pieces will be of self.pieceLength length
  self.lastPieceLength = totalSize - (self.pieceLength * piece);
};

/**
 * Starts the actual hashing process
 */
Hasher.prototype.start = function() {
  var self = this;
  if (!self.ready) {
    return self.emit('error', new Error('Cannot start before being ready'));
  }
  self.hashing = true;
  self.stopped = false;

  // create piece objects
  var pieces = [];
  for (var i = 0; i < self.pieces; i++) {
    pieces[i] = {
      buffer: null
    , length: self.pieceLength
    };
  }
  pieces[pieces.length - 1].length = self.lastPieceLength;

  // keep track of progress
  var totalRead = 0;

  self.streams = [];
  var filesqueue = queue(function(task, callback) {
    if (self.err || self.stopped) return callback();

    // keep track how much has been read
    var readSoFar   = 0
      , pieceIndex = task.piece

    // open file for reading
    var file = path.join(self.dir, path.join.apply(null, task.path));
    var rs = fs.createReadStream(file);
    self.streams.push(rs);

    rs.on('open', function() {
      self.emit('open', file);
    });

    rs.on('data', function(data) {
      // write data to piece buffer
      var targetStart = (task.offset + readSoFar) % self.pieceLength;
      var sourceEnd =
        Math.min(data.length, self.pieceLength - task.offset);
      sourceEnd = self._writePiece(pieces, pieceIndex, targetStart,
                      data, 0, sourceEnd, file, readSoFar);
      if (sourceEnd > 0) {
        self._writePiece(pieces, pieceIndex + 1, 0,
                        data, sourceEnd, data.length,
                        file, readSoFar);
      }

      // update amount of bytes written to buffer
      readSoFar += data.length;
      totalRead += data.length;
      pieceIndex = task.piece + Math.floor(readSoFar / self.pieceLength);

      // emit hash event with piece info and progress
      var percent =
        round(totalRead / self.totalSize * 100, 2);
      self.emit('progress', percent);

    });

    // close this file when there is an error or finished reading
    rs.on('error', function(err) {
      self.emit('error', err);
      self.streams.splice(self.streams.indexOf(rs), 1);
      callback();
    });

    rs.on('end', function() {
      self.streams.splice(self.streams.indexOf(rs), 1);
      callback();
    });
  }, self.maxFiles);

  // when all file queues are finished,
  // join all the pieces
  filesqueue.drain = function() {
    self.hashing = false;
    self.emit('end');
  };

  // start queueing jobs
  self.filetasks.forEach(function(filetask) {
    filesqueue.push(filetask);
  });
};

Hasher.prototype._writePiece = function(pieces, index, offset,
                                       data, start, end,
                                       file, readSoFar) {
  var piece = pieces[index];
  if (!piece) return;
  if (piece.buffer === null) {
    piece.buffer = new Buffer(piece.length);
  }

  // write data to piece buffer and update bytes written
  var size = end - start;
  var diff = piece.length -= size;
  if (diff < 0) {
    end += diff;
    size += diff;
  }
  data.copy(piece.buffer, offset, start, end);

  // check if piece buffer is full
  if (diff <= 0) {
    pieces[index] = null;
    var buf = new Buffer(sha1(piece.buffer), 'binary');
    this.emit('hash', index, buf, file, readSoFar, size);
    return end;
  }

  return 0;
}

/**
 * Recursively traverses files to call fs.stat on them.
 * Will add all files in folders if any are encountered.
 * @param (Array.string) list List of files and/or directories.
 * @param (function(err)) callback
 */
Hasher.prototype._getFilesStats = function(queue, list, callback) {
  var self = this;
  var funs = [];

  list.forEach(function(file, i) {
    funs[i] = function(callback) {
      self._getFileStat(queue, file, callback);
    };
  });

  // read all file stats in parallel
  async.parallel(funs, callback);
 }

/**
 * stat is needed to know file size.
 * @param (string) dir
 * @param (Array.string) list
 */
Hasher.prototype._getFileStat = function(queue, file, callback) {
  var self = this;
  var filepath = path.join(self.dir, file);

  queue.push(filepath, function(err, stat) {
    if (err) return callback(err);

    // if this is a directory, add all files in it to torrent files
    if (stat.isDirectory()) {
      fs.readdir(filepath, function(err, dirfiles) {
        if (err) return callback(err);

        dirfiles = dirfiles.map(function(dirfile) {
          return path.join(file, dirfile);
        });
        // call _getFilesStats again
        self._getFilesStats(queue, dirfiles, callback);
      });

    } else if (stat.isFile()) {

      self.files.push({
        length: stat.size
      , path: util.splitPath(file)
      });
      callback(null);
    }
  });
 }

 /**
 * Allows pausing of the hashing process by storing functions from the
 * file and read queues that will not get called back until resume()
 * is called.
 */
Hasher.prototype.pause = function() {
  if (this.paused || !this.hashing) return;
  this.paused = true;

  this.streams.forEach(function(rs) {
    rs.pause();
  });
};

 /**
 * Resumes hashing
 */
Hasher.prototype.resume = function() {
  if (!this.paused) return;
  this.paused = false;

  this.streams.forEach(function(rs) {
    rs.resume();
  });
};

/**
 * Stops hashing completely.
 * Closes file descriptors and does not emit any more events.
 */
Hasher.prototype.destroy = function() {
  if (!this.hashing) return;
  this.stopped = true;

  this.streams.forEach(function(rs) {
    rs.destroy();
  });
};

/**
 * Aliases destroy.
 */
 Hasher.prototype.stop = Hasher.prototype.destroy;
<|endoftext|>


<commit_before>module.exports = {

  sendWithTpl: function(inputs, exits) {
    var that = this;
    require('email-templates')(inputs.mail.templatesDir, function(err, template){
      if (err) return exits.error(err);
      template(inputs.mail.folder, inputs.mail.params, function(err, html, text) {
        inputs.mail.html = html;
        inputs.mail.text = text;
        that.sendMail(inputs, exits);
      });
    });
  },

  transport: function(inputs) {
    if (input.customTransport) {
      // inputs.customTransport = require('nodemailer-smtp-transport') - is possible Oo
      var options = input.customTransport(inputs);
      //var options = require('nodemailer-smtp-transport')(inputs);
    }
    return require('nodemailer').createTransport(options||inputs);
  },

  sendMail: function(inputs, exits) {
    var transporter = this.transport(inputs);
    transporter.sendMail(inputs.mail, function(err, result){
      transporter.close();
      if (err) return exits.error(err);
      return exits.success(result);
    });
  },

};
<commit_msg>fix typo in transport<commit_after>module.exports = {

  sendWithTpl: function(inputs, exits) {
    var that = this;
    require('email-templates')(inputs.mail.templatesDir, function(err, template){
      if (err) return exits.error(err);
      template(inputs.mail.folder, inputs.mail.params, function(err, html, text) {
        inputs.mail.html = html;
        inputs.mail.text = text;
        that.sendMail(inputs, exits);
      });
    });
  },

  transport: function(inputs) {
    if (inputs.customTransport) {
      // inputs.customTransport = require('nodemailer-smtp-transport') - is possible Oo
      var options = inputs.customTransport(inputs);
      //var options = require('nodemailer-smtp-transport')(inputs);
    }
    return require('nodemailer').createTransport(options||inputs);
  },

  sendMail: function(inputs, exits) {
    var transporter = this.transport(inputs);
    transporter.sendMail(inputs.mail, function(err, result){
      transporter.close();
      if (err) return exits.error(err);
      return exits.success(result);
    });
  },

};
<|endoftext|>


<commit_before>/*
 * openi_data_api
 * openi-ict.eu
 *
 * Copyright (c) 2013 dmccarthy
 * Licensed under the MIT license.
 */

'use strict';

var dbc          = require('dbc');
var openiLogger  = require('openi-logger')
var openiUtils   = require('openi-cloudlet-utils')
var url          = require('url')

var init = function(logger_params){
   this.logger = openiLogger(logger_params);
}

var createCloudlet = function(msg){

   dbc.assert(null !== msg.json)

   var cloudletId      = 'c_' + openiUtils.randomHash();
   var cloudletRestURL = "http://" + msg.headers.host + '/api/v1/cloudlets/' + cloudletId;

   var cloudletDBObj = {
      id           : cloudletId,
      alias        : msg.json.alias,
      username     : msg.json.username,
      date_created : new Date().toJSON()
   }

   var responseObject = {
      value      : true,
      data       : {
         cloudletId : cloudletId
      }
   }

   return {
      'dao_actions'      : [
         { 'action'       : 'POST',
            'database'    : cloudletId,
            'object_name' : 'meta',
            'object_data' : cloudletDBObj,
            'rest_uuid'   : cloudletRestURL }
      ],
      'clients'      : [
         {'uuid' : msg.uuid, 'connId' : msg.connId }
      ]
   }
}

var deleteCloudlet = function(msg){

   var cloudletId = msg.path.replace('/api/v1/cloudlets/', '')

   var responseObject = {
      value      : true,
      data       : {
         cloudletId : cloudletId
      }
   }

   return {
      'dao_actions'      : [
         { 'action' : 'DELETE', 'database': cloudletId }
      ],
      'clients'      : [
         {'uuid' : msg.uuid, 'connId' : msg.connId }
      ]
   }
}

var exportCloudlet = function(msg){

   if ( msg.path === '/api/v1/cloudlets'){

      var url_parts = url.parse(msg.headers.URI, true);
      var query     = url_parts.query;
      var viewName  = 'cloudlet_list';

      if ('true' === query.id_only){
         viewName  = 'cloudlet_id_list';
      }

      return {
         'dao_actions'      : [
            {
               'action'       : 'VIEW',
               'design_doc'  : 'cloudlets_views',
               'view_name'   : viewName,
               'count'       : Number(query.limit),
               'skip'        : Number(query.skip),
               'filter_show' : query.only_show_properties,
               'resp_type'   : 'cloudlet'
            }
         ],
         'clients'      : [
            {'uuid' : msg.uuid, 'connId' : msg.connId }
         ]
      }
   }
   else{
      var cloudletId = msg.path.replace('/api/v1/cloudlets/', '')

      return {
         'dao_actions'      : [
            { 'action' : 'FETCH', 'database': cloudletId, 'object_name': {} }
         ],
         'clients'      : [
            {'uuid' : msg.uuid, 'connId' : msg.connId }
         ]
      }
   }

}

var processMongrel2Message = function (msg) {

   this.logger.log('debug', 'process Mongrel 2 Message function')

   this.logger.logMongrel2Message(msg)

   if (     msg.headers.METHOD === 'POST'){
      return createCloudlet(msg)
   }
   else if (msg.headers.METHOD === 'DELETE'){
      return deleteCloudlet(msg)
   }
   else if (msg.headers.METHOD === 'GET'){
      return exportCloudlet(msg)
   }
   else{
      this.logger.log('debug', 'Matching function wasn\'t found')
   }
}

module.exports.init                   = init
module.exports.processMongrel2Message = processMongrel2Message<commit_msg>Added support for @ characters in model names, moved long ids to @location, and introduced short ids<commit_after>/*
 * openi_data_api
 * openi-ict.eu
 *
 * Copyright (c) 2013 dmccarthy
 * Licensed under the MIT license.
 */

'use strict';

var dbc          = require('dbc');
var openiLogger  = require('openi-logger')
var openiUtils   = require('openi-cloudlet-utils')
var url          = require('url')

var init = function(logger_params){
   this.logger = openiLogger(logger_params);
}

var createCloudlet = function(msg){

   dbc.assert(null !== msg.json)

   var cloudletId      = 'c_' + openiUtils.randomHash();
   var cloudletRestURL = "http://" + msg.headers.host + '/api/v1/cloudlets/' + cloudletId;

   var cloudletDBObj = {
      id             : cloudletId,
      location       : cloudletRestURL,
      alias          : msg.json.alias,
      username       : msg.json.username,
      _date_created  : new Date().toJSON()
   }

   return {
      'dao_actions'      : [
         {
            'action'       : 'POST',
            'database'    : cloudletId,
            'object_name' : 'meta',
            'object_data' : cloudletDBObj,
            'resp_type'   : 'cloudlet',
            'id'          : cloudletId
         }
      ],
      'clients'      : [
         {'uuid' : msg.uuid, 'connId' : msg.connId }
      ]
   }
}

var deleteCloudlet = function(msg){

   var cloudletId = msg.path.replace('/api/v1/cloudlets/', '')

   var responseObject = {
      value      : true,
      data       : {
         cloudletId : cloudletId
      }
   }

   return {
      'dao_actions'      : [
         { 'action' : 'DELETE', 'database': cloudletId }
      ],
      'clients'      : [
         {'uuid' : msg.uuid, 'connId' : msg.connId }
      ]
   }
}

var exportCloudlet = function(msg){

   if ( msg.path === '/api/v1/cloudlets'){

      var url_parts = url.parse(msg.headers.URI, true);
      var query     = url_parts.query;
      var viewName  = 'cloudlet_list';

      if ('true' === query.id_only){
         viewName  = 'cloudlet_id_list';
      }

      return {
         'dao_actions'      : [
            {
               'action'       : 'VIEW',
               'design_doc'  : 'cloudlets_views',
               'view_name'   : viewName,
               'count'       : Number(query.limit),
               'skip'        : Number(query.skip),
               'filter_show' : query.only_show_properties,
               'resp_type'   : 'cloudlet'
            }
         ],
         'clients'      : [
            {'uuid' : msg.uuid, 'connId' : msg.connId }
         ]
      }
   }
   else{
      var cloudletId = msg.path.replace('/api/v1/cloudlets/', '')

      console.log(cloudletId)

      return {
         'dao_actions'      : [
            { 'action' : 'GET', 'database': cloudletId, 'object_name': {}, 'resp_type':'cloudlet' }
         ],
         'clients'      : [
            {'uuid' : msg.uuid, 'connId' : msg.connId }
         ]
      }
   }

}

var processMongrel2Message = function (msg) {

   this.logger.log('debug', 'process Mongrel 2 Message function')

   this.logger.logMongrel2Message(msg)

   if (     msg.headers.METHOD === 'POST'){
      return createCloudlet(msg)
   }
   else if (msg.headers.METHOD === 'DELETE'){
      return deleteCloudlet(msg)
   }
   else if (msg.headers.METHOD === 'GET'){
      return exportCloudlet(msg)
   }
   else{
      this.logger.log('debug', 'Matching function wasn\'t found')
   }
}

module.exports.init                   = init
module.exports.processMongrel2Message = processMongrel2Message<|endoftext|>


<commit_before>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/

/**********************************************************\
 *                                                        *
 * hprose.js                                              *
 *                                                        *
 * hprose for Node.js.                                    *
 *                                                        *
 * LastModified: Feb 25, 2016                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

/*global global */
/*jshint node:true, noused:true, eqeqeq:true */
'use strict';

global.hprose = global.hprose || Object.create(null);

require('./common/HarmonyMaps.js');
require('./common/setImmediate.js');
require('./common/Future.js');
require('./common/ResultMode.js');

require('./io/BytesIO.js');
require('./io/ClassManager.js');
require('./io/Tags.js');
require('./io/Writer.js');
require('./io/Reader.js');
require('./io/Formatter.js');

require('./client/Client.js');
require('./client/HttpClient.js');
require('./client/SocketClient.js');
require('./client/WebSocketClient.js');

require('./server/Service.js');
require('./server/HttpService.js');
require('./server/HttpServer.js');
require('./server/SocketService.js');
require('./server/SocketServer.js');
require('./server/WebSocketService.js');
require('./server/WebSocketServer.js');
require('./server/Server.js');

require('./filter/JSONRPCClientFilter.js');
require('./filter/JSONRPCServiceFilter.js');

global.HproseCompleter = global.hprose.Completer;
global.HproseFuture = global.hprose.Future;
global.HproseResultMode = global.hprose.ResultMode;

global.HproseBytesIO = global.hprose.BytesIO;
global.HproseClassManager = global.hprose.ClassManager;
global.HproseTags = global.hprose.Tags;
global.HproseWriter = global.hprose.Writer;
global.HproseRawReader = global.hprose.RawReader;
global.HproseReader = global.hprose.Reader;
global.HproseFormatter = global.hprose.Formatter;

global.HproseClient = global.hprose.Client;
global.HproseHttpClient = global.hprose.HttpClient;
global.HproseSocketClient = global.hprose.SocketClient;
global.HproseTcpClient = global.hprose.TcpClient;
global.HproseUnixClient = global.hprose.UnixClient;
global.HproseWebSocketClient = global.hprose.WebSocketClient;

global.HproseService = global.hprose.Service;
global.HproseServer = global.hprose.Server;
global.HproseHttpService = global.hprose.HttpService;
global.HproseHttpServer = global.hprose.HttpServer;
global.HproseSocketService = global.hprose.SocketService;
global.HproseSocketServer = global.hprose.SocketServer;
global.HproseTcpServer = global.hprose.TcpServer;
global.HproseUnixServer = global.hprose.UnixServer;
global.HproseWebSocketService = global.hprose.WebSocketService;
global.HproseWebSocketServer = global.hprose.WebSocketServer;

global.HproseJSONRPCClientFilter = global.hprose.JSONRPCClientFilter;
global.HproseJSONRPCServiceFilter = global.hprose.JSONRPCServiceFilter;

global.hprose.common = {
    Completer: global.hprose.Completer,
    Future: global.hprose.Future,
    ResultMode: global.hprose.ResultMode
};

global.hprose.io = {
    BytesIO: global.hprose.BytesIO,
    ClassManager: global.hprose.ClassManager,
    Tags: global.hprose.Tags,
    RawReader: global.hprose.RawReader,
    Reader: global.hprose.Reader,
    Writer: global.hprose.Writer,
    Formatter: global.hprose.Formatter
};

global.hprose.client = {
    Client: global.hprose.Client,
    HttpClient: global.hprose.HttpClient,
    SocketClient: global.hprose.SocketClient,
    TcpClient: global.hprose.TcpClient,
    UnixClient: global.hprose.UnixClient,
    WebSocketClient: global.hprose.WebSocketClient
};

global.hprose.server = {
    Service: global.hprose.Service,
    Server: global.hprose.Server,
    HttpService: global.hprose.HttpService,
    HttpServer: global.hprose.HttpServer,
    SocketService: global.hprose.SocketService,
    SocketServer: global.hprose.SocketServer,
    TcpServer: global.hprose.TcpServer,
    UnixServer: global.hprose.UnixServer,
    WebSocketService: global.hprose.WebSocketService,
    WebSocketServer: global.hprose.WebSocketServer
};

global.hprose.filter = {
    JSONRPCClientFilter: global.hprose.JSONRPCClientFilter,
    JSONRPCServiceFilter: global.hprose.JSONRPCServiceFilter
};

module.exports = global.hprose;
<commit_msg>Added Helper.js<commit_after>/**********************************************************\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: http://www.hprose.com/                 |
|                   http://www.hprose.org/                 |
|                                                          |
\**********************************************************/

/**********************************************************\
 *                                                        *
 * hprose.js                                              *
 *                                                        *
 * hprose for Node.js.                                    *
 *                                                        *
 * LastModified: Feb 25, 2016                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
\**********************************************************/

/*global global */
/*jshint node:true, noused:true, eqeqeq:true */
'use strict';

global.hprose = global.hprose || Object.create(null);

require('./common/Helper.js');
require('./common/HarmonyMaps.js');
require('./common/setImmediate.js');
require('./common/Future.js');
require('./common/ResultMode.js');

require('./io/BytesIO.js');
require('./io/ClassManager.js');
require('./io/Tags.js');
require('./io/Writer.js');
require('./io/Reader.js');
require('./io/Formatter.js');

require('./client/Client.js');
require('./client/HttpClient.js');
require('./client/SocketClient.js');
require('./client/WebSocketClient.js');

require('./server/Service.js');
require('./server/HttpService.js');
require('./server/HttpServer.js');
require('./server/SocketService.js');
require('./server/SocketServer.js');
require('./server/WebSocketService.js');
require('./server/WebSocketServer.js');
require('./server/Server.js');

require('./filter/JSONRPCClientFilter.js');
require('./filter/JSONRPCServiceFilter.js');

global.HproseCompleter = global.hprose.Completer;
global.HproseFuture = global.hprose.Future;
global.HproseResultMode = global.hprose.ResultMode;

global.HproseBytesIO = global.hprose.BytesIO;
global.HproseClassManager = global.hprose.ClassManager;
global.HproseTags = global.hprose.Tags;
global.HproseWriter = global.hprose.Writer;
global.HproseRawReader = global.hprose.RawReader;
global.HproseReader = global.hprose.Reader;
global.HproseFormatter = global.hprose.Formatter;

global.HproseClient = global.hprose.Client;
global.HproseHttpClient = global.hprose.HttpClient;
global.HproseSocketClient = global.hprose.SocketClient;
global.HproseTcpClient = global.hprose.TcpClient;
global.HproseUnixClient = global.hprose.UnixClient;
global.HproseWebSocketClient = global.hprose.WebSocketClient;

global.HproseService = global.hprose.Service;
global.HproseServer = global.hprose.Server;
global.HproseHttpService = global.hprose.HttpService;
global.HproseHttpServer = global.hprose.HttpServer;
global.HproseSocketService = global.hprose.SocketService;
global.HproseSocketServer = global.hprose.SocketServer;
global.HproseTcpServer = global.hprose.TcpServer;
global.HproseUnixServer = global.hprose.UnixServer;
global.HproseWebSocketService = global.hprose.WebSocketService;
global.HproseWebSocketServer = global.hprose.WebSocketServer;

global.HproseJSONRPCClientFilter = global.hprose.JSONRPCClientFilter;
global.HproseJSONRPCServiceFilter = global.hprose.JSONRPCServiceFilter;

global.hprose.common = {
    Completer: global.hprose.Completer,
    Future: global.hprose.Future,
    ResultMode: global.hprose.ResultMode
};

global.hprose.io = {
    BytesIO: global.hprose.BytesIO,
    ClassManager: global.hprose.ClassManager,
    Tags: global.hprose.Tags,
    RawReader: global.hprose.RawReader,
    Reader: global.hprose.Reader,
    Writer: global.hprose.Writer,
    Formatter: global.hprose.Formatter
};

global.hprose.client = {
    Client: global.hprose.Client,
    HttpClient: global.hprose.HttpClient,
    SocketClient: global.hprose.SocketClient,
    TcpClient: global.hprose.TcpClient,
    UnixClient: global.hprose.UnixClient,
    WebSocketClient: global.hprose.WebSocketClient
};

global.hprose.server = {
    Service: global.hprose.Service,
    Server: global.hprose.Server,
    HttpService: global.hprose.HttpService,
    HttpServer: global.hprose.HttpServer,
    SocketService: global.hprose.SocketService,
    SocketServer: global.hprose.SocketServer,
    TcpServer: global.hprose.TcpServer,
    UnixServer: global.hprose.UnixServer,
    WebSocketService: global.hprose.WebSocketService,
    WebSocketServer: global.hprose.WebSocketServer
};

global.hprose.filter = {
    JSONRPCClientFilter: global.hprose.JSONRPCClientFilter,
    JSONRPCServiceFilter: global.hprose.JSONRPCServiceFilter
};

module.exports = global.hprose;
<|endoftext|>