<commit_before>let debug = require('./debug')('indexd')
let dbwrapper = require('./dbwrapper')
let { EventEmitter } = require('events')
let parallel = require('run-parallel')
let rpcUtil = require('./rpc')

let FeeIndex = require('./indexes/fee')
let MtpIndex = require('./indexes/mediantime')
let ScriptIndex = require('./indexes/script')
let TxIndex = require('./indexes/tx')
let TxinIndex = require('./indexes/txin')
let TxoIndex = require('./indexes/txo')

function txoToString ({ txId, vout }) {
  return `${txId}:${vout}`
}

function Indexd (db, rpc) {
  this.db = dbwrapper(db)
  this.rpc = rpc
  this.emitter = new EventEmitter() // TODO: bind to this
  this.emitter.setMaxListeners(Infinity)
  this.indexes = {
    fee: new FeeIndex(),
    mtp: new MtpIndex(),
    script: new ScriptIndex(),
    tx: new TxIndex(),
    txin: new TxinIndex(),
    txo: new TxoIndex()
  }
}

Indexd.prototype.tips = function (callback) {
  let tasks = {}

  for (let indexName in this.indexes) {
    let index = this.indexes[indexName]
    tasks[indexName] = (next) => index.tip(this.db, next)
  }

  parallel(tasks, callback)
}

// recurses until `nextBlockId` is falsy
Indexd.prototype.connectFrom = function (prevBlockId, blockId, callback) {
  this.tips((err, tips) => {
    if (err) return callback(err)

    let todo = {}
    for (let indexName in tips) {
      let tip = tips[indexName]
      if (tip && tip.blockId !== prevBlockId) continue
      if (indexName === 'fee') {
        if (!tips.txo) continue
        if (tip && tips.fee.height >= tips.txo.height) continue
      }

      todo[indexName] = true
    }

    let todoList = Object.keys(todo)
    if (todoList.length === 0) return callback(new RangeError('Misconfiguration'))

    debug(`Downloading ${blockId} (for ${todoList})`)

    rpcUtil.block(this.rpc, blockId, (err, block) => {
      if (err) return callback(err)

      let atomic = this.db.atomic()
      let events // TODO
      let { height } = block
      debug(`Connecting ${blockId} @ ${height}`)

      // connect block to relevant chain tips
      for (let indexName in todo) {
        let index = this.indexes[indexName]
        if (!index.connect) continue

        index.connect(atomic, block, events)
      }

      atomic.write((err) => {
        if (err) return callback(err)
        debug(`Connected ${blockId} @ ${height}`)

        let self = this
        function loop (err) {
          if (err) return callback(err)
          // recurse until nextBlockId is falsy
          if (!block.nextBlockId) return callback()
          self.connectFrom(blockId, block.nextBlockId, callback)
        }

        if (!todo.fee) return loop()

        debug(`Connecting ${blockId} (2nd Order)`)
        let atomic2 = this.db.atomic()
        this.indexes.fee.connect2ndOrder(this.db, this.indexes.txo, atomic2, block, (err) => {
          if (err) return loop(err)

          debug(`Connected ${blockId} (2nd Order)`)
          atomic2.write(loop)
        })
      })
    })
  })
}

Indexd.prototype.disconnect = function (blockId, callback) {
  debug(`Disconnecting ${blockId}`)

  function fin (err) {
    if (err) return callback(err)
    debug(`Disconnected ${blockId}`)
    callback()
  }

  this.tips((err, tips) => {
    if (err) return fin(err)

    // TODO: fetch lazily
    rpcUtil.block(this.rpc, blockId, (err, block) => {
      if (err) return fin(err)

      let atomic = this.db.atomic()

      // disconnect block from relevant chain tips
      for (let indexName in this.indexes) {
        let index = this.indexes[indexName]
        let tip = tips[indexName]
        if (!tip) continue
        if (tip.blockId !== block.blockId) continue

        index.disconnect(atomic, block)
      }

      atomic.write(fin)
    })
  })
}

Indexd.prototype.resync = function (done) {
  this.emitter.once('resync', done)
  if (this.syncing) return
  this.syncing = true

  debug('resynchronizing')
  let self = this
  function fin (err) {
    self.syncing = false
    self.emitter.emit('resync', err)
  }

  function trySyncFrom (prevBlockId, blockId, confirmations) {
    // reset mempools
    for (let indexName in self.indexes) {
      self.indexes[indexName].constructor()
    }

    // TODO: if confirmations > 100, go fast
    self.connectFrom(prevBlockId, blockId, fin)
  }

  function lowestTip (callback) {
    self.tips((err, tips) => {
      if (err) return callback(err)

      let lowest
      for (let key in tips) {
        let tip = tips[key]
        if (!tip) return callback()
        if (!lowest) lowest = tip
        if (lowest.height < tip.height) continue
        lowest = tip
      }

      callback(null, lowest)
    })
  }

  parallel({
    bitcoind: (f) => rpcUtil.tip(this.rpc, f),
    indexd: (f) => lowestTip(f)
  }, (err, r) => {
    if (err) return fin(err)

    // Step 0, genesis?
    if (!r.indexd) {
      debug('genesis')
      return rpcUtil.blockIdAtHeight(this.rpc, 0, (err, genesisId) => {
        if (err) return fin(err)

        trySyncFrom(null, genesisId, r.bitcoind.height)
      })
    }

    // Step 1, equal?
    debug('...', r)
    if (r.bitcoind.blockId === r.indexd.blockId) return fin()

    // Step 2, is indexd behind? [aka, does bitcoind have the indexd tip]
    rpcUtil.headerJSON(this.rpc, r.indexd.blockId, (err, common) => {
//        if (err && /not found/.test(err.message)) return fin(err) // uh, burn it to the ground
      if (err) return fin(err)

      // forked?
      if (common.confirmations === -1) {
        debug('forked')
        return this.disconnect(r.indexd.blockId, (err) => {
          if (err) return fin(err)

          this.resync(fin)
        })
      }

      // yes, indexd is behind
      debug('bitcoind is ahead')
      trySyncFrom(common.blockId, common.nextBlockId, common.confirmations)
    })
  })
}

Indexd.prototype.notify = function (txId, callback) {
  rpcUtil.transaction(this.rpc, txId, (err, tx) => {
    if (err) return callback(err)

    for (let indexName in this.indexes) {
      let index = this.indexes[indexName]

      if (!index.mempool) continue
      index.mempool(tx)
    }

    callback()
  })
}

// QUERIES
Indexd.prototype.blockIdByTransactionId = function (txId, callback) {
  this.indexes.tx.heightBy(this.db, txId, (err, height) => {
    if (err) return callback(err)
    if (height === -1) return callback()

    rpcUtil.blockIdAtHeight(this.rpc, height, callback)
  })
}

Indexd.prototype.latestFeesForNBlocks = function (nBlocks, callback) {
  this.indexes.fee.latestFeesFor(this.db, nBlocks, callback)
}

// returns whether (true/false) the script id has even been seen
Indexd.prototype.seenScriptId = function (scId, callback) {
  this.indexes.scripts.seenScriptId(this.db, scId, callback)
}

// returns a set of txIds with inputs/outputs from/to a { scId, heightRange }
Indexd.prototype.transactionIdsByScriptRange = function (scRange, dbLimit, callback) {
  this.txosByScriptId(scRange, dbLimit, (err, txos) => {
    if (err) return callback(err)

    let txIdSet = {}
    let tasks = txos.map((txo) => {
      txIdSet[txo.txId] = true
      return (next) => this.indexes.txin.txinBy(this.db, txo, next)
    })

    parallel(tasks, (err, txins) => {
      if (err) return callback(err)

      txins.forEach((txin) => {
        if (!txin) return
        txIdSet[txin.txId] = true
      })

      callback(null, txIdSet)
    })
  })
}

// returns a list of txos { txId, vout, height, value } by { scId, heightRange }
Indexd.prototype.txosByScriptRange = function (scRange, dbLimit, callback) {
  this.indexes.script.txosBy(this.db, scRange, dbLimit, callback)
}

// returns a txo { txId, vout, value, script }, by key { txId, vout }
Indexd.prototype.txoByTxo = function (txo, callback) {
  this.indexes.txo.txoBy(this.db, txo, callback)
}

// returns a list of (unspent) txos { txId, vout, height, value }, by { scId, heightRange }
// XXX: despite txo queries being bound by heightRange, the UTXO status is up-to-date
Indexd.prototype.utxosByScriptRange = function (scRange, dbLimit, callback) {
  this.txosByScriptRange(scRange, dbLimit, (err, txos) => {
    if (err) return callback(err)

    let taskMap = {}
    let unspentMap = {}

    txos.forEach((txo) => {
      let txoId = txoToString(txo)

      unspentMap[txoId] = txo
      taskMap[txoId] = (next) => this.indexes.txin.txinBy(this.db, txo, next)
    })

    parallel(taskMap, (err, txinMap) => {
      if (err) return callback(err)

      for (let txoId in txinMap) {
        let txin = txinMap[txoId]
        if (!txin) continue

        // has a txin, therefore spent
        delete unspentMap[txoId]
      }

      callback(null, unspentMap)
    })
  })
}

module.exports = Indexd
<commit_msg>fix fee index lagging by 1 block<commit_after>let debug = require('./debug')('indexd')
let dbwrapper = require('./dbwrapper')
let { EventEmitter } = require('events')
let parallel = require('run-parallel')
let rpcUtil = require('./rpc')

let FeeIndex = require('./indexes/fee')
let MtpIndex = require('./indexes/mediantime')
let ScriptIndex = require('./indexes/script')
let TxIndex = require('./indexes/tx')
let TxinIndex = require('./indexes/txin')
let TxoIndex = require('./indexes/txo')

function txoToString ({ txId, vout }) {
  return `${txId}:${vout}`
}

function Indexd (db, rpc) {
  this.db = dbwrapper(db)
  this.rpc = rpc
  this.emitter = new EventEmitter() // TODO: bind to this
  this.emitter.setMaxListeners(Infinity)
  this.indexes = {
    fee: new FeeIndex(),
    mtp: new MtpIndex(),
    script: new ScriptIndex(),
    tx: new TxIndex(),
    txin: new TxinIndex(),
    txo: new TxoIndex()
  }
}

Indexd.prototype.tips = function (callback) {
  let tasks = {}

  for (let indexName in this.indexes) {
    let index = this.indexes[indexName]
    tasks[indexName] = (next) => index.tip(this.db, next)
  }

  parallel(tasks, callback)
}

// recurses until `nextBlockId` is falsy
Indexd.prototype.connectFrom = function (prevBlockId, blockId, callback) {
  this.tips((err, tips) => {
    if (err) return callback(err)

    let todo = {}
    for (let indexName in tips) {
      let tip = tips[indexName]
      if (tip && tip.blockId !== prevBlockId) continue
      if (indexName === 'fee') {
        if (!tips.txo) continue
        if (tip && tips.fee.height > tips.txo.height) continue
      }

      todo[indexName] = true
    }

    let todoList = Object.keys(todo)
    if (todoList.length === 0) return callback(new RangeError('Misconfiguration'))

    debug(`Downloading ${blockId} (for ${todoList})`)

    rpcUtil.block(this.rpc, blockId, (err, block) => {
      if (err) return callback(err)

      let atomic = this.db.atomic()
      let events // TODO
      let { height } = block
      debug(`Connecting ${blockId} @ ${height}`)

      // connect block to relevant chain tips
      for (let indexName in todo) {
        let index = this.indexes[indexName]
        if (!index.connect) continue

        index.connect(atomic, block, events)
      }

      atomic.write((err) => {
        if (err) return callback(err)
        debug(`Connected ${blockId} @ ${height}`)

        let self = this
        function loop (err) {
          if (err) return callback(err)
          // recurse until nextBlockId is falsy
          if (!block.nextBlockId) return callback()
          self.connectFrom(blockId, block.nextBlockId, callback)
        }

        if (!todo.fee) return loop()

        debug(`Connecting ${blockId} (2nd Order)`)
        let atomic2 = this.db.atomic()
        this.indexes.fee.connect2ndOrder(this.db, this.indexes.txo, atomic2, block, (err) => {
          if (err) return loop(err)

          debug(`Connected ${blockId} (2nd Order)`)
          atomic2.write(loop)
        })
      })
    })
  })
}

Indexd.prototype.disconnect = function (blockId, callback) {
  debug(`Disconnecting ${blockId}`)

  function fin (err) {
    if (err) return callback(err)
    debug(`Disconnected ${blockId}`)
    callback()
  }

  this.tips((err, tips) => {
    if (err) return fin(err)

    // TODO: fetch lazily
    rpcUtil.block(this.rpc, blockId, (err, block) => {
      if (err) return fin(err)

      let atomic = this.db.atomic()

      // disconnect block from relevant chain tips
      for (let indexName in this.indexes) {
        let index = this.indexes[indexName]
        let tip = tips[indexName]
        if (!tip) continue
        if (tip.blockId !== block.blockId) continue

        index.disconnect(atomic, block)
      }

      atomic.write(fin)
    })
  })
}

Indexd.prototype.resync = function (done) {
  this.emitter.once('resync', done)
  if (this.syncing) return
  this.syncing = true

  debug('resynchronizing')
  let self = this
  function fin (err) {
    self.syncing = false
    self.emitter.emit('resync', err)
  }

  function trySyncFrom (prevBlockId, blockId, confirmations) {
    // reset mempools
    for (let indexName in self.indexes) {
      self.indexes[indexName].constructor()
    }

    // TODO: if confirmations > 100, go fast
    self.connectFrom(prevBlockId, blockId, fin)
  }

  function lowestTip (callback) {
    self.tips((err, tips) => {
      if (err) return callback(err)

      let lowest
      for (let key in tips) {
        let tip = tips[key]
        if (!tip) return callback()
        if (!lowest) lowest = tip
        if (lowest.height < tip.height) continue
        lowest = tip
      }

      callback(null, lowest)
    })
  }

  parallel({
    bitcoind: (f) => rpcUtil.tip(this.rpc, f),
    indexd: (f) => lowestTip(f)
  }, (err, r) => {
    if (err) return fin(err)

    // Step 0, genesis?
    if (!r.indexd) {
      debug('genesis')
      return rpcUtil.blockIdAtHeight(this.rpc, 0, (err, genesisId) => {
        if (err) return fin(err)

        trySyncFrom(null, genesisId, r.bitcoind.height)
      })
    }

    // Step 1, equal?
    debug('...', r)
    if (r.bitcoind.blockId === r.indexd.blockId) return fin()

    // Step 2, is indexd behind? [aka, does bitcoind have the indexd tip]
    rpcUtil.headerJSON(this.rpc, r.indexd.blockId, (err, common) => {
//        if (err && /not found/.test(err.message)) return fin(err) // uh, burn it to the ground
      if (err) return fin(err)

      // forked?
      if (common.confirmations === -1) {
        debug('forked')
        return this.disconnect(r.indexd.blockId, (err) => {
          if (err) return fin(err)

          this.resync(fin)
        })
      }

      // yes, indexd is behind
      debug('bitcoind is ahead')
      trySyncFrom(common.blockId, common.nextBlockId, common.confirmations)
    })
  })
}

Indexd.prototype.notify = function (txId, callback) {
  rpcUtil.transaction(this.rpc, txId, (err, tx) => {
    if (err) return callback(err)

    for (let indexName in this.indexes) {
      let index = this.indexes[indexName]

      if (!index.mempool) continue
      index.mempool(tx)
    }

    callback()
  })
}

// QUERIES
Indexd.prototype.blockIdByTransactionId = function (txId, callback) {
  this.indexes.tx.heightBy(this.db, txId, (err, height) => {
    if (err) return callback(err)
    if (height === -1) return callback()

    rpcUtil.blockIdAtHeight(this.rpc, height, callback)
  })
}

Indexd.prototype.latestFeesForNBlocks = function (nBlocks, callback) {
  this.indexes.fee.latestFeesFor(this.db, nBlocks, callback)
}

// returns whether (true/false) the script id has even been seen
Indexd.prototype.seenScriptId = function (scId, callback) {
  this.indexes.scripts.seenScriptId(this.db, scId, callback)
}

// returns a set of txIds with inputs/outputs from/to a { scId, heightRange }
Indexd.prototype.transactionIdsByScriptRange = function (scRange, dbLimit, callback) {
  this.txosByScriptId(scRange, dbLimit, (err, txos) => {
    if (err) return callback(err)

    let txIdSet = {}
    let tasks = txos.map((txo) => {
      txIdSet[txo.txId] = true
      return (next) => this.indexes.txin.txinBy(this.db, txo, next)
    })

    parallel(tasks, (err, txins) => {
      if (err) return callback(err)

      txins.forEach((txin) => {
        if (!txin) return
        txIdSet[txin.txId] = true
      })

      callback(null, txIdSet)
    })
  })
}

// returns a list of txos { txId, vout, height, value } by { scId, heightRange }
Indexd.prototype.txosByScriptRange = function (scRange, dbLimit, callback) {
  this.indexes.script.txosBy(this.db, scRange, dbLimit, callback)
}

// returns a txo { txId, vout, value, script }, by key { txId, vout }
Indexd.prototype.txoByTxo = function (txo, callback) {
  this.indexes.txo.txoBy(this.db, txo, callback)
}

// returns a list of (unspent) txos { txId, vout, height, value }, by { scId, heightRange }
// XXX: despite txo queries being bound by heightRange, the UTXO status is up-to-date
Indexd.prototype.utxosByScriptRange = function (scRange, dbLimit, callback) {
  this.txosByScriptRange(scRange, dbLimit, (err, txos) => {
    if (err) return callback(err)

    let taskMap = {}
    let unspentMap = {}

    txos.forEach((txo) => {
      let txoId = txoToString(txo)

      unspentMap[txoId] = txo
      taskMap[txoId] = (next) => this.indexes.txin.txinBy(this.db, txo, next)
    })

    parallel(taskMap, (err, txinMap) => {
      if (err) return callback(err)

      for (let txoId in txinMap) {
        let txin = txinMap[txoId]
        if (!txin) continue

        // has a txin, therefore spent
        delete unspentMap[txoId]
      }

      callback(null, unspentMap)
    })
  })
}

module.exports = Indexd
<|endoftext|>


<commit_before>var archiver = require( "archiver" );
var assert = require( "assert" );
var fs = require( "fs" );
var path = require( "path" );
var Q = require( "q" );

Q.longStackSupport = true;

function set( files, filepath, data ) {

	// String or Buffer.
	if ( typeof data === "string" || Buffer.isBuffer( data ) ) {
		files[ filepath ] = data;

	// Object containing (subpath, data) key-value pairs.
	} else if ( typeof data === "object" && data !== null ) {
		Object.keys( data ).forEach(function( subpath ) {
			set( files, path.join( filepath, subpath ), data[ subpath ] );
		});

	// null
	} else if ( data === null ) {
		return;
	} else {
		throw new Error( "Invalid type `" + typeof data + "` for `" + filepath + "` (String, Buffer or Object expected)." );
	}
}

function stopwatch( promise, stats, name ) {
	var start = new Date();
	stats[ name ] = stats[ name ] || {};
	promise.then(function() {
		stats[ name ].time = new Date() - start;
	});
}

function Packager( files, Package, runtimeVars, options ) {
	var builtFiles, cached, cacheKey, pkg, stats;
	var ready = Q.defer();

	assert( typeof files === "object", "Must include files object" );
	assert( typeof Package === "function", "Must include Package class" );
	if ( runtimeVars !== undefined ) {
		assert( typeof runtimeVars === "object", "Invalid runtimeVars type (object expected)" );
	}
	if ( runtimeVars !== undefined ) {
		assert( typeof options === "object", "Invalid options type (object expected)" );
	}

	options = options || {};

	// TODO: assert options.cache

	runtimeVars = runtimeVars || {};
	this.builtFiles = builtFiles = {};
	this.pkg = pkg = new Package( files, runtimeVars );
	this.ready = ready.promise;
	this.stats = stats = {};

	stopwatch( ready.promise, stats, "build" );

	if ( options.cache ) {
		cacheKey = Package.toString() +
			Object.keys(Package.prototype).join() +
			JSON.stringify( runtimeVars );

		if ( cached = options.cache.get( cacheKey ) ) {
			this.builtFiles = cached.builtFiles;
			return ready.resolve();

		} else {
			this.ready.then(function() {
				options.cache.set( cacheKey, {
					builtFiles: builtFiles
				});
			});
		}
	}

	assert( typeof pkg === "object", "Could not create Package instance" );

	pkg.files = files;
	pkg.runtime = runtimeVars;

	// Generate the build files (based on each Package method).
	Q.all( Object.keys( Package.prototype ).map(function( methodName ) {
		var deferred = Q.defer();
		var filepath = methodName;
		var method = pkg[ methodName ];

		var type = typeof method;

		stopwatch( deferred.promise, stats, filepath );

		// String (builtFile is a shallow copy of file whose path has been passed).
		if ( type === "string" ) {
			builtFiles[ filepath ] = files[ method ];
			deferred.resolve();

		// Async function.
		} else if ( type === "function" && method.length ) {
			pkg[ methodName ](function( error, data ) {
				if ( error ) {
					return deferred.reject( error );
				}
				try {
					set( builtFiles, filepath, data );
					deferred.resolve();
				} catch( error ) {
					deferred.reject( error );
				}
			});

		// Sync function.
		} else if ( type === "function" ) {
			try {
				set( builtFiles, filepath, pkg[ methodName ]() );
				deferred.resolve();
			} catch( error ) {
				deferred.reject( error );
			}

		// Unknown.
		} else {
			deferred.reject( new Error( "Invalid type `" + typeof method + "` for method `" + methodName + "`" ) );

		}

		return deferred.promise;

	})).then(function() {
		ready.resolve();

	}).catch(function( error ) {
		ready.reject( error );
	});
}

/**
 * .toJson( callback )
 *
 * @callback( error ) [ Function ]: callback function.
 */
Packager.prototype.toJson = function( callback ) {
	var files = this.builtFiles;
	this.ready.then(function() {
		callback( null, files );
	}).catch( callback );
};

/**
 * .toZip( target [, options], callback )
 *
 * @target [ Stream / String ]: The target stream, or the target filename (when string).
 *
 * @options [ Object ]:
 * - options.basedir: [ String ] Set the ZIP base directory.
 *
 * @callback( error ) [ Function ]: callback function.
 */
Packager.prototype.toZip = function( target, options, callback ) {
	var deferred = Q.defer();
	var files = this.builtFiles;
	var stats = this.stats;

	if ( arguments.length === 2 ) {
		callback = options;
		options = {};
	}

	this.ready.then(function() {
		var finishEvent = "finish",
			zip = archiver( "zip" );

		if ( options.basedir ) {
			files = Object.keys( files ).reduce(function( _files, filepath ) {
				_files[ path.join( options.basedir, filepath ) ] = files[ filepath ];
				return _files;
			}, {} );
		}

		stopwatch( deferred.promise, stats, "toZip" );

		if ( typeof target === "string" ) {
			target = fs.createWriteStream( target );
		}

		if ( typeof target.fd !== "undefined" ) {
			finishEvent = "close";
		}

		target.on( finishEvent, function() {
			stats.toZip.size = zip.pointer();
			deferred.resolve();
		});

		zip.on( "error", function( error ) {
			deferred.reject( error );
		});

		zip.pipe( target );

		Object.keys( files ).forEach(function( filepath ) {
			var data = files[ filepath ] || "";
			zip.append( data, { name: filepath } );
		});

		zip.finalize();
	}).catch( callback );

	deferred.promise.nodeify( callback );
};

module.exports = Packager;
<commit_msg>Don't confuse runtimeVars with options when checking options type<commit_after>var archiver = require( "archiver" );
var assert = require( "assert" );
var fs = require( "fs" );
var path = require( "path" );
var Q = require( "q" );

Q.longStackSupport = true;

function set( files, filepath, data ) {

	// String or Buffer.
	if ( typeof data === "string" || Buffer.isBuffer( data ) ) {
		files[ filepath ] = data;

	// Object containing (subpath, data) key-value pairs.
	} else if ( typeof data === "object" && data !== null ) {
		Object.keys( data ).forEach(function( subpath ) {
			set( files, path.join( filepath, subpath ), data[ subpath ] );
		});

	// null
	} else if ( data === null ) {
		return;
	} else {
		throw new Error( "Invalid type `" + typeof data + "` for `" + filepath + "` (String, Buffer or Object expected)." );
	}
}

function stopwatch( promise, stats, name ) {
	var start = new Date();
	stats[ name ] = stats[ name ] || {};
	promise.then(function() {
		stats[ name ].time = new Date() - start;
	});
}

function Packager( files, Package, runtimeVars, options ) {
	var builtFiles, cached, cacheKey, pkg, stats;
	var ready = Q.defer();

	assert( typeof files === "object", "Must include files object" );
	assert( typeof Package === "function", "Must include Package class" );
	if ( runtimeVars !== undefined ) {
		assert( typeof runtimeVars === "object", "Invalid runtimeVars type (object expected)" );
	}
	if ( options !== undefined ) {
		assert( typeof options === "object", "Invalid options type (object expected)" );
	}

	options = options || {};

	// TODO: assert options.cache

	runtimeVars = runtimeVars || {};
	this.builtFiles = builtFiles = {};
	this.pkg = pkg = new Package( files, runtimeVars );
	this.ready = ready.promise;
	this.stats = stats = {};

	stopwatch( ready.promise, stats, "build" );

	if ( options.cache ) {
		cacheKey = Package.toString() +
			Object.keys(Package.prototype).join() +
			JSON.stringify( runtimeVars );

		if ( cached = options.cache.get( cacheKey ) ) {
			this.builtFiles = cached.builtFiles;
			return ready.resolve();

		} else {
			this.ready.then(function() {
				options.cache.set( cacheKey, {
					builtFiles: builtFiles
				});
			});
		}
	}

	assert( typeof pkg === "object", "Could not create Package instance" );

	pkg.files = files;
	pkg.runtime = runtimeVars;

	// Generate the build files (based on each Package method).
	Q.all( Object.keys( Package.prototype ).map(function( methodName ) {
		var deferred = Q.defer();
		var filepath = methodName;
		var method = pkg[ methodName ];

		var type = typeof method;

		stopwatch( deferred.promise, stats, filepath );

		// String (builtFile is a shallow copy of file whose path has been passed).
		if ( type === "string" ) {
			builtFiles[ filepath ] = files[ method ];
			deferred.resolve();

		// Async function.
		} else if ( type === "function" && method.length ) {
			pkg[ methodName ](function( error, data ) {
				if ( error ) {
					return deferred.reject( error );
				}
				try {
					set( builtFiles, filepath, data );
					deferred.resolve();
				} catch( error ) {
					deferred.reject( error );
				}
			});

		// Sync function.
		} else if ( type === "function" ) {
			try {
				set( builtFiles, filepath, pkg[ methodName ]() );
				deferred.resolve();
			} catch( error ) {
				deferred.reject( error );
			}

		// Unknown.
		} else {
			deferred.reject( new Error( "Invalid type `" + typeof method + "` for method `" + methodName + "`" ) );

		}

		return deferred.promise;

	})).then(function() {
		ready.resolve();

	}).catch(function( error ) {
		ready.reject( error );
	});
}

/**
 * .toJson( callback )
 *
 * @callback( error ) [ Function ]: callback function.
 */
Packager.prototype.toJson = function( callback ) {
	var files = this.builtFiles;
	this.ready.then(function() {
		callback( null, files );
	}).catch( callback );
};

/**
 * .toZip( target [, options], callback )
 *
 * @target [ Stream / String ]: The target stream, or the target filename (when string).
 *
 * @options [ Object ]:
 * - options.basedir: [ String ] Set the ZIP base directory.
 *
 * @callback( error ) [ Function ]: callback function.
 */
Packager.prototype.toZip = function( target, options, callback ) {
	var deferred = Q.defer();
	var files = this.builtFiles;
	var stats = this.stats;

	if ( arguments.length === 2 ) {
		callback = options;
		options = {};
	}

	this.ready.then(function() {
		var finishEvent = "finish",
			zip = archiver( "zip" );

		if ( options.basedir ) {
			files = Object.keys( files ).reduce(function( _files, filepath ) {
				_files[ path.join( options.basedir, filepath ) ] = files[ filepath ];
				return _files;
			}, {} );
		}

		stopwatch( deferred.promise, stats, "toZip" );

		if ( typeof target === "string" ) {
			target = fs.createWriteStream( target );
		}

		if ( typeof target.fd !== "undefined" ) {
			finishEvent = "close";
		}

		target.on( finishEvent, function() {
			stats.toZip.size = zip.pointer();
			deferred.resolve();
		});

		zip.on( "error", function( error ) {
			deferred.reject( error );
		});

		zip.pipe( target );

		Object.keys( files ).forEach(function( filepath ) {
			var data = files[ filepath ] || "";
			zip.append( data, { name: filepath } );
		});

		zip.finalize();
	}).catch( callback );

	deferred.promise.nodeify( callback );
};

module.exports = Packager;
<|endoftext|>


<commit_before>const app = require('koa')()
const router = require('koa-router')()

const request = require('co-request')
const RSS = require('rss')
const { DOMParser, XMLSerializer } = require('xmldom')
const parser = new DOMParser({
	errorHandler: {}
})
const serlializer = new XMLSerializer()

const HOST_URL = 'https://github.com'

router
	.get('/', function *() {
		this.body = 'test'
	})
	.get('/:owner/:repo', function *() {
		const { owner, repo } = this.params
		const src = `${HOST_URL}/${owner}/${repo}/issues`

		const feed = new RSS({
			title: `${owner}/${repo}`,
			generator: 'gh-feed',
			feed_url: this.url,
			site_url: src,
			ttl: 60
		})

		const res = yield request(src)
		const doc = parser.parseFromString(res.body, 'text/html')
		const issues = Array.from(doc.getElementsByTagName('ul')[1].getElementsByTagName('li'))
			.map(li => {
				const issue = {}
				Array.from(li.getElementsByTagName('a')).forEach(a => {
					const className = a.getAttribute('class')
					if (/h4/.test(className)) {
						issue.title = a.textContent.trim()
						issue.url = HOST_URL + a.getAttribute('href')
					} else if (/label/.test(className)) {
						issue.categories = issue.categories || []
						issue.categories.push(a.textContent.trim())
					} else if (className === 'tooltipped tooltipped-s muted-link') {
						issue.author = a.textContent.trim()
					}
				})
				issue.date = li.getElementsByTagName('relative-time')[0].getAttribute('datetime')
				return issue
			})

		yield issues.map(issue => {
			return function *() {
				const res = yield request(issue.url)
				const body = res.body.replace(/<!-- '"` --><!-- <\/textarea><\/xmp> --><\/option><\/form>/g, '')
				const id = body.match(/id="(issue-.*?)"/)[1]
				const doc = parser.parseFromString(body, 'text/html')
				const contentElement = doc
					.getElementById(id)
					.childNodes[3]
					.childNodes[1]
					.childNodes
				issue.description = serlializer.serializeToString(contentElement)
				feed.item(issue)
			}
		})
		this.set('Content-Type', 'application/rss+xml; charset=utf-8')
		this.body = feed.xml()
	})

app.use(router.routes())

app.listen(process.env.PORT || 3000)<commit_msg>add feed avatar<commit_after>const app = require('koa')()
const router = require('koa-router')()

const request = require('co-request')
const RSS = require('rss')
const { DOMParser, XMLSerializer } = require('xmldom')
const parser = new DOMParser({
	errorHandler: {}
})
const serlializer = new XMLSerializer()

const HOST_URL = 'https://github.com'

router
	.get('/', function *() {
		this.body = 'test'
	})
	.get('/:owner/:repo', function *() {
		const { owner, repo } = this.params
		const src = `${HOST_URL}/${owner}/${repo}/issues`

		const feed = new RSS({
			title: `${owner}/${repo}`,
			generator: 'gh-feed',
			feed_url: this.url,
			site_url: src,
			image_url: `${HOST_URL}/${owner}.png`,
			ttl: 60
		})

		const res = yield request(src)
		const doc = parser.parseFromString(res.body, 'text/html')
		const issues = Array.from(doc.getElementsByTagName('ul')[1].getElementsByTagName('li'))
			.map(li => {
				const issue = {}
				Array.from(li.getElementsByTagName('a')).forEach(a => {
					const className = a.getAttribute('class')
					if (/h4/.test(className)) {
						issue.title = a.textContent.trim()
						issue.url = HOST_URL + a.getAttribute('href')
					} else if (/label/.test(className)) {
						issue.categories = issue.categories || []
						issue.categories.push(a.textContent.trim())
					} else if (className === 'tooltipped tooltipped-s muted-link') {
						issue.author = a.textContent.trim()
					}
				})
				issue.date = li.getElementsByTagName('relative-time')[0].getAttribute('datetime')
				return issue
			})

		yield issues.map(issue => {
			return function *() {
				const res = yield request(issue.url)
				const body = res.body.replace(/<!-- '"` --><!-- <\/textarea><\/xmp> --><\/option><\/form>/g, '')
				const id = body.match(/id="(issue-.*?)"/)[1]
				const doc = parser.parseFromString(body, 'text/html')
				const contentElement = doc
					.getElementById(id)
					.childNodes[3]
					.childNodes[1]
					.childNodes
				issue.description = serlializer.serializeToString(contentElement)
				feed.item(issue)
			}
		})
		this.set('Content-Type', 'application/rss+xml; charset=utf-8')
		this.body = feed.xml()
	})

app.use(router.routes())

app.listen(process.env.PORT || 3000)<|endoftext|>


<commit_before>var vmModule = require("./main-view-model");
var observableModule = require("data/observable");
var mapsModule = require("nativescript-google-maps-sdk");
var Image = require("ui/image").Image;
var imageSource = require("image-source");

function wait(milliSeconds) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
           resolve(milliSeconds);
        }, milliSeconds);
    });
}

function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = vmModule.mainViewModel;
}
exports.pageLoaded = pageLoaded;

function onMapReady(args) {
    console.log("onMapReady");

    var mapView = args.object;

    console.log("Setting a marker...");

    var marker = new mapsModule.Marker();
    marker.position = mapsModule.Position.positionFromLatLng(-33.86, 151.20);
    marker.title = "Sydney";
    marker.snippet = "Australia";
    marker.userData = { index : 1};
    mapView.addMarker(marker);

    marker = new mapsModule.Marker();
    marker.position = mapsModule.Position.positionFromLatLng(-33.42, 151.32);
    marker.title = "Gosford";
    marker.snippet = "Australia";
    var icon = new Image();
    icon.imageSource = imageSource.fromResource('icon');
    marker.icon = icon;
    // marker.icon = 'icon';
    marker.alpha = 0.8;
    marker.flat = true;
    marker.draggable = true;
    marker.visible = false;
    marker.userData = { index : 2};
    mapView.addMarker(marker);

    wait(3000).then(function() {
        var marker2 = mapView.findMarker(function(marker) { return marker.userData.index === 2; });
        console.log("Moving marker...", marker2.userData);
        marker2.position = mapsModule.Position.positionFromLatLng(-33.33,151.08);
        marker2.rotation = 45;
        return wait(3000);
    }).then(function() {
        var marker2 = mapView.findMarker(function(marker) { return marker.userData.index === 2; });
        marker2.visible = true;
        return wait(3000);
    }).then(function() {
        var marker2 = mapView.findMarker(function(marker) { return marker.userData.index === 2; });
        marker.position = mapsModule.Position.positionFromLatLng(-32.89,151.44);
        return wait(3000);
    }).then(function() {
        var marker1 = mapView.findMarker(function(marker) { return marker.userData.index === 1; });
        console.log("Removing marker...", marker1.userData);
        mapView.removeMarker(marker1);
        return wait(3000);
    }).then(function() {
        console.log("Removing all markers...");
        mapView.removeAllMarkers();
    }).catch(function(error){
      console.log(error);
    });

}

function onMarkerSelect(args) {
   console.log("Clicked on "+args.marker.title);
}

var lastCamera = null;
function onCameraChanged(args) {
    console.log("Camera changed: "+JSON.stringify(args.camera), JSON.stringify(args.camera) === lastCamera);
    lastCamera = JSON.stringify(args.camera);
}

exports.onMapReady = onMapReady;
exports.onMarkerSelect = onMarkerSelect;
exports.onCameraChanged = onCameraChanged;
<commit_msg>Adding zooming to example<commit_after>var vmModule = require("./main-view-model");
var observableModule = require("data/observable");
var mapsModule = require("nativescript-google-maps-sdk");
var Image = require("ui/image").Image;
var imageSource = require("image-source");

function wait(milliSeconds) {
    return new Promise(function(resolve, reject) {
        setTimeout(function(){
           resolve(milliSeconds);
        }, milliSeconds);
    });
}

function pageLoaded(args) {
    var page = args.object;
    page.bindingContext = vmModule.mainViewModel;
}
exports.pageLoaded = pageLoaded;

function onMapReady(args) {
    console.log("onMapReady");

    var mapView = args.object;

    console.log("Setting a marker...");

    var marker = new mapsModule.Marker();
    marker.position = mapsModule.Position.positionFromLatLng(-33.86, 151.20);
    marker.title = "Sydney";
    marker.snippet = "Australia";
    marker.userData = { index : 1};
    mapView.addMarker(marker);

    marker = new mapsModule.Marker();
    marker.position = mapsModule.Position.positionFromLatLng(-33.42, 151.32);
    marker.title = "Gosford";
    marker.snippet = "Australia";
    var icon = new Image();
    icon.imageSource = imageSource.fromResource('icon');
    marker.icon = icon;
    // marker.icon = 'icon';
    marker.alpha = 0.8;
    marker.flat = true;
    marker.draggable = true;
    marker.visible = false;
    marker.userData = { index : 2};
    mapView.addMarker(marker);

    wait(3000).then(function() {
        var marker2 = mapView.findMarker(function(marker) { return marker.userData.index === 2; });
        console.log("Moving marker...", marker2.userData);
        marker2.position = mapsModule.Position.positionFromLatLng(-33.33,151.08);
        marker2.rotation = 45;
        return wait(3000);
    }).then(function() {
        vmModule.mainViewModel.set("zoom", 10);
        console.log("Zooming in...", vmModule.mainViewModel);
        return wait(3000);
    }).then(function() {
        var marker2 = mapView.findMarker(function(marker) { return marker.userData.index === 2; });
        marker2.visible = true;
        return wait(3000);
    }).then(function() {
        var marker2 = mapView.findMarker(function(marker) { return marker.userData.index === 2; });
        marker.position = mapsModule.Position.positionFromLatLng(-32.89,151.44);
        return wait(3000);
    }).then(function() {
        var marker1 = mapView.findMarker(function(marker) { return marker.userData.index === 1; });
        console.log("Removing marker...", marker1.userData);
        mapView.removeMarker(marker1);
        return wait(3000);
    }).then(function() {
        console.log("Removing all markers...");
        mapView.removeAllMarkers();
    }).catch(function(error){
      console.log(error);
    });

}

function onMarkerSelect(args) {
   console.log("Clicked on "+args.marker.title);
}

var lastCamera = null;
function onCameraChanged(args) {
    console.log("Camera changed: "+JSON.stringify(args.camera), JSON.stringify(args.camera) === lastCamera);
    lastCamera = JSON.stringify(args.camera);
}

exports.onMapReady = onMapReady;
exports.onMarkerSelect = onMarkerSelect;
exports.onCameraChanged = onCameraChanged;
<|endoftext|>


<commit_before>/**
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of frb-cosmos-proxy.
 *
 * frb-cosmos-proxy is free software: you can redistribute it and/or modify it under the terms of the GNU Affero
 * General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * frb-cosmos-proy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with frb-cosmos-proxy. If not, see
 * http://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License please contact with
 * francisco dot romerobueno at telefonica dot com
 */

/**
 * Author: pcoello25
 */

var fs = require('fs'),
    cache = JSON.parse("[]"),
    pathToFile = require('../conf/cosmos-proxy.json').cache_file;

function createEmptyFileCache() {
    return fs.closeSync(fs.openSync(pathToFile, 'w'));
} // createEmptyCache

function isCacheEmpty() {
    return (cache == null || cache.length == undefined || cache.length == 0);
} // isCacheEmpty

function isCacheAuthenticated(reqUser, token) {
    if (!isCacheEmpty()) {
        for (var i = 0; i < cache.length; i++) {
            if (cache[i].user === reqUser) {
                if (cache[i].token === token) {
                    return 1; // 1
                } else {
                    return 2; // 2
                } // if else
            } // if
        } // for
        return 0;
    } else {
        return 0;
    } // if else
} // isCacheAuthenticated

function updateCacheFile() {
    fs.writeFileSync(pathToFile, JSON.stringify(cache), 'utf-8');
} // updateFileCache

function loadCacheData() {
    cache = JSON.parse(fs.readFileSync(pathToFile, 'utf8'));
} // loadCacheData

function pushNewEntry(newValue) {
    cache.push(newValue);
    updateCacheFile(pathToFile);
} // pushNewEntry

function updateEntry(user, token) {
    for (var i = 0; i < cache.length; i++) {
        if (cache[i].user === user) {
            cache[i].token = token;
            updateCacheFile(pathToFile);
            break;
        } // if
    } // for
} // updateEntry

function toString() {
    return JSON.stringify(cache);
} // toString

module.exports = {
    isCacheAuthenticated: isCacheAuthenticated,
    createEmptyFileCache: createEmptyFileCache,
    loadCacheData: loadCacheData,
    isCacheEmpty: isCacheEmpty,
    pushNewEntry: pushNewEntry,
    updateEntry: updateEntry,
    toString: toString
} // module.exports

<commit_msg>[198][cosmos-proxy][cache.js] Fix issues raised during code review<commit_after>/**
 * Copyright 2016 Telefonica Investigación y Desarrollo, S.A.U
 *
 * This file is part of frb-cosmos-proxy.
 *
 * frb-cosmos-proxy is free software: you can redistribute it and/or modify it under the terms of the GNU Affero
 * General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * frb-cosmos-proy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Affero General Public License along with frb-cosmos-proxy. If not, see
 * http://www.gnu.org/licenses/.
 *
 * For those usages not covered by the GNU Affero General Public License please contact with
 * francisco dot romerobueno at telefonica dot com
 */

/**
 * Author: pcoello25
 */

var fs = require('fs'),
    cache = JSON.parse("[]"),
    pathToFile = require('../conf/cosmos-proxy.json').cache_file;

function createEmptyFileCache() {
    return fs.closeSync(fs.openSync(pathToFile, 'w'));
} // createEmptyCache

function isCacheEmpty() {
    return (cache == null || cache.length == undefined || cache.length == 0);
} // isCacheEmpty

function isCacheAuthenticated(reqUser, token) {
    if (!isCacheEmpty()) {
        for (var i = 0; i < cache.length; i++) {
            if (cache[i].user === reqUser) {
                if (cache[i].token === token) {
                    return 1;
                } else {
                    return 2;
                } // if else
            } // if
        } // for
        return 0;
    } else {
        return 0;
    } // if else
} // isCacheAuthenticated

function updateCacheFile() {
    fs.writeFileSync(pathToFile, JSON.stringify(cache), 'utf8');
} // updateFileCache

function loadCacheData() {
    cache = JSON.parse(fs.readFileSync(pathToFile, 'utf8'));
} // loadCacheData

function pushNewEntry(newValue) {
    cache.push(newValue);
    updateCacheFile(pathToFile);
} // pushNewEntry

function updateEntry(user, token) {
    for (var i = 0; i < cache.length; i++) {
        if (cache[i].user === user) {
            cache[i].token = token;
            updateCacheFile(pathToFile);
            break;
        } // if
    } // for
} // updateEntry

function toString() {
    return JSON.stringify(cache);
} // toString

module.exports = {
    isCacheAuthenticated: isCacheAuthenticated,
    createEmptyFileCache: createEmptyFileCache,
    loadCacheData: loadCacheData,
    isCacheEmpty: isCacheEmpty,
    pushNewEntry: pushNewEntry,
    updateEntry: updateEntry,
    toString: toString
} // module.exports

<|endoftext|>


<commit_before>user_pref("general.useragent.updates.enabled", true);
user_pref("gfx.compositor.external-window", true);
user_pref("gfx.compositor.clear-context", false);
user_pref("embedlite.compositor.external_gl_context", true);
<commit_msg>Fix default preferences<commit_after>user_pref("general.useragent.updates.enabled", true);
user_pref("gfx.compositor.external-window", true);
user_pref("gfx.compositor.clear-context", false);
user_pref("embedlite.compositor.external_gl_context", true);
user_pref("apz.allow_zooming", true);
user_pref("dom.meta-viewport.enabled", true);
<|endoftext|>


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

process.title = "pagediff";

const join = require("path").join;
const fs = require("fs");
const Pageres = require("pageres");
const bd = require("blink-diff");
const slugify = require("slugify-url");
const ImageJS = require("imagejs");

const img = join(__dirname, "img");
function imgPath(name) { return join(img, slugify(name)) + ".png"; }
function imgUrl(name) { return "img/" + slugify(name) + ".png"; }

module.exports = function pagediff(a, b, w, h, delay) {
  return new Promise(function(resolve, reject) {
    const dims = w + "x" + h;
    Promise.all([
      new Pageres({delay: delay, crop: true, filename: slugify(a)}).src(a, [dims]).dest(img).run().catch(function() {
        return createBlankPng(imgPath(a), w, h);
      }),
      new Pageres({delay: delay, crop: true, filename: slugify(b)}).src(b, [dims]).dest(img).run().catch(function() {
        return createBlankPng(imgPath(a), w, h);
      }),
    ]).then(function() {
      return new Promise((resolve, reject) => {
        (new bd({
          imageAPath: imgPath(a),
          imageBPath: imgPath(b),
          imageOutputPath: imgPath(a + "-" + b),
          outputBackgroundBlue: 255,
          outputBackgroundGreen: 255,
          outputBackgroundOpacity: .85,
          outputBackgroundRed: 255,
          composition: false,
          delta: 0,
          threshold: 0,
        })).run((err, res) => {
          if (err) return reject(err);
          resolve(Math.round((res.differences / res.dimension) * 1e4) / 1e2);
        });
      });
    }).then(perc => {
      resolve({
        a: imgUrl(a),
        b: imgUrl(b),
        diff: imgUrl(a + "-" + b),
        perc: perc,
      });
    }).catch(err => {
      console.error(err);
      reject({err: err});
    });
  });
};

function createBlankPng(path, w, h) {
  return new Promise(function(resolve) {
    var ws = fs.createWriteStream(path);
    var bitmap = new ImageJS.Bitmap({width: w, height: h, color: {r: 255, g: 255, b: 255, a: 255}});
    bitmap.write(ws, {type: ImageJS.ImageType.PNG}).then(resolve);
  });
}
<commit_msg>fix blank b image generation<commit_after>#!/usr/bin/env node
"use strict";

process.title = "pagediff";

const join = require("path").join;
const fs = require("fs");
const Pageres = require("pageres");
const bd = require("blink-diff");
const slugify = require("slugify-url");
const ImageJS = require("imagejs");

const img = join(__dirname, "img");
function imgPath(name) { return join(img, slugify(name)) + ".png"; }
function imgUrl(name) { return "img/" + slugify(name) + ".png"; }

module.exports = function pagediff(a, b, w, h, delay) {
  return new Promise(function(resolve, reject) {
    const dims = w + "x" + h;
    Promise.all([
      new Pageres({delay: delay, crop: true, filename: slugify(a)}).src(a, [dims]).dest(img).run().catch(function() {
        return createBlankPng(imgPath(a), w, h);
      }),
      new Pageres({delay: delay, crop: true, filename: slugify(b)}).src(b, [dims]).dest(img).run().catch(function() {
        return createBlankPng(imgPath(b), w, h);
      }),
    ]).then(function() {
      return new Promise((resolve, reject) => {
        (new bd({
          imageAPath: imgPath(a),
          imageBPath: imgPath(b),
          imageOutputPath: imgPath(a + "-" + b),
          outputBackgroundBlue: 255,
          outputBackgroundGreen: 255,
          outputBackgroundOpacity: .85,
          outputBackgroundRed: 255,
          composition: false,
          delta: 0,
          threshold: 0,
        })).run((err, res) => {
          if (err) return reject(err);
          resolve(Math.round((res.differences / res.dimension) * 1e4) / 1e2);
        });
      });
    }).then(perc => {
      resolve({
        a: imgUrl(a),
        b: imgUrl(b),
        diff: imgUrl(a + "-" + b),
        perc: perc,
      });
    }).catch(err => {
      console.error(err);
      reject({err: err});
    });
  });
};

function createBlankPng(path, w, h) {
  return new Promise(function(resolve) {
    var ws = fs.createWriteStream(path);
    var bitmap = new ImageJS.Bitmap({width: w, height: h, color: {r: 255, g: 255, b: 255, a: 255}});
    bitmap.write(ws, {type: ImageJS.ImageType.PNG}).then(resolve);
  });
}
<|endoftext|>


<commit_before>/**
 * Mobile Browser Address-bar Resize Jump Fix.
 * Prevent elements from jumping if the mobile browser address bar appears/disappears.
 *
 * Usage:
 *
 * just import the script like this:
 *    import jumpFix from 'addressBarJumpFix';
 *
 * then call this function after all elements are finished loading:
 *    addressBarJumpFix.init();
 *
 * Now put the following data attribute on each html element that has a height depending on the
 * vp height:
 *    data-jump-fix='true'
 *
 */

class JumpFix {

  constructor() {
    this.items = null;
    this.isScrolling = false;
    this.timeoutScroll = null;
  }

  // This sets 'isScrolling' to true for a certain amount of time.
  setScrollingStatus() {
    if (this.timeoutScroll)
      clearTimeout(this.timeoutScroll);
    this.isScrolling = true;
    this.timeoutScroll = setTimeout(() => {
      this.isScrolling = false;
    }, 100);
  }

  // Save actual heights
  saveHeights() {
    this.items.forEach((item, i) => {
      this.items[i].savedHeight = item.clientHeight;
    });
  }

  // On resize: if user is scrolling use the saved height.
// if user is NOT scrolling save the new nativ height after resize.
  handleResize() {
    if (this.isScrolling === true) {
      this.items.forEach((item, i) => {
        this.items[i].style.height = `${this.items[i].savedHeight}px`;
      });
    } else {
      this.items.forEach((item, i) => {
        this.items[i].style.height = '';
      });
    }
  }

  handleScroll() {
    if (this.isScrolling === false)
      this.saveHeights();
    this.setScrollingStatus();
  }

// Bind Events
  addListeners() {
    window.addEventListener('scroll', this.handleScroll.bind(this));
    window.addEventListener('touchmove', this.handleScroll.bind(this));
    window.addEventListener('resize', this.handleResize.bind(this));
  }

  init() {
    this.items = document.querySelectorAll('[data-jump-fix="true"]');
    this.addListeners();
  }
}

export default new JumpFix();
<commit_msg>changed comment<commit_after>/**
 * Mobile Browser Address-bar Resize Jump Fix.
 * Prevent elements from jumping if the mobile browser address bar appears/disappears.
*/

class JumpFix {

  constructor() {
    this.items = null;
    this.isScrolling = false;
    this.timeoutScroll = null;
  }

  // This sets 'isScrolling' to true for a certain amount of time.
  setScrollingStatus() {
    if (this.timeoutScroll)
      clearTimeout(this.timeoutScroll);
    this.isScrolling = true;
    this.timeoutScroll = setTimeout(() => {
      this.isScrolling = false;
    }, 100);
  }

  // Save actual heights
  saveHeights() {
    this.items.forEach((item, i) => {
      this.items[i].savedHeight = item.clientHeight;
    });
  }

  // On resize: if user is scrolling use the saved height.
// if user is NOT scrolling save the new nativ height after resize.
  handleResize() {
    if (this.isScrolling === true) {
      this.items.forEach((item, i) => {
        this.items[i].style.height = `${this.items[i].savedHeight}px`;
      });
    } else {
      this.items.forEach((item, i) => {
        this.items[i].style.height = '';
      });
    }
  }

  handleScroll() {
    if (this.isScrolling === false)
      this.saveHeights();
    this.setScrollingStatus();
  }

// Bind Events
  addListeners() {
    window.addEventListener('scroll', this.handleScroll.bind(this));
    window.addEventListener('touchmove', this.handleScroll.bind(this));
    window.addEventListener('resize', this.handleResize.bind(this));
  }

  init() {
    this.items = document.querySelectorAll('[data-jump-fix="true"]');
    this.addListeners();
  }
}

export default new JumpFix();
<|endoftext|>


<commit_before>var _ = require('underscore');

var userConfig = require(process.env.HOME + '/.partyplayConfig.js');
var defaultConfig = require(__dirname + '/partyplayConfigDefaults.js');
var config = _.defaults(userConfig, defaultConfig);

var player = {
    config: config,
    queue: [],
    nowPlaying: null,
    plugins: {},
    backends: {}
}

// call hook function in all modules
// if any hooks return a truthy value, it is an error and we abort
// be very careful with calling hooks from within a hook, infinite loops are possible
var callHooks = function(hook, argv) {
    // _.find() used instead of _.each() because we want to break out as soon
    // as a hook returns a truthy value (used to indicate an error, e.g. in form
    // of a string)
    return _.find(player.plugins, function(plugin) {
        if(plugin[hook]) {
            return plugin[hook].apply(null, argv);
        }
    });
};
player.callHooks = callHooks;

// returns number of hook functions attached to given hook
var numHooks = function(hook) {
    var cnt = 0;

    _.find(player.plugins, function(plugin) {
        if(plugin[hook]) {
            cnt++;
        }
    });

    return cnt;
};
player.numHooks = numHooks;

// to be called whenever the queue has been modified
// this function will:
// - play back the first song in the queue if no song is playing
// - prepare first and second songs in the queue
var onQueueModify = function() {
    if(!player.queue.length) {
        callHooks('onEndOfQueue', [player]);
        console.log('end of queue, waiting for more songs');
        return;
    }

    var startPlayingNext = false;
    if(!player.nowPlaying) {
        // play song
        player.nowPlaying = player.queue.shift();
        removeFromQueue(player.nowPlaying.backendName, player.nowPlaying.songID);
        startPlayingNext = true;
    }

    // TODO: error handling if backends[...] is undefined
    // prepare now playing song
    player.backends[player.nowPlaying.backendName].prepareSong(player.nowPlaying.songID, function() {
        callHooks('onSongPrepared', [player]);

        if(startPlayingNext) {
            console.log('playing song: ' + player.nowPlaying.songID);

            player.nowPlaying.playbackStart = new Date();

            callHooks('onSongChange', [player]);

            var songTimeout = parseInt(player.nowPlaying.duration) + config.songDelayMs;
            setTimeout(function() {
                console.log('end of song ' + player.nowPlaying.songID);
                callHooks('onSongEnd', [player]);

                player.nowPlaying = null;
                onQueueModify();
            }, songTimeout);
        }

        // TODO: support pre-caching multiple songs at once if configured so
        // prepare next song(s) in queue
        if(player.queue.length) {
            player.backends[player.queue[0].backendName].prepareSong(player.queue[0].songID, function() {
                callHooks('onNextSongPrepared', [player, 0]);
                // do nothing
            }, function(err) {
                // error pre-caching, get rid of this song
                console.log('error! removing song from queue ' + player.queue[0].songID);
                callHooks('onNextSongPrepareError', [player, 0]);
                removeFromQueue(player.queue[0].backendName, player.queue[0].songID);
            });
        } else {
            console.log('no songs in queue to prepare');
            callHooks('onNothingToPrepare', [player]);
        }
    }, function(err) {
        // error pre-caching, get rid of this song
        console.log('error! removing song from queue ' + player.nowPlaying.songID);
        callHooks('onSongPrepareError', [player]);
        removeFromQueue(player.nowPlaying.backendName, player.nowPlaying.songID);
    });
};
player.onQueueModify = onQueueModify;

// find song from queue
var searchQueue = function(backendName, songID) {
    for(var i = 0; i < player.queue.length; i++) {
        if(player.queue[i].songID === songID
                && player.queue[i].backendName === backendName)
            return player.queue[i];
    }

    if(player.nowPlaying && player.nowPlaying.songID === songID
            && player.nowPlaying.backendName === backendName)
        return player.nowPlaying;

    return null;
};
player.searchQueue = searchQueue;

// get rid of song in queue
var removeFromQueue = function(backendName, songID) {
    for(var i = 0; i < player.queue.length; i++) {
        if(player.queue[i].songID === songID && player.queue[i].backendName === backendName) {
            player.queue.splice(i, 1);
            return;
        }
    }
};
player.removeFromQueue = removeFromQueue;

// TODO: partyplay specific stuff
// initialize song object
var initializeSong = function(song) {
    song.playbackStart = null;
    song.timeAdded = new Date().getTime();

    player.queue.push(song);
    return song;
};
player.initializeSong = initializeSong;

// add a song to the queue
//
// metadata is optional and can contain information passed between plugins
// (e.g. which user added a song)
var addToQueue = function(song, metadata) {
    // check that required fields are provided
    if(!song.title || !song.songID || !song.backendName || !song.duration) {
        console.log('required song fields not provided: ' + song.songID);
        return 'required song fields not provided';
    }

    // if same song is already queued, don't create a duplicate
    var queuedSong = searchQueue(song.backendName, song.songID);
    if(queuedSong) {
        console.log('not adding duplicate song to queue: ' + queuedSong.songID);
        return 'duplicate songID';
    }

    var err = callHooks('preSongQueued', [player, song, metadata]);
    if(err)
        return err;

    // no duplicate found, initialize a few properties of song
    queuedSong = initializeSong(song);

    callHooks('sortQueue', [player, metadata]);
    onQueueModify();

    console.log('added song to queue: ' + queuedSong.songID);
    callHooks('postSongQueued', [player, queuedSong, metadata]);
};
player.addToQueue = addToQueue;

_.each(config.plugins, function(pluginName) {
    // TODO: put plugin modules into npm
    // must implement .init, can implement hooks
    var plugin = require('./plugins/' + pluginName);

    plugin.init(player, function() {
        player.plugins[pluginName] = plugin;
        console.log('plugin ' + pluginName + ' initialized');
    }, function(err) {
        console.log('error in ' + pluginName + ': ' + err);
        callHooks('onPluginInitError', [player, plugin]);
    });
});

// TODO: maybe wait for callbacks before this?
callHooks('onPluginsInitialized', [player]);

// init backends
_.each(config.backends, function(backendName) {
    // TODO: put backend modules into npm
    // must implement .search, .prepareSong, .init
    var backend = require('./backends/' + backendName);

    backend.init(player, function() {
        player.backends[backendName] = backend;

        console.log('backend ' + backendName + ' initialized');
        callHooks('onBackendInit', [player, backend]);
    }, function(err) {
        console.log('error in ' + backendName + ': ' + err);
        callHooks('onBackendInitError', [player, backend]);
    });
});

// TODO: maybe wait for callbacks before this?
callHooks('onBackendsInitialized', [player]);
<commit_msg>ignore exceptions for now...<commit_after>var _ = require('underscore');

var userConfig = require(process.env.HOME + '/.partyplayConfig.js');
var defaultConfig = require(__dirname + '/partyplayConfigDefaults.js');
var config = _.defaults(userConfig, defaultConfig);

var player = {
    config: config,
    queue: [],
    nowPlaying: null,
    plugins: {},
    backends: {}
}

// call hook function in all modules
// if any hooks return a truthy value, it is an error and we abort
// be very careful with calling hooks from within a hook, infinite loops are possible
var callHooks = function(hook, argv) {
    // _.find() used instead of _.each() because we want to break out as soon
    // as a hook returns a truthy value (used to indicate an error, e.g. in form
    // of a string)
    return _.find(player.plugins, function(plugin) {
        if(plugin[hook]) {
            return plugin[hook].apply(null, argv);
        }
    });
};
player.callHooks = callHooks;

// returns number of hook functions attached to given hook
var numHooks = function(hook) {
    var cnt = 0;

    _.find(player.plugins, function(plugin) {
        if(plugin[hook]) {
            cnt++;
        }
    });

    return cnt;
};
player.numHooks = numHooks;

// to be called whenever the queue has been modified
// this function will:
// - play back the first song in the queue if no song is playing
// - prepare first and second songs in the queue
var onQueueModify = function() {
    if(!player.queue.length) {
        callHooks('onEndOfQueue', [player]);
        console.log('end of queue, waiting for more songs');
        return;
    }

    var startPlayingNext = false;
    if(!player.nowPlaying) {
        // play song
        player.nowPlaying = player.queue.shift();
        removeFromQueue(player.nowPlaying.backendName, player.nowPlaying.songID);
        startPlayingNext = true;
    }

    // TODO: error handling if backends[...] is undefined
    // prepare now playing song
    player.backends[player.nowPlaying.backendName].prepareSong(player.nowPlaying.songID, function() {
        callHooks('onSongPrepared', [player]);

        if(startPlayingNext) {
            console.log('playing song: ' + player.nowPlaying.songID);

            player.nowPlaying.playbackStart = new Date();

            callHooks('onSongChange', [player]);

            var songTimeout = parseInt(player.nowPlaying.duration) + config.songDelayMs;
            setTimeout(function() {
                console.log('end of song ' + player.nowPlaying.songID);
                callHooks('onSongEnd', [player]);

                player.nowPlaying = null;
                onQueueModify();
            }, songTimeout);
        }

        // TODO: support pre-caching multiple songs at once if configured so
        // prepare next song(s) in queue
        if(player.queue.length) {
            player.backends[player.queue[0].backendName].prepareSong(player.queue[0].songID, function() {
                callHooks('onNextSongPrepared', [player, 0]);
                // do nothing
            }, function(err) {
                // error pre-caching, get rid of this song
                console.log('error! removing song from queue ' + player.queue[0].songID);
                callHooks('onNextSongPrepareError', [player, 0]);
                removeFromQueue(player.queue[0].backendName, player.queue[0].songID);
            });
        } else {
            console.log('no songs in queue to prepare');
            callHooks('onNothingToPrepare', [player]);
        }
    }, function(err) {
        // error pre-caching, get rid of this song
        console.log('error! removing song from queue ' + player.nowPlaying.songID);
        callHooks('onSongPrepareError', [player]);
        removeFromQueue(player.nowPlaying.backendName, player.nowPlaying.songID);
    });
};
player.onQueueModify = onQueueModify;

// find song from queue
var searchQueue = function(backendName, songID) {
    for(var i = 0; i < player.queue.length; i++) {
        if(player.queue[i].songID === songID
                && player.queue[i].backendName === backendName)
            return player.queue[i];
    }

    if(player.nowPlaying && player.nowPlaying.songID === songID
            && player.nowPlaying.backendName === backendName)
        return player.nowPlaying;

    return null;
};
player.searchQueue = searchQueue;

// get rid of song in queue
var removeFromQueue = function(backendName, songID) {
    for(var i = 0; i < player.queue.length; i++) {
        if(player.queue[i].songID === songID && player.queue[i].backendName === backendName) {
            player.queue.splice(i, 1);
            return;
        }
    }
};
player.removeFromQueue = removeFromQueue;

// TODO: partyplay specific stuff
// initialize song object
var initializeSong = function(song) {
    song.playbackStart = null;
    song.timeAdded = new Date().getTime();

    player.queue.push(song);
    return song;
};
player.initializeSong = initializeSong;

// add a song to the queue
//
// metadata is optional and can contain information passed between plugins
// (e.g. which user added a song)
var addToQueue = function(song, metadata) {
    // check that required fields are provided
    if(!song.title || !song.songID || !song.backendName || !song.duration) {
        console.log('required song fields not provided: ' + song.songID);
        return 'required song fields not provided';
    }

    // if same song is already queued, don't create a duplicate
    var queuedSong = searchQueue(song.backendName, song.songID);
    if(queuedSong) {
        console.log('not adding duplicate song to queue: ' + queuedSong.songID);
        return 'duplicate songID';
    }

    var err = callHooks('preSongQueued', [player, song, metadata]);
    if(err)
        return err;

    // no duplicate found, initialize a few properties of song
    queuedSong = initializeSong(song);

    callHooks('sortQueue', [player, metadata]);
    onQueueModify();

    console.log('added song to queue: ' + queuedSong.songID);
    callHooks('postSongQueued', [player, queuedSong, metadata]);
};
player.addToQueue = addToQueue;

_.each(config.plugins, function(pluginName) {
    // TODO: put plugin modules into npm
    // must implement .init, can implement hooks
    var plugin = require('./plugins/' + pluginName);

    plugin.init(player, function() {
        player.plugins[pluginName] = plugin;
        console.log('plugin ' + pluginName + ' initialized');
    }, function(err) {
        console.log('error in ' + pluginName + ': ' + err);
        callHooks('onPluginInitError', [player, plugin]);
    });
});

// TODO: maybe wait for callbacks before this?
callHooks('onPluginsInitialized', [player]);

// init backends
_.each(config.backends, function(backendName) {
    // TODO: put backend modules into npm
    // must implement .search, .prepareSong, .init
    var backend = require('./backends/' + backendName);

    backend.init(player, function() {
        player.backends[backendName] = backend;

        console.log('backend ' + backendName + ' initialized');
        callHooks('onBackendInit', [player, backend]);
    }, function(err) {
        console.log('error in ' + backendName + ': ' + err);
        callHooks('onBackendInitError', [player, backend]);
    });
});

// TODO: maybe wait for callbacks before this?
callHooks('onBackendsInitialized', [player]);

process.on('uncaughtException', function (err) {
    console.error(err.stack);
    console.log("ERROR! Node not exiting.");
});

<|endoftext|>


<commit_before>'use strict';

function um(num) {
    var numeros = [
        'zero',
        'um',
        'dois',
        'três',
        'quatro',
        'cinco',
        'seis',
        'sete',
        'oito',
        'nove'
    ];

    return numeros[num];
}

function dez(num) {
    if (num < 10) {
        return um(num);
    }

    var numeros = [
        'dez',
        'onze',
        'doze',
        'treze',
        'quatorze',
        'quinze',
        'dezesseis',
        'dezessete',
        'dezoito',
        'dezenove',
        'vinte',
        'trinta',
        'quarenta',
        'cinquenta',
        'sessenta',
        'setenta',
        'oitenta',
        'noventa'
    ];

    if (num < 20) {
        return numeros[num - 10];
    } else {
        var n1 = numeros[(num - num % 10) / 10 + 8],
            n2 = num % 10;

        if (num % 10 === 0) {
            return n1;
        } else {
            return n1 + ' e ' + um(n2);
        }
    }
}

function cem(num) {
    if (num < 100) {
        return dez(num);
    } else if (num === 100) {
        return 'cem';
    } else {
        var numeros = [
            'cento',
            'duzentos',
            'trezentos',
            'quatrocentos',
            'quinhentos',
            'seissentos',
            'setecentos',
            'oitocentos',
            'novecentos'
        ];

        var n1 = numeros[(num - num % 100) / 100 - 1],
            n2 = num % 100;

        if (num % 100 === 0) {
            return n1;
        } else {
            return n1 + ' e ' + dez(n2);
        }
    }
}

function f(numero, sigular, plural, fun) {
    return function (num) {
        if (num === 1e3) {
            return 'mil';
        }

        var n = (num - num % numero) / numero, n2;

        if (num < numero) {
            return fun(num);
        } else if (n === 1) {
            if (num === numero) {
                return 'um ' + sigular;
            }

            n2 = num % numero;

            if (n2 % 100 === 0 || n2 < 100) {
                return 'um ' + sigular + ' e ' + fun(n2);
            } else {
                return 'um ' + sigular + ' ' + fun(n2);
            }
        } else {
            if (num % numero === 0) {
                return fun(n) + ' ' + plural;
            } else {
                var n1 = n;
                n2 = num % numero;

                if (n2 % 100 === 0 || n2 < 100) {
                    return fun(n1) + ' ' + plural + ' e ' + fun(n2);
                } else {
                    return fun(n1) + ' ' + plural + ' ' + fun(n2);
                }
            }
        }
    };
}

var mil = f(1e+3, 'mil', 'mil', cem),
    milhao = f(1e+6, 'milhão', 'milhões', mil),
    bilhao = f(1e+9, 'bilhão', 'bilhões', milhao),
    trilhao = f(1e+12, 'trilhão', 'trilhões', bilhao),
    quadrilhao = f(1e+15, 'quadrilhão', 'quadrilhões', trilhao),
    quintilhao = f(1e+18, 'quintilhão', 'quintilhões', quadrilhao),
    sextilhao = f(1e+21, 'sextilhão', 'sextilhões', quintilhao);

module.exports = function (num) {
    if (typeof num === 'number' && !(num % 1) && num >= 0 && num < 1e+24) {
        return sextilhao(num);
    } else if (num) {
        return new Error('Número inválido.');
    } else {
        return undefined;
    }
};
<commit_msg>Corrige erro em números menores que 1 milhão.<commit_after>'use strict';

function um(num) {
    var numeros = [
        'zero',
        'um',
        'dois',
        'três',
        'quatro',
        'cinco',
        'seis',
        'sete',
        'oito',
        'nove'
    ];

    return numeros[num];
}

function dez(num) {
    if (num < 10) {
        return um(num);
    }

    var numeros = [
        'dez',
        'onze',
        'doze',
        'treze',
        'quatorze',
        'quinze',
        'dezesseis',
        'dezessete',
        'dezoito',
        'dezenove',
        'vinte',
        'trinta',
        'quarenta',
        'cinquenta',
        'sessenta',
        'setenta',
        'oitenta',
        'noventa'
    ];

    if (num < 20) {
        return numeros[num - 10];
    } else {
        var n1 = numeros[(num - num % 10) / 10 + 8],
            n2 = num % 10;

        if (num % 10 === 0) {
            return n1;
        } else {
            return n1 + ' e ' + um(n2);
        }
    }
}

function cem(num) {
    if (num < 100) {
        return dez(num);
    } else if (num === 100) {
        return 'cem';
    } else {
        var numeros = [
            'cento',
            'duzentos',
            'trezentos',
            'quatrocentos',
            'quinhentos',
            'seissentos',
            'setecentos',
            'oitocentos',
            'novecentos'
        ];

        var n1 = numeros[(num - num % 100) / 100 - 1],
            n2 = num % 100;

        if (num % 100 === 0) {
            return n1;
        } else {
            return n1 + ' e ' + dez(n2);
        }
    }
}

function f(numero, sigular, plural, fun) {
    return function (num) {
        if (num === 1e3) {
            return 'mil';
        }

        var n = (num - num % numero) / numero, n2;

        if (num < numero) {
            return fun(num);
        } else if (n === 1) {
            if (num === numero) {
                return 'um ' + sigular;
            }

            n2 = num % numero;

            if (num < 1e6) {
                if (n2 % 100 === 0 || n2 < 100) {
                    return sigular + ' e ' + fun(n2);
                } else {
                    return sigular + ' ' + fun(n2);
                }
            } else {
                if (n2 % 100 === 0 || n2 < 100) {
                    return 'um ' + sigular + ' e ' + fun(n2);
                } else {
                    return 'um ' + sigular + ' ' + fun(n2);
                }
            }
        } else {
            if (num % numero === 0) {
                return fun(n) + ' ' + plural;
            } else {
                var n1 = n;
                n2 = num % numero;

                if (n2 % 100 === 0 || n2 < 100) {
                    return fun(n1) + ' ' + plural + ' e ' + fun(n2);
                } else {
                    return fun(n1) + ' ' + plural + ' ' + fun(n2);
                }
            }
        }
    };
}

var mil = f(1e+3, 'mil', 'mil', cem),
    milhao = f(1e+6, 'milhão', 'milhões', mil),
    bilhao = f(1e+9, 'bilhão', 'bilhões', milhao),
    trilhao = f(1e+12, 'trilhão', 'trilhões', bilhao),
    quadrilhao = f(1e+15, 'quadrilhão', 'quadrilhões', trilhao),
    quintilhao = f(1e+18, 'quintilhão', 'quintilhões', quadrilhao),
    sextilhao = f(1e+21, 'sextilhão', 'sextilhões', quintilhao);

module.exports = function (num) {
    if (typeof num === 'number' && !(num % 1) && num >= 0 && num < 1e+24) {
        return sextilhao(num);
    } else if (num) {
        return new Error('Número inválido.');
    } else {
        return undefined;
    }
};
<|endoftext|>