<commit_before>/**
 * Module dependencies
 */

var path = require('path');
var Writable = require('stream').Writable;
var Transform = require('stream').Transform;
var concat = require('concat-stream');
var _ = require('lodash');
_.defaultsDeep = require('merge-defaults');
var knox = require('knox');
var S3MultipartUpload = require('knox-mpu');
var S3Lister = require('s3-lister');

/**
 * skipper-s3
 *
 * @param  {Object} globalOpts
 * @return {Object}
 */

module.exports = function SkipperS3 (globalOpts) {
  globalOpts = globalOpts || {};

  _.defaults(globalOpts, {

      // By default, create new files on disk
      // using their uploaded filenames.
      // (no overwrite-checking is performed!!)
      saveAs: function (__newFile) {
        return __newFile.filename;
      },

      // Max bytes (defaults to ~15MB)
      maxBytes: 15000000,

      // The bucket we're going to upload stuff into
      // bucket: '',

      // Our S3 API key
      // key: '',

      // Our S3 API secret
      // secret: '',

      // By default, upload files to `/` (within the bucket)
      dirname: '/'
    });

  var adapter = {

    read: function (filepath, cb) {

      // DONT trim leading slash to form prefix!!
      var prefix = filepath;
      // var prefix = dirpath.replace(/^\//, '');
      // console.log('Trying to look up:', prefix);

      var client = knox.createClient({
        key: globalOpts.key,
        secret: globalOpts.secret,
        bucket: globalOpts.bucket
      });

      // Build a noop transform stream that will pump the S3 output through
      var __transform__ = new Transform();
      __transform__._transform = function (chunk, encoding, callback) {
        return callback(null, chunk);
      };

      client.get(prefix).on('response', function(s3res){
        // Handle explicit s3res errors
        s3res.once('error', function (err) {
          __transform__.emit('error', err);
        });

        // check whether we got an actual file stream:
        if (s3res.statusCode < 300) {
          s3res.pipe(__transform__);
        }
        // or an error:
        else {
          // Wait for the body of the error message to stream in:
          var body = '';
          s3res.setEncoding('utf8');
          s3res.on('readable', function (){
            var chunk = s3res.read();
            if (typeof chunk === 'string') body += chunk;
          });
          // Then build the error and emit it
          s3res.once('end', function () {
            var err = new Error();
            err.status = s3res.statusCode;
            err.headers = s3res.headers;
            err.message = 'Non-200 status code returned from S3 for requested file.';
            if (body) err.message += ('\n'+body);
            __transform__.emit('error', err);
          });
        }
      })
      .end();

      if (cb) {
        var firedCb;
        __transform__.once('error', function (err) {
          if (firedCb) return;
          firedCb = true;
          cb(err);
        });
        __transform__.pipe(concat(function (data) {
          if (firedCb) return;
          firedCb = true;
          cb(null, data);
        }));
      }

      return __transform__;
    },

    rm: function (filepath, cb){
      return cb(new Error('TODO'));
    },
    ls: function (dirpath, cb) {
      var client = knox.createClient({
        key: globalOpts.key,
        secret: globalOpts.secret,
        bucket: globalOpts.bucket
      });

      // TODO: take a look at maxKeys
      // https://www.npmjs.org/package/s3-lister

      // Allow empty dirpath (defaults to `/`)
      if (!dirpath) {
        dirpath='/';
      }
      // Strip leading slash from dirpath to form prefix
      var prefix = dirpath.replace(/^\//, '');

      var lister = new S3Lister(client, {
        prefix : prefix
      });

      if (!cb) {
        return lister;
      }
      else {
        var firedCb;
        lister.once('error', function (err) {
          if(firedCb)return;
          firedCb=true;
          cb(err);
        });
        lister.pipe(concat(function (data) {
          if(firedCb)return;
          firedCb=true;

          // Pluck just the "Key" (i.e. file path)
          // and return only the filename (i.e. snip
          // off the path prefix)
          data = _.pluck(data, 'Key');
          data = _.map(data, function snipPathPrefixes (thisPath) {
            return thisPath.replace(/^.*[\/]([^\/]*)$/, '$1');
          });

          // console.log('______ files _______\n', data);
          cb(null, data);
        }));

        // TODO: marshal each matched file in the stream
        // (using a Transform- take a look at all the
        //  "plucking" and stuff I have going on above ^)
        return lister;
      }
    },

    receiver: S3Receiver,
    receive: S3Receiver
  };

  return adapter;

  /**
   * A simple receiver for Skipper that writes Upstreams to
   * S3 to the configured bucket at the configured path.
   *
   * Includes a garbage-collection mechanism for failed
   * uploads.
   *
   * @param  {Object} options
   * @return {Stream.Writable}
   */
  function S3Receiver (options) {
    options = options || {};
    options = _.defaults(options, globalOpts);

    var receiver__ = Writable({
      objectMode: true
    });

    // This `_write` method is invoked each time a new file is received
    // from the Readable stream (Upstream) which is pumping filestreams
    // into this receiver.  (filename === `__newFile.filename`).
    receiver__._write = function onFile(__newFile, encoding, next) {

      // Garbage-collect the bytes that were already written for this file.
      // (called when a read or write error occurs)
      function gc(err) {
        // console.log('************** Garbage collecting file `' + __newFile.filename + '` located @ ' + filePath + '...');
        adapter.rm(filePath, function (gcErr) {
          if (gcErr) return done([err].concat([gcErr]));
          else return done();
        });
      }

      // Determine location where file should be written:
      // -------------------------------------------------------
      var filePath, dirPath, filename;
      dirPath = options.dirname;
      filename = options.filename || options.saveAs(__newFile);
      filePath = path.join(dirPath, filename);
      // -------------------------------------------------------

      // console.log(('Receiver: Received file `' + __newFile.filename + '` from an Upstream.').grey);

      // console.log('->',options);
      var mpu = new S3MultipartUpload({
        objectName: filePath,
        stream: __newFile,
        maxUploadSize: options.maxBytes,
        client: knox.createClient({
          key: options.key,
          secret: options.secret,
          bucket: options.bucket
        })
      }, function (err, body) {
        if (err) {
          // console.log(('Receiver: Error writing `' + __newFile.filename + '`:: ' + require('util').inspect(err) + ' :: Cancelling upload and cleaning up already-written bytes...').red);
          receiver__.emit('error', err);
          return;
        }

        // Package extra metadata about the S3 response on each file stream
        // in case we decide we want to use it for something later
        __newFile.extra = body;

        // console.log(('Receiver: Finished writing `' + __newFile.filename + '`').grey);
        next();
      });

      mpu.on('progress', function(data) {
        receiver__.emit('progress', {
          name: __newFile.filename,
          written: data.written,
          total: data.total,
          percent: data.percent
        });
      });
    };

    return receiver__;
  }

};

<commit_msg>With logs in place, added a couple of todos (one for cancelling uploads that were aborted, and the other for finding the miscreant responsible for out backpressure dillemna.)<commit_after>/**
 * Module dependencies
 */

var path = require('path');
var Writable = require('stream').Writable;
var Transform = require('stream').Transform;
var concat = require('concat-stream');
var _ = require('lodash');
_.defaultsDeep = require('merge-defaults');
var knox = require('knox');
var S3MultipartUpload = require('knox-mpu');
var S3Lister = require('s3-lister');

/**
 * skipper-s3
 *
 * @param  {Object} globalOpts
 * @return {Object}
 */

module.exports = function SkipperS3 (globalOpts) {
  globalOpts = globalOpts || {};

  _.defaults(globalOpts, {

      // By default, create new files on disk
      // using their uploaded filenames.
      // (no overwrite-checking is performed!!)
      saveAs: function (__newFile) {
        return __newFile.filename;
      },

      // Max bytes (defaults to ~15MB)
      maxBytes: 15000000,

      // The bucket we're going to upload stuff into
      // bucket: '',

      // Our S3 API key
      // key: '',

      // Our S3 API secret
      // secret: '',

      // By default, upload files to `/` (within the bucket)
      dirname: '/'
    });

  var adapter = {

    read: function (filepath, cb) {

      // DONT trim leading slash to form prefix!!
      var prefix = filepath;
      // var prefix = dirpath.replace(/^\//, '');
      console.log('Trying to look up:', prefix);

      var client = knox.createClient({
        key: globalOpts.key,
        secret: globalOpts.secret,
        bucket: globalOpts.bucket
      });

      // Build a noop transform stream that will pump the S3 output through
      var __transform__ = new Transform();
      __transform__._transform = function (chunk, encoding, callback) {
        return callback(null, chunk);
      };

      client.get(prefix).on('response', function(s3res){
        // Handle explicit s3res errors
        s3res.once('error', function (err) {
          __transform__.emit('error', err);
        });

        // check whether we got an actual file stream:
        if (s3res.statusCode < 300) {
          s3res.pipe(__transform__);
        }
        // or an error:
        else {
          // Wait for the body of the error message to stream in:
          var body = '';
          s3res.setEncoding('utf8');
          s3res.on('readable', function (){
            var chunk = s3res.read();
            if (typeof chunk === 'string') body += chunk;
          });
          // Then build the error and emit it
          s3res.once('end', function () {
            var err = new Error();
            err.status = s3res.statusCode;
            err.headers = s3res.headers;
            err.message = 'Non-200 status code returned from S3 for requested file.';
            if (body) err.message += ('\n'+body);
            __transform__.emit('error', err);
          });
        }
      })
      .end();

      if (cb) {
        var firedCb;
        __transform__.once('error', function (err) {
          if (firedCb) return;
          firedCb = true;
          cb(err);
        });
        __transform__.pipe(concat(function (data) {
          if (firedCb) return;
          firedCb = true;
          cb(null, data);
        }));
      }

      return __transform__;
    },

    rm: function (filepath, cb){
      return cb(new Error('TODO'));
    },
    ls: function (dirpath, cb) {
      var client = knox.createClient({
        key: globalOpts.key,
        secret: globalOpts.secret,
        bucket: globalOpts.bucket
      });

      // TODO: take a look at maxKeys
      // https://www.npmjs.org/package/s3-lister

      // Allow empty dirpath (defaults to `/`)
      if (!dirpath) {
        dirpath='/';
      }
      // Strip leading slash from dirpath to form prefix
      var prefix = dirpath.replace(/^\//, '');

      var lister = new S3Lister(client, {
        prefix : prefix
      });

      if (!cb) {
        return lister;
      }
      else {
        var firedCb;
        lister.once('error', function (err) {
          if(firedCb)return;
          firedCb=true;
          cb(err);
        });
        lister.pipe(concat(function (data) {
          if(firedCb)return;
          firedCb=true;

          // Pluck just the "Key" (i.e. file path)
          // and return only the filename (i.e. snip
          // off the path prefix)
          data = _.pluck(data, 'Key');
          data = _.map(data, function snipPathPrefixes (thisPath) {
            return thisPath.replace(/^.*[\/]([^\/]*)$/, '$1');
          });

          console.log('______ files _______\n', data);
          cb(null, data);
        }));

        // TODO: marshal each matched file in the stream
        // (using a Transform- take a look at all the
        //  "plucking" and stuff I have going on above ^)
        return lister;
      }
    },

    receiver: S3Receiver,
    receive: S3Receiver
  };

  return adapter;

  /**
   * A simple receiver for Skipper that writes Upstreams to
   * S3 to the configured bucket at the configured path.
   *
   * Includes a garbage-collection mechanism for failed
   * uploads.
   *
   * @param  {Object} options
   * @return {Stream.Writable}
   */
  function S3Receiver (options) {
    options = options || {};
    options = _.defaults(options, globalOpts);

    var receiver__ = Writable({
      objectMode: true
    });

    receiver__.once('error', function (err) {
      console.log('ERROR ON RECEIVER__ ::',err);
    });

    // This `_write` method is invoked each time a new file is received
    // from the Readable stream (Upstream) which is pumping filestreams
    // into this receiver.  (filename === `__newFile.filename`).
    receiver__._write = function onFile(__newFile, encoding, next) {

      __newFile.once('error', function (err) {
        console.log('ERROR ON file read stream in receiver (%s) ::', __newFile.filename, err);
        // TODO: the upload has been cancelled, so we need to stop writing
        // all buffered bytes, then call gc() to remove the parts of the file that WERE written.
        // (caveat: may not need to actually call gc()-- need to see how this is implemented
        // in the underlying knox-mpu module)
      });

      // Garbage-collect the bytes that were already written for this file.
      // (called when a read or write error occurs)
      // function gc(err) {
      //   console.log('************** Garbage collecting file `' + __newFile.filename + '` located @ ' + filePath + '...');
      //   adapter.rm(filePath, function (gcErr) {
      //     if (gcErr) return done([err].concat([gcErr]));
      //     else return done();
      //   });
      // }

      // Determine location where file should be written:
      // -------------------------------------------------------
      var filePath, dirPath, filename;
      dirPath = options.dirname;
      filename = options.filename || options.saveAs(__newFile);
      filePath = path.join(dirPath, filename);
      // -------------------------------------------------------

      console.log(('Receiver: Received file `' + __newFile.filename + '` from an Upstream.').grey);

      // TODO: fix backpressure issues
      // It would appear that knox-mpu is not
      // properly handling backpressure, which
      // breaks the TCP backpressure we're expecting
      // to keep ourselves from overflowing
      // Not 100% sure yet- problem could also
      // be in multiparty.

      console.log('->',options);
      var mpu = new S3MultipartUpload({
        objectName: filePath,
        stream: __newFile,
        maxUploadSize: options.maxBytes,
        client: knox.createClient({
          key: options.key,
          secret: options.secret,
          bucket: options.bucket
        })
      }, function (err, body) {
        if (err) {
          console.log(('Receiver: Error writing `' + __newFile.filename + '`:: ' + require('util').inspect(err) + ' :: Cancelling upload and cleaning up already-written bytes...').red);
          receiver__.emit('error', err);
          return;
        }

        // Package extra metadata about the S3 response on each file stream
        // in case we decide we want to use it for something later
        __newFile.extra = body;

        console.log(('Receiver: Finished writing `' + __newFile.filename + '`').grey);
        next();
      });

      mpu.on('progress', function(data) {
        console.log('Uploading (%s)..',__newFile.filename, data);
        receiver__.emit('progress', {
          name: __newFile.filename,
          written: data.written,
          total: data.total,
          percent: data.percent
        });
      });
    };

    return receiver__;
  }

};

<|endoftext|>


<commit_before>function Texture(opts) {
  var self = this;
  if (!(this instanceof Texture)) return new Texture(opts || {});
  this.THREE              = opts.THREE          || require('three');
  this.materials          = [];
  this.texturePath        = opts.texturePath    || '/textures/';
  this.materialParams     = opts.materialParams || {};
  this.materialType       = opts.materialType   || this.THREE.MeshLambertMaterial;
  this.materialIndex      = [];
  this._materialDefaults  = { ambient: 0xbbbbbb, transparent: true };
  this.applyTextureParams = opts.applyTextureParams || function(map) {
    map.magFilter = self.THREE.NearestFilter;
    map.minFilter = self.THREE.LinearMipMapLinearFilter;
    map.wrapT     = self.THREE.RepeatWrapping;
    map.wrapS     = self.THREE.RepeatWrapping;
  }
}
module.exports = Texture;

Texture.prototype.load = function(names, opts) {
  var self = this;
  opts = self._options(opts);
  if (!isArray(names)) names = [names];
  if (!hasSubArray(names)) names = [names];
  return [].concat.apply([], names.map(function(name) {
    name = self._expandName(name);
    self.materialIndex.push([self.materials.length, self.materials.length + name.length]);
    return name.map(function(n) {
      if (n instanceof self.THREE.Texture) {
        var map = n;
        n = n.name;
      } else if (typeof n === 'string') {
        var map = self.THREE.ImageUtils.loadTexture(self.texturePath + ext(n));
      } else {
        var map = new self.THREE.Texture(n);
        n = map.name;
      }
      self.applyTextureParams.call(self, map);
      var mat = new opts.materialType(opts.materialParams);
      mat.map = map;
      mat.name = n;
      self.materials.push(mat);
      return mat;
    });
  }));
};

Texture.prototype.get = function(index) {
  if (index == null) return this.materials;
  if (typeof index === 'number') {
    index = this.materialIndex[index];
  } else {
    for (var i = 0; i < this.materials.length; i++) {
      if (index === this.materials[i].name) {
        index = i;
        break;
      }
    }
    for (var i = 0; i < this.materialIndex.length; i++) {
      var idx = this.materialIndex[i];
      if (index >= idx[0] && index < idx[1]) {
        index = idx;
        break;
      }
    }
  }
  return this.materials.slice(index[0], index[1]);
};

Texture.prototype._expandName = function(name) {
  if (name.top) return [name.back, name.front, name.top, name.bottom, name.left, name.right];
  if (!isArray(name)) name = [name];
  // load the 0 texture to all
  if (name.length === 1) name = [name[0],name[0],name[0],name[0],name[0],name[0]];
  // 0 is top/bottom, 1 is sides
  if (name.length === 2) name = [name[1],name[1],name[0],name[0],name[1],name[1]];
  // 0 is top, 1 is bottom, 2 is sides
  if (name.length === 3) name = [name[2],name[2],name[0],name[1],name[2],name[2]];
  // 0 is top, 1 is bottom, 2 is front/back, 3 is left/right
  if (name.length === 4) name = [name[2],name[2],name[0],name[1],name[3],name[3]];
  return name;
};

Texture.prototype._options = function(opts) {
  opts = opts || {};
  opts.materialType = opts.materialType || this.materialType;
  opts.materialParams = defaults(opts.materialParams || {}, this._materialDefaults, this.materialParams);
  opts.applyTextureParams = opts.applyTextureParams || this.applyTextureParams;
  return opts;
};

Texture.prototype.paint = function(geom) {
  var self = this;
  geom.faces.forEach(function(face, i) {
    var c = face.vertexColors[0];
    var index = self.materialIndex[Math.floor(c.b*255 + c.g*255*255 + c.r*255*255*255) - 1][0];
    // BACK, FRONT, TOP, BOTTOM, LEFT, RIGHT
    if      (face.normal.z === 1)  index += 1;
    else if (face.normal.y === 1)  index += 2;
    else if (face.normal.y === -1) index += 3;
    else if (face.normal.x === -1) index += 4;
    else if (face.normal.x === 1)  index += 5;
    face.materialIndex = index;
  });
};

Texture.prototype.sprite = function(name, w, h, cb) {
  var self = this;
  if (typeof w === 'function') { cb = w; w = null; }
  if (typeof h === 'function') { cb = h; h = null; }
  w = w || 16; h = h || w;
  var img = new Image();
  img.src = self.texturePath + ext(name);
  img.onerror = cb;
  img.onload = function() {
    var textures = [];
    for (var x = 0; x < img.width; x += w) {
      for (var y = 0; y < img.height; y += h) {
        var canvas = document.createElement('canvas');
        canvas.width = w; canvas.height = h;
        var ctx = canvas.getContext('2d');
        ctx.drawImage(img, x, y, w, h, 0, 0, w, h);
        var tex = new self.THREE.Texture(canvas);
        tex.name = name + '_' + x + '_' + y;
        tex.needsUpdate = true;
        textures.push(tex);
      }
    }
    cb(null, textures);
  };
  return self;
};

function ext(name) {
  return (String(name).indexOf('.') !== -1) ? name : name + '.png';
}

// copied from https://github.com/joyent/node/blob/master/lib/util.js#L433
function isArray(ar) {
  return Array.isArray(ar) || (typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]');
}

function hasSubArray(ar) {
  var has = false;
  ar.forEach(function(a) { if (isArray(a)) { has = true; return false; } });
  return has;
}

function defaults(obj) {
  [].slice.call(arguments, 1).forEach(function(from) {
    if (from) for (var k in from) if (obj[k] == null) obj[k] = from[k];
  });
  return obj;
}
<commit_msg>Use opaque to detect whether texture should be transparent<commit_after>var transparent = require('opaque').transparent;

function Texture(opts) {
  var self = this;
  if (!(this instanceof Texture)) return new Texture(opts || {});
  this.THREE              = opts.THREE          || require('three');
  this.materials          = [];
  this.texturePath        = opts.texturePath    || '/textures/';
  this.materialParams     = opts.materialParams || {};
  this.materialType       = opts.materialType   || this.THREE.MeshLambertMaterial;
  this.materialIndex      = [];
  this._materialDefaults  = { ambient: 0xbbbbbb };
  this.applyTextureParams = opts.applyTextureParams || function(map) {
    map.magFilter = self.THREE.NearestFilter;
    map.minFilter = self.THREE.LinearMipMapLinearFilter;
    map.wrapT     = self.THREE.RepeatWrapping;
    map.wrapS     = self.THREE.RepeatWrapping;
  }
}
module.exports = Texture;

Texture.prototype.load = function(names, opts) {
  var self = this;
  opts = self._options(opts);
  if (!isArray(names)) names = [names];
  if (!hasSubArray(names)) names = [names];
  return [].concat.apply([], names.map(function(name) {
    name = self._expandName(name);
    self.materialIndex.push([self.materials.length, self.materials.length + name.length]);
    return name.map(function(n) {
      if (n instanceof self.THREE.Texture) {
        var map = n;
        n = n.name;
      } else if (typeof n === 'string') {
        var map = self.THREE.ImageUtils.loadTexture(self.texturePath + ext(n));
      } else {
        var map = new self.THREE.Texture(n);
        n = map.name;
      }
      self.applyTextureParams.call(self, map);
      var mat = new opts.materialType(opts.materialParams);
      mat.map = map;
      mat.name = n;
      self._isTransparent(mat);
      self.materials.push(mat);
      return mat;
    });
  }));
};

Texture.prototype.get = function(index) {
  if (index == null) return this.materials;
  if (typeof index === 'number') {
    index = this.materialIndex[index];
  } else {
    for (var i = 0; i < this.materials.length; i++) {
      if (index === this.materials[i].name) {
        index = i;
        break;
      }
    }
    for (var i = 0; i < this.materialIndex.length; i++) {
      var idx = this.materialIndex[i];
      if (index >= idx[0] && index < idx[1]) {
        index = idx;
        break;
      }
    }
  }
  return this.materials.slice(index[0], index[1]);
};

Texture.prototype._expandName = function(name) {
  if (name.top) return [name.back, name.front, name.top, name.bottom, name.left, name.right];
  if (!isArray(name)) name = [name];
  // load the 0 texture to all
  if (name.length === 1) name = [name[0],name[0],name[0],name[0],name[0],name[0]];
  // 0 is top/bottom, 1 is sides
  if (name.length === 2) name = [name[1],name[1],name[0],name[0],name[1],name[1]];
  // 0 is top, 1 is bottom, 2 is sides
  if (name.length === 3) name = [name[2],name[2],name[0],name[1],name[2],name[2]];
  // 0 is top, 1 is bottom, 2 is front/back, 3 is left/right
  if (name.length === 4) name = [name[2],name[2],name[0],name[1],name[3],name[3]];
  return name;
};

Texture.prototype._options = function(opts) {
  opts = opts || {};
  opts.materialType = opts.materialType || this.materialType;
  opts.materialParams = defaults(opts.materialParams || {}, this._materialDefaults, this.materialParams);
  opts.applyTextureParams = opts.applyTextureParams || this.applyTextureParams;
  return opts;
};

Texture.prototype.paint = function(geom) {
  var self = this;
  geom.faces.forEach(function(face, i) {
    var c = face.vertexColors[0];
    var index = self.materialIndex[Math.floor(c.b*255 + c.g*255*255 + c.r*255*255*255) - 1][0];
    // BACK, FRONT, TOP, BOTTOM, LEFT, RIGHT
    if      (face.normal.z === 1)  index += 1;
    else if (face.normal.y === 1)  index += 2;
    else if (face.normal.y === -1) index += 3;
    else if (face.normal.x === -1) index += 4;
    else if (face.normal.x === 1)  index += 5;
    face.materialIndex = index;
  });
};

Texture.prototype.sprite = function(name, w, h, cb) {
  var self = this;
  if (typeof w === 'function') { cb = w; w = null; }
  if (typeof h === 'function') { cb = h; h = null; }
  w = w || 16; h = h || w;
  var img = new Image();
  img.src = self.texturePath + ext(name);
  img.onerror = cb;
  img.onload = function() {
    var textures = [];
    for (var x = 0; x < img.width; x += w) {
      for (var y = 0; y < img.height; y += h) {
        var canvas = document.createElement('canvas');
        canvas.width = w; canvas.height = h;
        var ctx = canvas.getContext('2d');
        ctx.drawImage(img, x, y, w, h, 0, 0, w, h);
        var tex = new self.THREE.Texture(canvas);
        tex.name = name + '_' + x + '_' + y;
        tex.needsUpdate = true;
        textures.push(tex);
      }
    }
    cb(null, textures);
  };
  return self;
};

Texture.prototype._isTransparent = function(material) {
  if (!material.map) return;
  if (!material.map.image) return;
  if (material.map.image.nodeName.toLowerCase() === 'img') {
    material.map.image.onload = function() {
      if (transparent(this)) {
        material.transparent = true;
        material.needsUpdate = true;
      }
    };
  } else {
    if (transparent(material.map.image)) {
      material.transparent = true;
      material.needsUpdate = true;
    }
  }
};

function ext(name) {
  return (String(name).indexOf('.') !== -1) ? name : name + '.png';
}

// copied from https://github.com/joyent/node/blob/master/lib/util.js#L433
function isArray(ar) {
  return Array.isArray(ar) || (typeof ar === 'object' && Object.prototype.toString.call(ar) === '[object Array]');
}

function hasSubArray(ar) {
  var has = false;
  ar.forEach(function(a) { if (isArray(a)) { has = true; return false; } });
  return has;
}

function defaults(obj) {
  [].slice.call(arguments, 1).forEach(function(from) {
    if (from) for (var k in from) if (obj[k] == null) obj[k] = from[k];
  });
  return obj;
}
<|endoftext|>


<commit_before>var Feedly = require('feedly');
var util = require('util');
var request = require('request');
var read = require('node-read');
var open = require('open');
var applescript = require('applescript');
var fs    = require('fs'),
     nconf = require('nconf');
var clipboard = require("copy-paste")

nconf.file('./config.json');
nconf.use('file', { file: './config.json' });

var f = new Feedly({
  client_id: nconf.get('client_id'),
  // 'REPLACE with your client_secret or use your developer-token and set developer=true (NOTE: developer mode only works on cloud.feedly.com not in sandbox)'
  client_secret: nconf.get('client_secret'),
  developer: nconf.get('developer'),
  port: 8080
});

var blessed = require('blessed')
, fs = require('fs');

var screen = blessed.screen({
  smartCSR: true,
  dockBorders: true
});

var listCategories = blessed.list({
  parent: screen,
  label: ' {bold}{cyan-fg}Categories',
  tags: true,
  draggable: true,
  top: 0,
  left: 0,
  width: '20%',
  height: '50%',
  keys: true,
  vi: true,
  mouse: true,
  border: 'line',
  scrollbar: {
    ch: ' ',
    track: {
      bg: 'cyan'
    },
    style: {
      inverse: true
    }
  },
  style: {
    item: {
      hover: {
        bg: 'blue'
      }
    },
    selected: {
      bg: 'blue',
      bold: true
    }
  }
});

var listEntries = blessed.list({
  parent: screen,
  label: ' {bold}{cyan-fg}Feed Entries',
  tags: true,
  draggable: true,
  top: 0,
  right: 0,
  width: '80%',
  height: '50%',
  keys: true,
  vi: true,
  mouse: true,
  border: 'line',
  scrollbar: {
    ch: ' ',
    track: {
      bg: 'cyan'
    },
    style: {
      inverse: true
    }
  },
  style: {
    item: {
      hover: {
        bg: 'blue'
      }
    },
    selected: {
      bg: 'blue',
      bold: true
    }
  }
});

function setlistEntrieslabel(string) {
  listEntries.setLabel({text:'{bold}{cyan-fg}Feed Entries (' + string +')',side:'center'});
}

function setContentLabel(string) {
  content.setLabel({text:'{bold}{cyan-fg}Feed Name: ' + string,side:'center'});
}

var categoriesMap = null;

f.categories().then(function(results) {
  categoriesMap = new Object();
  var userId = results[0].id.substring(0, results[0].id.lastIndexOf('/'));
  results.unshift({ id: userId.replace('category','tag') + '/global.saved', label: 'Saved'});
  results.unshift({ id: userId + '/global.all', label: 'All'});
  results.forEach(function(obj) {
    categoriesMap[obj.label] = obj;
    });
  listCategories.setItems(Object.keys(categoriesMap));
  screen.render();
},
function (error) {
	updateStatus(error);
});

var content = blessed.box({
  parent: screen,
  label: ' {bold}{cyan-fg}Feed Source:',
  tags: true,
  draggable: true,
  bottom: 1,
  right: 0,
  width: '100%',
  height: '50%',
  keys: true,
  vi: true,
  mouse: true,
  scrollable: true,
  border: 'line',
  scrollbar: {
    ch: ' ',
    track: {
      bg: 'cyan'
    },
    style: {
      inverse: true
    }
  },
  style: {
    item: {
      hover: {
        bg: 'blue'
      }
    }
  },
});

var status = blessed.box({
  parent: screen,
  bottom: 0,
  right: 0,
  height: 1,
  width: 'shrink',
  style: {
    bg: 'blue'
  },
  content: 'Feedly Terminal'
});

var loader = blessed.loading({
  parent: screen,
  top: 'center',
  left: 'center',
  height: 5,
  align: 'center',
  width: '50%',
  tags: true,
  hidden: true,
  border: 'line'
});

var msg = blessed.message({
  parent: screen,
  top: 'center',
  left: 'center',
  height: 'shrink',
  width: '50%',
  align: 'center',
  tags: true,
  hidden: true,
  border: 'line'
});

var prompt = blessed.prompt({
  parent: screen,
  top: 'center',
  left: 'center',
  height: 'shrink',
  width: 'shrink',
  keys: true,
  vi: true,
  mouse: true,
  tags: true,
  border: 'line',
  hidden: true
});

var entriesMap = null;

listCategories.on('select', function(el, selected) {
  if (listCategories._.rendering) return;
  if(el == null)
  {
    updateStatus('Error loading Categories');
    return;
  }

  var name = el.getText();
  var id = categoriesMap[name].id;
  status.setContent(id);

  listCategories._.rendering = true;
  loader.load('Loading...');

  f.contents(id, nconf.get('count'), nconf.get('ranked'), nconf.get('unreadOnly')).then(function(result) {
    loader.stop();
    listCategories._.rendering = false;

    entriesMap = new Object();
    result.items.forEach(function(obj) {
      entriesMap[obj.title] = obj;
      });

    listEntries.setItems(Object.keys(entriesMap));
    setlistEntrieslabel(Object.keys(entriesMap).length);
    listEntries.focus();
    screen.render();
  },
  function (error) {
    updateStatus(error);
    loader.stop();
    listCategories._.rendering = false;
    screen.render();
  });
});

listEntries.on('select', function(el, selected) {
  if (listEntries._.rendering) return;

  var name = el.getText();

  if(entriesMap[name] == null || entriesMap[name].alternate == null)
  {
        updateStatus(name + ' not found in entriesMap');
        return;
  }

  var url = entriesMap[name].alternate[0].href;

  listEntries._.rendering = true;
  loader.load('Loading...');

  read(url, function(err, article, res) {
    listEntries._.rendering = false;
    loader.stop();
    if(article == null) {
        updateStatus('Error fetching article');
        return;
    }

    var formattedContent = beautifyString(article.title + ' ' + article.content);
    try {
      content.setContent(formattedContent);
      content.focus();
      screen.render();
    }
    catch(ex) {
      content.setContent('');
      updateStatus(ex);
      screen.render();
    }
    finally {
    }
  });

});

function beautifyString(string)
{
  var removeHtml = string//.split("/
]*>/g").join("\n\n\n").split("
").join("")
    .replace(/<(?:.|\n)*?>/gm, '');

  for(i=300; i < removeHtml.length; i+=300)
  {
    var index = removeHtml.indexOf('.',i);
    if(index == -1)
        break;

    i = index + 1;
    removeHtml = [removeHtml.slice(0, i), '\n\n\n', removeHtml.slice(i)].join('');
  }

  return removeHtml;
}

listEntries.key('j', function(ch, key) {
  listEntriesScrollDown(ch, key);
});

listEntries.key('down', function(ch, key) {
  listEntriesScrollDown(ch, key);
});

listEntries.key('k', function(ch, key) {
  listEntriesScrollDown(ch, key);
});

listEntries.key('up', function(ch, key) {
  listEntriesScrollDown(ch, key);
});

function listEntriesScrollDown(ch, key) {
  if(listEntries.focused)
  {
    var index = listEntries.getScroll();
    switch (key.name) {
      case 'j':
      case 'down':
          if(index >= Object.keys(entriesMap).length - 1)
            return;
          if(nconf.get('markAsRead'))
            markAsRead(index);

          listEntriesScroll(index + 1);
        break;
      case 'k':
      case 'up':
          if(index == 0)
            return;
          listEntriesScroll(index - 1);
          break;
      default:
    }
  }
}

function markAsRead(index) {
  var item = listEntries.getItem(index);
  var name = item.getText();
  item = entriesMap[name];
  f.markEntryRead(item.id).then(function(results){
      updateStatus('success marked as read');
  },
  function (error) {
      updateStatus(error);
  });
}

function markAsSaved(index) {
  var item = listEntries.getItem(index);
  var name = item.getText();
  item = entriesMap[name];
  f.markEntrySaved(item.id).then(function(results){
      updateStatus('Article has been Saved');
  },
  function (error) {
      updateStatus(error);
  });
}

function markAsUnsaved(index) {
  var item = listEntries.getItem(index);
  var name = item.getText();
  item = entriesMap[name];
  f.markEntryUnsaved(item.id).then(function(results){
      updateStatus('Article has been unsaved');
  },
  function (error) {
      updateStatus(error);
  });
}

function listEntriesScroll(index) {
  var item = listEntries.getItem(index);
  var text = "";
  var count = Object.keys(entriesMap).length;
  if(item == null)
  {
    updateStatus(index + " not found. Length:" + count);
  }
  else
  {
    var name = item.getText();
    if(entriesMap[name] != null && entriesMap[name].summary != null) {
      text = entriesMap[name].summary.content;
      setContentLabel(entriesMap[name].origin.title);
    }
    else {
      updateStatus(name + " not found in entriesmap");
    }
  }
  setlistEntrieslabel(index + '/' + count);
  text = beautifyString(text);
  content.setContent(text);
  try {
    screen.render();
  } catch (e) {
    updateStatus(e);
  } finally {
  }
}

function updateStatus(content) {
  try {
    status.setContent(JSON.stringify(content));
  }catch(error) {
  }
  finally {
    screen.render();
  }
}

listCategories.items.forEach(function(item, i) {
  var text = item.label;
  item.setHover(categoriesMap[text]);
});

listEntries.items.forEach(function(item, i) {
  var text = item.id;
  item.setHover(entriesMap[text]);
});

listCategories.focus();

screen.key('i', function() {
  content.resetScroll();
  listCategories.focus();
});

screen.key('o', function() {
  content.resetScroll();
  listEntries.focus();
});

screen.key('p', function() {
  listCategories.focus();
});

screen.key('q', function() {
  return process.exit(0);
});

screen.key('s', function() {
  var index = listEntries.getScroll();
  markAsSaved(index);
});

screen.key('u', function() {
  var index = listEntries.getScroll();
  markAsUnsaved(index);
});

screen.key('v', function() {
  var index = listEntries.getScroll();
  var text = listEntries.getItem(index).getText();
  var entry = entriesMap[text];
  if(entry.alternate != null)
  {
    open(entry.alternate[0].href);
  }
});

screen.key('1', function() {
  var index = listEntries.getScroll();
  var text = listEntries.getItem(index).getText();
  var entry = entriesMap[text];
  if(entry.alternate != null)
  {
    clipboard.copy(entry.alternate[0].href);
    applescript.execString(nconf.get('applescript1').replace('$href$', entry.alternate[0].href), function(err, rtn) {
      if (err) {
        console.log(err);
      }
  });
  }
});

screen.key('2', function() {
  var index = listEntries.getScroll();
  var text = listEntries.getItem(index).getText();
  var entry = entriesMap[text];
  if(entry.alternate != null)
  {
    clipboard.copy(entry.alternate[0].href);
    applescript.execString(nconf.get('applescript2').replace('$href$', entry.alternate[0].href), function(err, rtn) {
      if (err) {
        console.log(err);
      }
  });
  }
});

screen.key('3', function() {
  var index = listEntries.getScroll();
  var text = listEntries.getItem(index).getText();
  var entry = entriesMap[text];
  if(entry.alternate != null)
  {
    clipboard.copy(entry.alternate[0].href);
    applescript.execString(nconf.get('applescript3').replace('$href$', entry.alternate[0].href), function(err, rtn) {
      if (err) {
        console.log(err);
      }
  });
  }
});

screen.key('m', function() {
  var index = listEntries.getScroll();
  var values = Object.keys(entriesMap);
  var result = [];
  var resultKeys = [];
  var count = 0;
  for(i = 0; i<= index; i++)
  {
    var entry = entriesMap[values[i]];
    if(entry.unread)
    {
      result.push(entry.id);
      resultKeys.push(values[i]);
      count++;
    }

    if(result.length >= 75 || i >= index)
    {
      f.markEntryRead(result).then(function(results){
        updateStatus('marked ' + count + '/' + index + ' items as read');
          resultKeys.forEach(function(obj) {
            entriesMap[obj].unread = false;
          });
      },
      function (error) {
          updateStatus(error);
      });
      result = [];
      resultKeys = [];
    }
  }
});

// https://github.com/chjj/blessed/issues/127
// https://github.com/Mithgol/node-singlebyte

function cp437ToUtf8(buf, callback) {
  try {
    return callback(null, singlebyte.bufToStr(buf, 'cp437'));
  } catch (e) {
    return callback(e);
  }
}
<commit_msg>bug fix for wrong scroll index<commit_after>var Feedly = require('feedly');
var util = require('util');
var request = require('request');
var read = require('node-read');
var open = require('open');
var applescript = require('applescript');
var fs    = require('fs'),
     nconf = require('nconf');
var clipboard = require("copy-paste")

nconf.file('./config.json');
nconf.use('file', { file: './config.json' });

var f = new Feedly({
  client_id: nconf.get('client_id'),
  // 'REPLACE with your client_secret or use your developer-token and set developer=true (NOTE: developer mode only works on cloud.feedly.com not in sandbox)'
  client_secret: nconf.get('client_secret'),
  developer: nconf.get('developer'),
  port: 8080
});

var blessed = require('blessed')
, fs = require('fs');

var screen = blessed.screen({
  smartCSR: true,
  dockBorders: true
});

var listCategories = blessed.list({
  parent: screen,
  label: ' {bold}{cyan-fg}Categories',
  tags: true,
  draggable: true,
  top: 0,
  left: 0,
  width: '20%',
  height: '50%',
  keys: true,
  vi: true,
  mouse: true,
  border: 'line',
  scrollbar: {
    ch: ' ',
    track: {
      bg: 'cyan'
    },
    style: {
      inverse: true
    }
  },
  style: {
    item: {
      hover: {
        bg: 'blue'
      }
    },
    selected: {
      bg: 'blue',
      bold: true
    }
  }
});

var listEntries = blessed.list({
  parent: screen,
  label: ' {bold}{cyan-fg}Feed Entries',
  tags: true,
  draggable: true,
  top: 0,
  right: 0,
  width: '80%',
  height: '50%',
  keys: true,
  vi: true,
  mouse: true,
  border: 'line',
  scrollbar: {
    ch: ' ',
    track: {
      bg: 'cyan'
    },
    style: {
      inverse: true
    }
  },
  style: {
    item: {
      hover: {
        bg: 'blue'
      }
    },
    selected: {
      bg: 'blue',
      bold: true
    }
  }
});

function setlistEntrieslabel(string) {
  listEntries.setLabel({text:'{bold}{cyan-fg}Feed Entries (' + string +')',side:'center'});
}

function setContentLabel(string) {
  content.setLabel({text:'{bold}{cyan-fg}Feed Name: ' + string,side:'center'});
}

var categoriesMap = null;

f.categories().then(function(results) {
  categoriesMap = new Object();
  var userId = results[0].id.substring(0, results[0].id.lastIndexOf('/'));
  results.unshift({ id: userId.replace('category','tag') + '/global.saved', label: 'Saved'});
  results.unshift({ id: userId + '/global.all', label: 'All'});
  results.forEach(function(obj) {
    categoriesMap[obj.label] = obj;
    });
  listCategories.setItems(Object.keys(categoriesMap));
  screen.render();
},
function (error) {
	updateStatus(error);
});

var content = blessed.box({
  parent: screen,
  label: ' {bold}{cyan-fg}Feed Source:',
  tags: true,
  draggable: true,
  bottom: 1,
  right: 0,
  width: '100%',
  height: '50%',
  keys: true,
  vi: true,
  mouse: true,
  scrollable: true,
  border: 'line',
  scrollbar: {
    ch: ' ',
    track: {
      bg: 'cyan'
    },
    style: {
      inverse: true
    }
  },
  style: {
    item: {
      hover: {
        bg: 'blue'
      }
    }
  },
});

var status = blessed.box({
  parent: screen,
  bottom: 0,
  right: 0,
  height: 1,
  width: 'shrink',
  style: {
    bg: 'blue'
  },
  content: 'Feedly Terminal'
});

var loader = blessed.loading({
  parent: screen,
  top: 'center',
  left: 'center',
  height: 5,
  align: 'center',
  width: '50%',
  tags: true,
  hidden: true,
  border: 'line'
});

var msg = blessed.message({
  parent: screen,
  top: 'center',
  left: 'center',
  height: 'shrink',
  width: '50%',
  align: 'center',
  tags: true,
  hidden: true,
  border: 'line'
});

var prompt = blessed.prompt({
  parent: screen,
  top: 'center',
  left: 'center',
  height: 'shrink',
  width: 'shrink',
  keys: true,
  vi: true,
  mouse: true,
  tags: true,
  border: 'line',
  hidden: true
});

var entriesMap = null;

listCategories.on('select', function(el, selected) {
  if (listCategories._.rendering) return;
  if(el == null)
  {
    updateStatus('Error loading Categories');
    return;
  }

  var name = el.getText();
  var id = categoriesMap[name].id;
  status.setContent(id);

  listCategories._.rendering = true;
  loader.load('Loading...');

  f.contents(id, nconf.get('count'), nconf.get('ranked'), nconf.get('unreadOnly')).then(function(result) {
    loader.stop();
    listCategories._.rendering = false;

    entriesMap = new Object();
    result.items.forEach(function(obj) {
      entriesMap[obj.title] = obj;
      });

    listEntries.setItems(Object.keys(entriesMap));
    setlistEntrieslabel(Object.keys(entriesMap).length);
    listEntries.focus();
    listEntriesScroll(0);
    screen.render();
  },
  function (error) {
    updateStatus(error);
    loader.stop();
    listCategories._.rendering = false;
    screen.render();
  });
});

listEntries.on('select', function(el, selected) {
  if (listEntries._.rendering) return;

  var name = el.getText();

  if(entriesMap[name] == null || entriesMap[name].alternate == null)
  {
        updateStatus(name + ' not found in entriesMap');
        return;
  }

  var url = entriesMap[name].alternate[0].href;

  listEntries._.rendering = true;
  loader.load('Loading...');

  read(url, function(err, article, res) {
    listEntries._.rendering = false;
    loader.stop();
    if(article == null) {
        updateStatus('Error fetching article');
        return;
    }

    var formattedContent = beautifyString(article.title + ' ' + article.content);
    try {
      content.setContent(formattedContent);
      content.focus();
      screen.render();
    }
    catch(ex) {
      content.setContent('');
      updateStatus(ex);
      screen.render();
    }
    finally {
    }
  });

});

function beautifyString(string)
{
  var removeHtml = string//.split("/
]*>/g").join("\n\n\n").split("
").join("")
    .replace(/<(?:.|\n)*?>/gm, '');

  for(i=300; i < removeHtml.length; i+=300)
  {
    var index = removeHtml.indexOf('.',i);
    if(index == -1)
        break;

    i = index + 1;
    removeHtml = [removeHtml.slice(0, i), '\n\n\n', removeHtml.slice(i)].join('');
  }

  return removeHtml;
}

listEntries.key('j', function(ch, key) {
  listEntriesScrollDown(ch, key);
});

listEntries.key('down', function(ch, key) {
  listEntriesScrollDown(ch, key);
});

listEntries.key('k', function(ch, key) {
  listEntriesScrollDown(ch, key);
});

listEntries.key('up', function(ch, key) {
  listEntriesScrollDown(ch, key);
});

function listEntriesScrollDown(ch, key) {
  if(listEntries.focused)
  {
    var index = listEntries.getScroll();
    switch (key.name) {
      case 'j':
      case 'down':
          if(index >= Object.keys(entriesMap).length - 1)
            return;
          if(nconf.get('markAsRead'))
            markAsRead(index);

          listEntriesScroll(index);
        break;
      case 'k':
      case 'up':
          if(index < 0)
            return;
          listEntriesScroll(index);
          break;
      default:
    }
  }
}

function markAsRead(index) {
  var item = listEntries.getItem(index);
  var name = item.getText();
  item = entriesMap[name];
  f.markEntryRead(item.id).then(function(results){
      updateStatus('success marked as read');
  },
  function (error) {
      updateStatus(error);
  });
}

function markAsSaved(index) {
  var item = listEntries.getItem(index);
  var name = item.getText();
  item = entriesMap[name];
  f.markEntrySaved(item.id).then(function(results){
      updateStatus('Article has been Saved');
  },
  function (error) {
      updateStatus(error);
  });
}

function markAsUnsaved(index) {
  var item = listEntries.getItem(index);
  var name = item.getText();
  item = entriesMap[name];
  f.markEntryUnsaved(item.id).then(function(results){
      updateStatus('Article has been unsaved');
  },
  function (error) {
      updateStatus(error);
  });
}

function listEntriesScroll(index) {
  var item = listEntries.getItem(index);
  var text = "";
  var count = Object.keys(entriesMap).length;
  if(item == null || item === 'undefined')
  {
    updateStatus(index + " not found. Length:" + count);
  }
  else
  {
    var name = item.getText();
    if(entriesMap[name] != null && entriesMap[name].summary != null) {
      text = entriesMap[name].summary.content;
      setContentLabel(entriesMap[name].origin.title);
    }
    else {
      updateStatus(name + " not found in entriesmap");
    }
  }
  setlistEntrieslabel(index + '/' + count);
  text = beautifyString(text);
  content.setContent(text);
  try {
    screen.render();
  } catch (e) {
    updateStatus(e);
  } finally {
  }
}

function updateStatus(content) {
  try {
    status.setContent(JSON.stringify(content));
  }catch(error) {
  }
  finally {
    screen.render();
  }
}

listCategories.items.forEach(function(item, i) {
  var text = item.label;
  item.setHover(categoriesMap[text]);
});

listEntries.items.forEach(function(item, i) {
  var text = item.id;
  item.setHover(entriesMap[text]);
});

listCategories.focus();

screen.key('i', function() {
  content.resetScroll();
  listCategories.focus();
});

screen.key('o', function() {
  content.resetScroll();
  listEntries.focus();
});

screen.key('p', function() {
  listCategories.focus();
});

screen.key('q', function() {
  return process.exit(0);
});

screen.key('s', function() {
  var index = listEntries.getScroll();
  markAsSaved(index);
});

screen.key('u', function() {
  var index = listEntries.getScroll();
  markAsUnsaved(index);
});

screen.key('v', function() {
  var index = listEntries.getScroll();
  var text = listEntries.getItem(index).getText();
  var entry = entriesMap[text];
  if(entry.alternate != null)
  {
    open(entry.alternate[0].href);
  }
});

screen.key('1', function() {
  var index = listEntries.getScroll();
  var text = listEntries.getItem(index).getText();
  var entry = entriesMap[text];
  if(entry.alternate != null)
  {
    clipboard.copy(entry.alternate[0].href);
    applescript.execString(nconf.get('applescript1').replace('$href$', entry.alternate[0].href), function(err, rtn) {
      if (err) {
        console.log(err);
      }
  });
  }
});

screen.key('2', function() {
  var index = listEntries.getScroll();
  var text = listEntries.getItem(index).getText();
  var entry = entriesMap[text];
  if(entry.alternate != null)
  {
    clipboard.copy(entry.alternate[0].href);
    applescript.execString(nconf.get('applescript2').replace('$href$', entry.alternate[0].href), function(err, rtn) {
      if (err) {
        console.log(err);
      }
  });
  }
});

screen.key('3', function() {
  var index = listEntries.getScroll();
  var text = listEntries.getItem(index).getText();
  var entry = entriesMap[text];
  if(entry.alternate != null)
  {
    clipboard.copy(entry.alternate[0].href);
    applescript.execString(nconf.get('applescript3').replace('$href$', entry.alternate[0].href), function(err, rtn) {
      if (err) {
        console.log(err);
      }
  });
  }
});

screen.key('m', function() {
  var index = listEntries.getScroll();
  var values = Object.keys(entriesMap);
  var result = [];
  var resultKeys = [];
  var count = 0;
  for(i = 0; i<= index; i++)
  {
    var entry = entriesMap[values[i]];
    if(entry.unread)
    {
      result.push(entry.id);
      resultKeys.push(values[i]);
      count++;
    }

    if(result.length >= 75 || i >= index)
    {
      f.markEntryRead(result).then(function(results){
        updateStatus('marked ' + count + '/' + index + ' items as read');
          resultKeys.forEach(function(obj) {
            entriesMap[obj].unread = false;
          });
      },
      function (error) {
          updateStatus(error);
      });
      result = [];
      resultKeys = [];
    }
  }
});

// https://github.com/chjj/blessed/issues/127
// https://github.com/Mithgol/node-singlebyte

function cp437ToUtf8(buf, callback) {
  try {
    return callback(null, singlebyte.bufToStr(buf, 'cp437'));
  } catch (e) {
    return callback(e);
  }
}
<|endoftext|>


<commit_before>var Pap = (function () {
    'use strict';

    var pap = {};

    function powerofTwo (num) {
        return !!( (num & (num - 1)) == 0 );
    }

    pap.smear = function (imageData, radius) {

        if(!powerofTwo(radius)) {
            console.warn('pixel size should be power of two for best results');
        }

        var newData = document.createElement('canvas').getContext('2d').getImageData(0,0,imageData.width, imageData.height);

        newData.data.set(imageData.data);

        var columns = imageData.width;
        var rows = imageData.height;
        var chunkWidth = radius; //112;
        var chunks = [];

        var currentRow = 0;
        var currentColumn = 0;

        var currentPixel = 0;
        var pixelRow = 0;

        var totalPixels = imageData.data.length / 4;
        var pixels = [];

        for(var i = 0; i < imageData.data.length; i += 4) {
            pixels.push({
                r : imageData.data[i],
                g : imageData.data[i + 1],
                b : imageData.data[i + 2],
                a : imageData.data[i + 3]
            });
        }

        var c = 0;
        var totalChunkColumns = imageData.width / chunkWidth;
        var totalChunkRows = imageData.height / chunkWidth;

        for(var k = 0; k < imageData.data.length; k += 4) {
            c += 1;

            var pixel = {
                r : imageData.data[k],
                g : imageData.data[k + 1],
                b : imageData.data[k + 2],
                a : imageData.data[k + 3]
            };

            if((c % chunkWidth) === 0) {

                currentColumn += 1;

                if(currentColumn === totalChunkColumns) {

                    currentColumn = 0;
                    pixelRow += 1;

                    if(pixelRow % chunkWidth === 0) {
                        currentRow += 1;
                        pixelRow = 0;
                    }

                }
            }

            if(chunks[currentRow] === undefined) {
                chunks[currentRow] = [];
            }

            if(chunks[currentRow][currentColumn] === undefined) {
                chunks[currentRow][currentColumn] = [];
            }

            chunks[currentRow][currentColumn].push(pixel);

        }

        for(var j = 0; j < chunks.length; j += 1) {

            var roe = chunks[j];

            for(var h = 0; h < roe.length; h += 1) {
                var callem = roe[h];
                var totalRed = 0;
                var totalGreen = 0;
                var totalBlue = 0;

                var averageRed,
                averageGreen,
                averageBlue;

                for(var q = 0; q < callem.length; q += 1) {
                    totalRed += callem[q].r;
                    totalGreen += callem[q].g;
                    totalBlue += callem[q].b;
                }

                averageRed = totalRed / callem.length;
                averageGreen = totalGreen / callem.length;
                averageBlue = totalBlue / callem.length;

                for(var q = 0; q < callem.length; q += 1) {
                    callem[q].r = averageRed;
                    callem[q].g = averageGreen;
                    callem[q].b = averageBlue;
                }

            }
        }

        currentRow = 0;
        currentColumn = 0;
        c = 0;

        for(var k = 0; k < imageData.data.length; k += 4) {

            c += 1;

            if((c % chunkWidth) === 0) {

                currentColumn += 1;

                if(currentColumn === totalChunkColumns) {

                    currentColumn = 0;
                    pixelRow += 1;

                    if(pixelRow % chunkWidth === 0) {
                        currentRow += 1;
                        pixelRow = 0;
                    }

                }
            }

            var blur = chunks[currentRow][currentColumn][0];

            newData.data[k] = blur.r;
            newData.data[k + 1] = blur.g;
            newData.data[k + 2] = blur.b;

        }

        return newData;

    };

    return pap;

}());

module.exports = Pap;
<commit_msg>clean up and lint<commit_after>var Pap = (function () {
    'use strict';

    var pap = {};

    function powerofTwo (num) {
        return !!( (num & (num - 1)) === 0 );
    }

    function pushPixelsIntoChunks (pixelData, chunkSize, totalChunkColumns) {

        var chunks = [],
        currentColumn = 0,
        currentRow = 0,
        pixelRow = 0,
        pixel = {},
        c = 0,
        i = 0;

        for(i = 0; i < pixelData.length; i += 4) {
            c += 1;

            pixel = {
                r : pixelData[i],
                g : pixelData[i + 1],
                b : pixelData[i + 2],
                a : pixelData[i + 3]
            };

            if((c % chunkSize) === 0) {

                currentColumn += 1;

                if(currentColumn === totalChunkColumns) {

                    currentColumn = 0;
                    pixelRow += 1;

                    if(pixelRow % chunkSize === 0) {
                        currentRow += 1;
                        pixelRow = 0;
                    }

                }
            }

            if(chunks[currentRow] === undefined) {
                chunks[currentRow] = [];
            }

            if(chunks[currentRow][currentColumn] === undefined) {
                chunks[currentRow][currentColumn] = [];
            }

            chunks[currentRow][currentColumn].push(pixel);

        }

        return chunks;
    }

    function averageOutChunkColors (chunks) {

        var i = 0,
        q = 0,
        h = 0,
        roe,
        callem,
        totalRed = 0,
        totalGreen = 0,
        totalBlue = 0,
        averageRed = 0,
        averageGreen = 0,
        averageBlue = 0;

        for(i = 0; i < chunks.length; i += 1) {

            roe = chunks[i];

            for(h = 0; h < roe.length; h += 1) {

                callem = roe[h];
                totalRed = 0;
                totalGreen = 0;
                totalBlue = 0;

                averageRed = 0;
                averageGreen = 0;
                averageBlue = 0;

                for(q = 0; q < callem.length; q += 1) {

                    totalRed += callem[q].r;
                    totalGreen += callem[q].g;
                    totalBlue += callem[q].b;

                }

                averageRed = totalRed / callem.length;
                averageGreen = totalGreen / callem.length;
                averageBlue = totalBlue / callem.length;

                for(q = 0; q < callem.length; q += 1) {
                    callem[q].r = averageRed;
                    callem[q].g = averageGreen;
                    callem[q].b = averageBlue;
                }

            }
        }

        return chunks;
    }

    function pushChunksIntoImageData (newData, chunks, chunkSize, totalChunkColumns) {

        var i = 0,
        c = 0,
        currentRow = 0,
        currentColumn = 0,
        pixelRow = 0,
        blur;

        for(i = 0; i < newData.data.length; i += 4) {

            c += 1;

            if((c % chunkSize) === 0) {

                currentColumn += 1;

                if(currentColumn === totalChunkColumns) {

                    currentColumn = 0;
                    pixelRow += 1;

                    if(pixelRow % chunkSize === 0) {
                        currentRow += 1;
                        pixelRow = 0;
                    }

                }
            }

            blur = chunks[currentRow][currentColumn][0];

            newData.data[i] = blur.r;
            newData.data[i + 1] = blur.g;
            newData.data[i + 2] = blur.b;

        }

        return newData;
    }

    pap.smear = function (imageData, size) {

        if(!powerofTwo(size)) {
            console.warn('pixel size should be power of two for best results');
        }

        var newData,
        chunkSize = size,
        chunks = [],
        totalChunkColumns = imageData.width / chunkSize;

        newData = document.createElement('canvas').getContext('2d').getImageData(0,0,imageData.width, imageData.height);
        newData.data.set(imageData.data);

        chunks = pushPixelsIntoChunks(imageData.data, chunkSize, totalChunkColumns);
        chunks = averageOutChunkColors(chunks);
        newData = pushChunksIntoImageData(newData, chunks, chunkSize, totalChunkColumns);

        return newData;

    };

    return pap;

}());

module.exports = Pap;
<|endoftext|>


<commit_before>
var EventEmitter = require('events').EventEmitter
var util = require('util')
var debug = require('debug')('websocket-relay')
var Q = require('q')
var http = require('http')
var typeforce = require('typeforce')
// var clone = require('xtend')
var io = require('socket.io')
var constants = require('@tradle/constants')

function Server (opts) {
  var self = this

  typeforce({
    port: 'Number',
    path: '?String',
    // byRootHash: 'Function'
  }, opts)

  EventEmitter.call(this)

  this._queues = {}
  this._pendingHandshakes = {}
  this._socketsByRootHash = {}
  // this._lookup = opts.byRootHash
  this._server = http.createServer(function (req, res) {
    res.writeHead(500)
    res.end('This is a websockets endpoint!')
  })

  this._server.listen(opts.port)
  this._io = io(this._server, { path: opts.path || '/' }) //.of('/' + opts.rootHash)
  this._io.on('connection', function (socket) {
    var clientRootHash
    socket.on('error', function (err) {
      debug('socket for client', clientRootHash, 'experienced error', err)
      socket.disconnect()
    })

    // TODO: handshake before allowing them to subscribe
    socket.once('subscribe', function (rootHash) {
      clientRootHash = rootHash
      self.emit('connect', clientRootHash)

      self._socketsByRootHash[clientRootHash] = socket
      socket._tradleRootHash = clientRootHash
      socket.on('message', function (msg, cb) {
        try {
          typeforce({
            to: 'String',
            message: 'String'
          }, msg)
        } catch (err) {
          debug('received invalid message from', clientRootHash, err)
          return socket.emit('error', { message: 'invalid message', data: msg })
        }

        msg.from = clientRootHash
        msg.callback = cb
        self._forwardMessage(msg)
      })
    })

    socket.once('disconnect', function () {
      if (clientRootHash) {
        self.emit('disconnect', clientRootHash)
      }

      var rh = socket._tradleRootHash
      if (rh) delete self._socketsByRootHash[rh]
    })
  })
}

util.inherits(Server, EventEmitter)
module.exports = Server

// Server.prototype._handshake = function (socket, rootHash) {
//   var self = this
//   var pending = this._pendingHandshakes[rootHash]
//   if (pending) return pending.promise

//   var serverNonce
//   var identityInfo
//   var verifyingKey
//   var challengeRespDefer = Q.defer()
//   var handshakeDefer = Q.defer()
//   socket.once('disconnect', function () {
//     challengeRespDefer.reject()
//     handshakeDefer.reject()
//   })

//   socket.once('handshake', challengeRespDefer.resolve)
//   this.once('welcome:' + rootHash, handshakeDefer.resolve)

//   this._lookup(rootHash)
//     .then(function (result) {
//       identityInfo = result
//       var pubkeys = result.identity.toJSON().pubkeys
//       verifyingKey = pubkeys.filter(function (k) {
//         return k.type === 'ec' && k.purpose === 'sign'
//       })[0]

//       serverNonce = crypto.randomBytes(32).toString('hex')
//       debug('handshake 1. sending challenge', rootHash)
//       socket.emit('handshake', {
//         serverNonce: serverNonce,
//         key: verifyingKey.fingerprint
//       })

//       return Q.race([
//         Q.Promise(function (resolve, reject) {
//           setTimeout(function () {
//             reject(new Error('handshake timed out'))
//           }, HANDSHAKE_TIMEOUT).unref()
//         }),
//         challengeRespDefer.promise
//       ])
//     })
//     .then(function (data) {
//       debug('handshake 2. received challenge response from', rootHash)
//       if (data.serverNonce !== serverNonce) {
//         throw new Error('nonce mismatch')
//       }

//       var sig = data[SIG]
//       if (!sig) throw new Error('missing signature')

//       delete data[SIG]
//       var responseStr = utils.stringify(data)
//       verifyingKey = kiki.toKey(verifyingKey)
//       if (!verifyingKey.verify(responseStr, sig)) {
//         throw new Error('client gave bad signature')
//       }
//     })
//     .then(function () {
//       debug('handshake 3. client authenticated', rootHash)

//       // http://stackoverflow.com/questions/17476294/how-to-send-a-message-to-a-particular-client-with-socket-io#comment39387846_17535099
//       // each customer joins the room corresponding to their ROOT_HASH
//       self._addSocket[rootHash]
//       // corresponding client side code:
//       // var socket = io.connect('http://localhost');
//       // socket.emit('join', {[ROOT_HASH]: '...'})
//     })
//     .catch(function (err) {
//       console.error(err, err.stack)
//       throw err
//     })

//   this._pendingHandshakes[rootHash] = {
//     socket: socket,
//     promise: handshakeDefer.promise
//   }

//   return handshakeDefer.promise
// }

// Server.prototype._addSocket = function (rootHash, socket) {
//   var self = this
//   delete this._pendingHandshakes[rootHash]
//   this._socketsByRootHash[rootHash] = socket
//   socket.join(rootHash)
//   socket.emit('welcome')
//   socket.on('message', function (msg, cb) {
//     var toRootHash = msg.toRootHash
//     debug('forwarding msg from', rootHash, 'to', toRootHash)
//     self._forwardMessage({
//       form: rootHash,
//       to: toRootHash,
//       message: msg.data,
//       callback: cb
//     })
//   })

//   this.emit('welcome:' + rootHash)

//   var queued = this._queues[rootHash]
//   if (queued && queued.length) {
//     queued = queued.slice()
//     this._queued[rootHash].length = 0
//     queued.forEach(self._forwardMessage, self)
//   }
// }

Server.prototype.getConnectedClients = function () {
  return Object.keys(this._socketsByRootHash)
}

Server.prototype._forwardMessage = function (msgInfo) {
  typeforce({
    from: 'String',
    to: 'String',
    message: 'String',
    callback: '?Function'
  }, msgInfo)

  var to = msgInfo.to
  var socket = this._socketsByRootHash[to]
  if (socket) {
    return socket.emit('message', {
      from: msgInfo.from,
      message: msgInfo.message
    }, msgInfo.callback)
  }

  if (msgInfo.callback) {
    msgInfo.callback({ error: { message: 'Recipient not found' } })
  }
}

Server.prototype.hasClient = function (rootHash) {
  return rootHash in this._socketsByRootHash
}

Server.prototype.destroy = function () {
  if (this._destroyed) return Q.reject(new Error('already destroyed'))

  this._destroyed = true
  debug('destroying')

  for (var rootHash in this._socketsByRootHash) {
    var s = this._socketsByRootHash[rootHash]
    s.disconnect()
    // s.removeAllListeners()
  }

  delete this._socketsByRootHash
  this._io.close()
  this._server.close()
  return Q()
}
<commit_msg>small reorg<commit_after>
var EventEmitter = require('events').EventEmitter
var util = require('util')
var debug = require('debug')('websocket-relay')
var Q = require('q')
var http = require('http')
var typeforce = require('typeforce')
// var clone = require('xtend')
var io = require('socket.io')
var constants = require('@tradle/constants')

function Server (opts) {
  var self = this

  typeforce({
    port: 'Number',
    path: '?String',
    // byRootHash: 'Function'
  }, opts)

  EventEmitter.call(this)

  this._queues = {}
  this._pendingHandshakes = {}
  this._socketsByRootHash = {}
  // this._lookup = opts.byRootHash
  this._server = http.createServer(function (req, res) {
    res.writeHead(500)
    res.end('This is a websockets endpoint!')
  })

  this._server.listen(opts.port)
  this._io = io(this._server, { path: opts.path || '/' }) //.of('/' + opts.rootHash)
  this._io.on('connection', this._onconnection.bind(this))
}

util.inherits(Server, EventEmitter)
module.exports = Server

Server.prototype._onconnection = function (socket) {
  var self = this
  var clientRootHash
  socket.on('error', function (err) {
    debug('disconnecting, socket for client ' + clientRootHash + ' experienced an error', err)
    socket.disconnect()
  })

  // TODO: handshake before allowing them to subscribe
  socket.once('subscribe', function (rootHash) {
    debug('got "subscribe" from ' + rootHash)
    var existing = self._socketsByRootHash[rootHash]
    if (existing) {
      if (existing !== socket) {
        debug('disconnecting second socket for ' + rootHash)
        socket.disconnect()
      }

      return
    }

    clientRootHash = rootHash
    self.emit('connect', clientRootHash)

    self._socketsByRootHash[clientRootHash] = socket
    socket._tradleRootHash = clientRootHash
    socket.on('message', function (msg, cb) {
      debug('got message from ' + clientRootHash)

      try {
        typeforce({
          to: 'String',
          message: 'String'
        }, msg)
      } catch (err) {
        debug('received invalid message from', clientRootHash, err)
        return socket.emit('error', { message: 'invalid message', data: msg })
      }

      msg.from = clientRootHash
      msg.callback = cb
      self._forwardMessage(msg)
    })
  })

  socket.once('disconnect', function () {
    if (!clientRootHash) return

    debug(clientRootHash + ' disconnected')
    self.emit('disconnect', clientRootHash)
    delete self._socketsByRootHash[clientRootHash]
  })
}

// Server.prototype._handshake = function (socket, rootHash) {
//   var self = this
//   var pending = this._pendingHandshakes[rootHash]
//   if (pending) return pending.promise

//   var serverNonce
//   var identityInfo
//   var verifyingKey
//   var challengeRespDefer = Q.defer()
//   var handshakeDefer = Q.defer()
//   socket.once('disconnect', function () {
//     challengeRespDefer.reject()
//     handshakeDefer.reject()
//   })

//   socket.once('handshake', challengeRespDefer.resolve)
//   this.once('welcome:' + rootHash, handshakeDefer.resolve)

//   this._lookup(rootHash)
//     .then(function (result) {
//       identityInfo = result
//       var pubkeys = result.identity.toJSON().pubkeys
//       verifyingKey = pubkeys.filter(function (k) {
//         return k.type === 'ec' && k.purpose === 'sign'
//       })[0]

//       serverNonce = crypto.randomBytes(32).toString('hex')
//       debug('handshake 1. sending challenge', rootHash)
//       socket.emit('handshake', {
//         serverNonce: serverNonce,
//         key: verifyingKey.fingerprint
//       })

//       return Q.race([
//         Q.Promise(function (resolve, reject) {
//           setTimeout(function () {
//             reject(new Error('handshake timed out'))
//           }, HANDSHAKE_TIMEOUT).unref()
//         }),
//         challengeRespDefer.promise
//       ])
//     })
//     .then(function (data) {
//       debug('handshake 2. received challenge response from', rootHash)
//       if (data.serverNonce !== serverNonce) {
//         throw new Error('nonce mismatch')
//       }

//       var sig = data[SIG]
//       if (!sig) throw new Error('missing signature')

//       delete data[SIG]
//       var responseStr = utils.stringify(data)
//       verifyingKey = kiki.toKey(verifyingKey)
//       if (!verifyingKey.verify(responseStr, sig)) {
//         throw new Error('client gave bad signature')
//       }
//     })
//     .then(function () {
//       debug('handshake 3. client authenticated', rootHash)

//       // http://stackoverflow.com/questions/17476294/how-to-send-a-message-to-a-particular-client-with-socket-io#comment39387846_17535099
//       // each customer joins the room corresponding to their ROOT_HASH
//       self._addSocket[rootHash]
//       // corresponding client side code:
//       // var socket = io.connect('http://localhost');
//       // socket.emit('join', {[ROOT_HASH]: '...'})
//     })
//     .catch(function (err) {
//       console.error(err, err.stack)
//       throw err
//     })

//   this._pendingHandshakes[rootHash] = {
//     socket: socket,
//     promise: handshakeDefer.promise
//   }

//   return handshakeDefer.promise
// }

// Server.prototype._addSocket = function (rootHash, socket) {
//   var self = this
//   delete this._pendingHandshakes[rootHash]
//   this._socketsByRootHash[rootHash] = socket
//   socket.join(rootHash)
//   socket.emit('welcome')
//   socket.on('message', function (msg, cb) {
//     var toRootHash = msg.toRootHash
//     debug('forwarding msg from', rootHash, 'to', toRootHash)
//     self._forwardMessage({
//       form: rootHash,
//       to: toRootHash,
//       message: msg.data,
//       callback: cb
//     })
//   })

//   this.emit('welcome:' + rootHash)

//   var queued = this._queues[rootHash]
//   if (queued && queued.length) {
//     queued = queued.slice()
//     this._queued[rootHash].length = 0
//     queued.forEach(self._forwardMessage, self)
//   }
// }

Server.prototype.getConnectedClients = function () {
  return Object.keys(this._socketsByRootHash)
}

Server.prototype._forwardMessage = function (msgInfo) {
  typeforce({
    from: 'String',
    to: 'String',
    message: 'String',
    callback: '?Function'
  }, msgInfo)

  var to = msgInfo.to
  var socket = this._socketsByRootHash[to]
  if (socket) {
    return socket.emit('message', {
      from: msgInfo.from,
      message: msgInfo.message
    }, msgInfo.callback)
  }

  if (msgInfo.callback) {
    msgInfo.callback({ error: { message: 'Recipient not found' } })
  }
}

Server.prototype.hasClient = function (rootHash) {
  return rootHash in this._socketsByRootHash
}

Server.prototype.destroy = function () {
  if (this._destroyed) return Q.reject(new Error('already destroyed'))

  this._destroyed = true
  debug('destroying')

  for (var rootHash in this._socketsByRootHash) {
    var s = this._socketsByRootHash[rootHash]
    s.disconnect()
    // s.removeAllListeners()
  }

  delete this._socketsByRootHash
  this._io.close()
  this._server.close()
  return Q()
}
<|endoftext|>


<commit_before>/*!
 * parse-git-config <https://github.com/jonschlinkert/parse-git-config>
 *
 * Copyright (c) 2015 Jon Schlinkert.
 * Licensed under the MIT license.
 */

'use strict';

var fs = require('fs');
var path = require('path');
var ini = require('ini');

function parseGitConfig(options, cb) {
  if (typeof options === 'function') {
    cb = options;
    options = null;
  }

  var fp = resolve(options);

  if (typeof cb !== 'function') {
    throw new TypeError('parse-git-config async expects a callback function.');
  }

  read(fp, function(err, buffer) {
    if (err) {
      cb(err);
      return;
    }
    cb(null, ini.parse(buffer.toString()));
  });
}

parseGitConfig.sync = function parseGitConfigSync(options) {
  var fp = resolve(options);
  if (!fs.existsSync(fp)) {
    return null;
  }
  return ini.parse(fs.readFileSync(fp, 'utf8'));
};

function read(fp, cb) {
  try {
    fs.readFile(fp, function(err, config) {
      if (err) {
        return cb(err);
      }
      cb(null, config);
    });
  } catch (err) {
    cb(err);
  }
}

function resolve(options) {
  options = options || {};
  var cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
  return path.resolve(cwd, options.path || '.git/config');
}

/**
 * Expose `resolve`
 */

parseGitConfig.resolve = resolve;

/**
 * Expose `parseGitConfig`
 */

module.exports = parseGitConfig;
<commit_msg>reorganize a little, add code comments<commit_after>/*!
 * parse-git-config <https://github.com/jonschlinkert/parse-git-config>
 *
 * Copyright (c) 2015 Jon Schlinkert.
 * Licensed under the MIT license.
 */

'use strict';

var fs = require('fs');
var path = require('path');
var ini = require('ini');

/**
 * Asynchronously parse a `.git/config` file. If only the callback is passed,
 * the `.git/config` file relative to `process.cwd()` is used.
 *
 * ```js
 * parse(function(err, config) {
 *   if (err) throw err;
 *   // do stuff with config
 * });
 * ```
 * @param {Object|String|Function} `options` Options with `cwd` or `path`, the cwd to use, or the callback function.
 * @param {Function} `cb` callback function if the first argument is options or cwd.
 * @return {Object}
 * @api public
 */

function parse(options, cb) {
  if (typeof options === 'function') {
    cb = options;
    options = {};
  }

  if (typeof cb !== 'function') {
    throw new TypeError('parse-git-config async expects a callback function.');
  }

  options = options || {};
  var filepath = parse.resolve(options);

  fs.stat(filepath, function(err, stat) {
    if (err) return cb(err);

    fs.readFile(filepath, 'utf8', function(err, str) {
      if (err) return cb(err);
      cb(null, ini.parse(str));
    });
  });
}

/**
 * Synchronously parse a `.git/config` file. If no arguments are passed,
 * the `.git/config` file relative to `process.cwd()` is used.
 *
 * ```js
 * var config = parse.sync();
 * ```
 *
 * @name .sync
 * @param {Object|String} `options` Options with `cwd` or `path`, or the cwd to use.
 * @return {Object}
 * @api public
 */

parse.sync = function parseSync(options) {
  options = options || {};
  var filepath = parse.resolve(options);
  if (exists(filepath)) {
    var str = fs.readFileSync(filepath, 'utf8');
    return ini.parse(str);
  }
  return {};
};

/**
 * Resolve the git config path
 */

parse.resolve = function resolve(options) {
  options = options || {};
  var cwd;
  if (typeof options === 'string') {
    cwd = path.resolve(options);
    options = {};
  } else if (options.cwd) {
    cwd = options.cwd;
  } else {
    cwd = process.cwd();
  }
  return path.resolve(cwd, options.path || '.git/config');
};

/**
 * Returns an object with only the properties that had ini-style keys
 * converted to objects (example below).
 *
 * ```js
 * var config = parse.sync();
 * var obj = parse.keys(config);
 * ```
 * @name .keys
 * @param {Object} `config` The parsed git config object.
 * @return {Object}
 * @api public
 */

parse.keys = function parseKeys(config) {
  var res = {};
  for (var key in config) {
    var m = /(\S+) "(.*)"/.exec(key);
    if (!m) continue;
    var prop = m[1];
    res[prop] = res[prop] || {};
    res[prop][m[2]] = config[key];
  }
  return res;
};

function exists(fp) {
  try {
    fs.statSync(fp);
    return true;
  } catch (err) {}
  return false;
}

/**
 * Expose `parse`
 */

module.exports = parse;
<|endoftext|>


<commit_before>

var through = require('through2'),
    gutil = require('gulp-util'),
    po = require('node-po'),
    PluginError = gutil.PluginError;

module.exports = function () {

    function write (f, enc, cb){
        if (f.isNull()) {
            this.push(file);
            return cb();
        }

        if (f.isStream()) {
            this.emit('error', new PluginError('gulp-po-json',
                'Streaming not supported'));
            return cb();
        }

        var pofile = po.parse(f.contents.toString()),
            res = {},
            dic = {},
            i,
            l;

        if (!pofile) {
            this.emit('error', new PluginError('gulp-po-json',
                'Unable to parse file ' + f.path));
            return cb();
        }

        res.meta = pofile.headers;
        res.dic = dic;

        for (i = 0, l = pofile.items.length; i < l; i += 1) {
            res.dic[pofile.items[i].msgid] = pofile.items[i].msgstr[0];
        }
        this.push(JSON.stringify(res));
        cb();
    }

    function end (cb) {
        cb();
    }

    return through.obj(write, end);
};
<commit_msg>Make sure content of po files is read in utf8<commit_after>

var through = require('through2'),
    gutil = require('gulp-util'),
    po = require('node-po'),
    PluginError = gutil.PluginError;

module.exports = function () {

    function write (f, enc, cb){
        if (f.isNull()) {
            this.push(file);
            return cb();
        }

        if (f.isStream()) {
            this.emit('error', new PluginError('gulp-po-json',
                'Streaming not supported'));
            return cb();
        }

        var pofile = po.parse(f.contents.toString('utf-8')),
            res = {},
            dic = {},
            i,
            l;

        if (!pofile) {
            this.emit('error', new PluginError('gulp-po-json',
                'Unable to parse file ' + f.path));
            return cb();
        }

        res.meta = pofile.headers;
        res.dic = dic;

        for (i = 0, l = pofile.items.length; i < l; i += 1) {
            res.dic[pofile.items[i].msgid] = pofile.items[i].msgstr[0];
        }
        this.push(JSON.stringify(res));
        cb();
    }

    function end (cb) {
        cb();
    }

    return through.obj(write, end);
};
<|endoftext|>


<commit_before>let EventEmitter = require("events").EventEmitter;
let util = require("util");

//Keue object
let Keue = function (options) {
    if (typeof options !== "object" || options === null) {
        let options = {};
    }
    if (!(this instanceof Keue)) {
        //Return a new instance of the keue method
        return new Keue(options)
    }
    //EventEmitter.call(this); //Extend the events class
    this._tasks = {}; //Tasks list
    this._running = false; //Tasks are running
    this._events = new EventEmitter(); //Event emitter
    return this;
};

//Register a new event listener
Keue.prototype.on = function (name, listener) {
    this._events.on(name, listener);
    return this;
};

//Register a new task
Keue.prototype.addTask = function (name, listener) {
    if (typeof name !== "string") {
        throw new Error("Task requires a name");
    }
    if (name.trim().length === 0) {
        throw new Error("Task name has to be a non-empty string");
    }
    if (typeof listener !== "function") {
        throw new Error("Task '" + name + "' requires a function");
    }
    //Register this task
    this._tasks[name] = {name: name, listener: listener, done: false};
    return this;
};

//Check if the provided task has been defined
Keue.prototype.hasTask = function (name) {
    return typeof this._tasks[name] === "object";
};

//Remove the task
Keue.prototype.removeTask = function (name) {
    if (this.hasTask(name) === true) {
        delete this._tasks[name];
    }
    return this;
};

//Start the tasks
Keue.prototype.run = function () {
    let self = this;
    let tasks = [];
    //Check if the queue is already running
    if (this._running === true) {
        return;
    }
    //Check the current number of tasks
    if (Object.keys(this._tasks).length === 0) {
        throw new Error("Empty tasks list");
    }
    //Parse all the arguments
    Array.prototype.slice.call(arguments, 0).forEach(function (arg) {
        if (typeof arg === "string") {
            tasks.push(arg);
        } else if (Array.isArray(arg) === true) {
            tasks = tasks.concat(arg);
        } else {
            throw new Error("Invalid task name provided");
        }
    });
    if (tasks.length < 1) {
        //Run all tasks
        Object.keys(this._tasks).forEach(function (name) {
            tasks.push(name);
        });
    }
    //Run a task
    let runTask = function (index) {
        if (index >= tasks.length) {
            //Tasks finished
            self._running = false;
            return self._events.emit("finish");
        }
        let task = self._tasks[tasks[index]];
        //Check for not found task
        if (typeof task !== "object" || task === null) {
            self._running = false;
            return self._events.emit("error", new Error("Task '" + tasks[index] + "' not found"));
        }
        //Check if the task has already executed
        if (task.done === true) {
            self._running = false;
            return self._events.emit("error", new Error("Task '" + task.name + "' has been already completed"));
        }
        //task.start = Date.now();
        self._events.emit("task:start", task.name);
        return task.listener.call(null, function (error) {
            //Check for error running the task
            if (error) {
                self._running = false;
                return self._events.emit("error", error);
            }
            //task.end = Date.now();
            task.done = true;
            self._events.emit("task:end", task.name);
            return process.nextTick(function () {
                //Continue with the next task in the queue
                return runTask(index + 1);
            });
        });
    };
    this._running = true;
    this._events.emit("start");
    process.nextTick(function () {
        //Initialize the task queue in the next tick. This prevents errors
        //when the event listeners are defined after executing the run method
        return runTask(0);
    });
    return this;
};

module.exports = Keue;
<commit_msg>Added task dependdencies support -> now you can determine the list of thasks that should be executed before running the desired task<commit_after>let EventEmitter = require("events").EventEmitter;
let util = require("util");

let buildSequence = require("./lib/sequence.js");

//Keue object
let Keue = function (options) {
    if (typeof options !== "object" || options === null) {
        let options = {};
    }
    if (!(this instanceof Keue)) {
        //Return a new instance of the keue method
        return new Keue(options)
    }
    //EventEmitter.call(this); //Extend the events class
    this._tasks = {}; //Tasks list
    this._running = false; //Tasks are running
    this._events = new EventEmitter(); //Event emitter
    return this;
};

//Register a new event listener
Keue.prototype.on = function (name, listener) {
    this._events.on(name, listener);
    return this;
};

//Register a new task
Keue.prototype.addTask = function (name, dependencies, listener) {
    if (!listener && typeof dependencies === "function") {
        listener = dependencies;
        dependencies = [];
    }
    if (typeof name !== "string") {
        throw new Error("Task requires a name");
    }
    if (name.trim().length === 0) {
        throw new Error("Task name has to be a non-empty string");
    }
    if (typeof listener !== "function") {
        throw new Error("Task '" + name + "' requires a function");
    }
    if (Array.isArray(dependencies) === false) {
        if (typeof dependencies === "string") {
            dependencies = [dependencies];
        } else {
            throw new Error("Dependencies should be a string or an array of strings");
        }
    }
    //Register this task
    this._tasks[name] = {name: name, dependencies: dependencies, listener: listener, done: false};
    return this;
};

//Check if the provided task has been defined
Keue.prototype.hasTask = function (name) {
    return typeof this._tasks[name] === "object";
};

//Remove the task
Keue.prototype.removeTask = function (name) {
    if (this.hasTask(name) === true) {
        delete this._tasks[name];
    }
    return this;
};

//Start the tasks
Keue.prototype.run = function () {
    let self = this;
    let names = [];
    //Check if the queue is already running
    if (this._running === true) {
        return;
    }
    //Check the current number of tasks
    if (Object.keys(this._tasks).length === 0) {
        throw new Error("Empty tasks list");
    }
    //Parse all the arguments
    Array.prototype.slice.call(arguments, 0).forEach(function (arg) {
        if (typeof arg === "string") {
            names.push(arg);
        } else if (Array.isArray(arg) === true) {
            names = names.concat(arg);
        } else {
            throw new Error("Invalid task name provided");
        }
    });
    if (names.length < 1) {
        //Run all tasks
        Object.keys(this._tasks).forEach(function (name) {
            names.push(name);
        });
    }
    //Generate the correct sequence of tasks
    let sequence = buildSequence(this._tasks, names);
    //Run a task
    let runTask = function (index) {
        if (index >= sequence.length) {
            //Tasks finished
            self._running = false;
            return self._events.emit("finish");
        }
        let task = self._tasks[sequence[index]];
        //Check for not found task
        if (typeof task !== "object" || task === null) {
            self._running = false;
            return self._events.emit("error", new Error("Task '" + sequence[index] + "' not found"));
        }
        //Check if the task has already executed
        if (task.done === true) {
            self._running = false;
            return self._events.emit("error", new Error("Task '" + task.name + "' has been already completed"));
        }
        //task.start = Date.now();
        self._events.emit("task:start", task.name);
        return task.listener.call(null, function (error) {
            //Check for error running the task
            if (error) {
                self._running = false;
                return self._events.emit("error", error);
            }
            //task.end = Date.now();
            task.done = true;
            self._events.emit("task:end", task.name);
            return process.nextTick(function () {
                //Continue with the next task in the queue
                return runTask(index + 1);
            });
        });
    };
    this._running = true;
    this._events.emit("start");
    process.nextTick(function () {
        //Initialize the task queue in the next tick. This prevents errors
        //when the event listeners are defined after executing the run method
        return runTask(0);
    });
    return this;
};

module.exports = Keue;
<|endoftext|>