<commit_before>'use strict';
/* jshint camelcase: false */

/**
 * nodecached server: memory functions.
 * (C) 2013 Alex Fernández.
 */

// requires
const Log = require('log');
const packageJson = require('../package.json');
const prototypes = require('prototypes');

// globals
const log = new Log('info');

// constants
const MAX_RELATIVE_SECONDS = 60*60*24*30;

/**
 * A record to store in memory. Params:
 *	- value: the value to store.
 *	- expirationSeconds: the number of seconds that the item must live, at most.
 *	- flags: to store with the value.
 */
class Record {
	constructor(value, expirationSeconds, flags)
	{
		this.flags = flags;
		this.expiration = 0;
		this.value = value;
		if (expirationSeconds)
		{
			if (expirationSeconds < MAX_RELATIVE_SECONDS)
			{
				this.expiration = Date.now() + 1000 * expirationSeconds;
			}
			else
			{
				this.expiration = 1000 * expirationSeconds;
			}
		}
	}

	/**
	 * Find out if the record is still valid.
	 */
	isValid()
	{
		if (!this.expiration)
		{
			return true;
		}
		log.debug('Now: %s, expiration: %s', Date.now(), this.expiration);
		return (Date.now() < this.expiration);
	}
}

/**
 * An in-memory cache object.
 * Options can be an integer defining the size of the cache in items.
 * Otherwise it is an object that may hold:
 *	- maxSizeMb: max size of the cache in MB.
 * Compatible with jscache API: https://github.com/monsur/jscache.
 */
class Cache {
	constructor(options = {})
	{
		this.records = {};
		this.maxRecords = parseInt(options.maxRecords) || 0;
		this.maxSizeMb = options.maxSizeMb || 0
		this.totalItems = 0;
		this.port = options.port || 0
	}

	/**
	 * Find out if the cache contains the given key.
	 */
	contains(key)
	{
		if (!(key in this.records))
		{
			return false;
		}
		const record = this.records[key];
		if (!record.isValid())
		{
			return false;
		}
		return true;
	}

	/**
	 * Get a value from the cache. Params:
	 *	- key: the key to look up.
	 * Returns the current value, or null if not in the cache.
	 */
	get(key)
	{
		if (!this.contains(key))
		{
			return null;
		}
		return this.records[key].value;
	}

	/**
	 * Get multiple values from the cache. Params:
	 *	- keys: an array of keys.
	 *	Returns a map with the current value for each key, if present.
	 */
	getMulti(keys)
	{
		const result = {};
		keys.forEach(function(key)
		{
			if (this.contains(key))
			{
				result[key] = this.records[key].value;
			}
		});
		return result;
	}

	/**
	 * Set a record in the cache. Params:
	 *	- key: the key to look up.
	 *	- value: to store.
	 *	- expirationSeconds: time to live, or 0 for unlimited.
	 *	- flags: for the value.
	 * Returns true if the item was stored.
	 */
	set(key, value, expirationSeconds, flags)
	{
		const record = new Record(value, expirationSeconds, flags);
		this.records[key] = record;
		this.totalItems++;
		process.nextTick(() => this.purge());
		return true;
	}

	/**
	 * Add a record to the cache, only if it is not present. Params equal to set().
	 */
	add(key)
	{
		if (this.contains(key))
		{
			return false;
		}
		return this.set.apply(this, arguments);
	}

	/**
	 * Replace a record in the cache, only if already present. Params equal to set().
	 */
	replace(key)
	{
		if (!this.contains(key))
		{
			return false;
		}
		return this.set.apply(this, arguments);
	}

	/**
	 * Append to a record in the cache, only if already present. Params equal to set().
	 * Expiration seconds and flags ignored.
	 */
	append(key, value)
	{
		return this.getStringRecord(key, function(record)
		{
			record.value += value;
		});
	}

	/**
	 * Append to a record in the cache, only if already present. Params equal to set().
	 * Expiration seconds and flags ignored.
	 */
	prepend(key, value)
	{
		return this.getStringRecord(key, function(record)
		{
			record.value = value + record.value;
		});
	}

	/**
	 * Get the record with the value as a string.
	 */
	getStringRecord(key, modify)
	{
		if (!this.contains(key))
		{
			return false;
		}
		const record = this.records[key];
		if (typeof record.value != 'string')
		{
			record.value = JSON.stringify(record.value);
		}
		modify(record);
		return true;
	}

	/**
	 * Delete a record from the cache. Params:
	 *	- key: the key to look up.
	 * Returns true if the item was deleted, false if not in the cache.
	 */
	delete(key)
	{
		if (!this.contains(key))
		{
			return false;
		}
		delete this.records[key];
		process.nextTick(() => this.purge());
		return true;
	}

	/**
	 * Increment a key with a value. Only valid for numbers. Params:
	 *	- key: the key to look up.
	 * Returns false if not found, new value otherwise.
	 */
	incr(key, value)
	{
		if (!this.contains(key))
		{
			return false;
		}
		const record = this.records[key];
		if (!prototypes.isNumber(record.value))
		{
			throw new Error('cannot increment or decrement non-numeric value');
		}
		const original = parseInt(record.value, 10);
		let result = original + value;
		if (result < 0)
		{
			result = 0;
		}
		record.value = result;
		return record.value;
	}

	/**
	 * Decrement a key with a value. Only valid for numbers. Params:
	 *	- key: the key to look up.
	 * Returns false if not found, new value otherwise.
	 */
	decr(key, value)
	{
		return this.incr(key, -value);
	}

	/**
	 * Change the expiration seconds of a record. Params:
	 *	- key: the key to look up.
	 *	- expirationSeconds: time to live, or 0 for unlimited.
	 */
	touch(key, expirationSeconds)
	{
		if (!this.contains(key))
		{
			return false;
		}
		const record = this.records[key];
		log.debug('touching: %j', record);
		record.expirationSeconds = expirationSeconds;
		process.nextTick(() => this.purge());
		return true;
	}

	/**
	 * Return an object with stats. Params:
	 *	- type: of stats wanted, ignored by now.
	 */
	stats(type)
	{
		if (type)
		{
			log.error('Unsupported stats type %s', type);
		}
		return {
			pid: process.pid,
			uptime: process.uptime(),
			time: Math.floor(Date.now() / 1000),
			version: this.version(),
			curr_items: this.records.countProperties(),
			total_items: this.totalItems,
			bytes: process.memoryUsage().rss,
			max_bytes: this.maxSizeMb * 1024 * 1024,
			tcpport: this.port,
			num_threads: 1,
			cas_enabled: 'no',
			evictions: 'on',
		};
	}

	/**
	 * Flush all items.
	 */
	flush()
	{
		this.records = {};
		log.notice('All items flushed');
		return true;
	}

	/**
	 * Flush all after an optional expiration in seconds.
	 */
	flush_all(expiration)
	{
		setTimeout(this.flush, expiration * 1000);
		return true;
	}

	/**
	 * Return the current version string.
	 */
	version()
	{
		return packageJson.name + '-' + packageJson.version;
	}

	/**
	 * Set verbosity level. Not implemented.
	 */
	verbosity()
	{
		return true;
	}

	/**
	 * Get a complete record from the cache. Params:
	 *	- key: the key to look up.
	 * Returns the current record (with flags, expiration and value), or null if not in the cache.
	 * Warning: returns even expired records, use with care.
	 */
	getRecord(key)
	{
		return this.records[key];
	}

	/**
	 * Get item directly. Params:
	 *	- key: the key to look up.
	 * Part of jscache API.
	 */
	getItem(key)
	{
		if (!this.contains(key))
		{
			return null;
		}
		return this.records[key].value;
	}

	/**
	 * Set an item directly. Params:
	 *	- key: the key to look up.
	 *	- value: to set.
	 *	- itemOptions: may contain:
	 *		- expirationAbsolute: the time to expire.
	 *		- expirationSeconds: seconds to expire.
	 * Part of jscache API.
	 */
	setItem(key, value, itemOptions)
	{
		let lifetime = 0;
		if (itemOptions)
		{
			if (itemOptions.expirationSeconds)
			{
				lifetime = itemOptions.expirationSeconds;
			}
			if (itemOptions.expirationAbsolute)
			{
				lifetime = (Date.now() - itemOptions.expirationAbsolute.getTime()) / 1000;
			}
		}
		log.debug('storing: %j', value);
		this.set(key, value, lifetime, 0);
	}

	/**
	 * Remove an item. Params:
	 *	- key: the key to look up.
	 * Part of jscache API.
	 */
	removeItem(key)
	{
		return this.delete(key);
	}

	/**
	 * Get how many items are stored.
	 * Part of jscache API.
	 */
	size()
	{
		return this.records.countProperties();
	}

	/**
	 * Purge memory, if needed.
	 */
	purge()
	{
		if (this.maxRecords)
		{
			this.purgeRecords()
		}
		if (this.maxSizeMb)
		{
			this.purgeMemory()
		}
	}

	purgeRecords()
	{
		if (this.records.countProperties() < this.maxRecords)
		{
			return;
		}
		for (const key in this.records)
		{
			delete this.records[key];
			if (this.records.countProperties() < this.maxRecords)
			{
				return;
			}
		}
	}

	purgeMemory()
	{
		const usageMb = process.memoryUsage().rss / 1024 / 1024;
		if (usageMb < this.maxSizeMb)
		{
			return;
		}
		log.info('Memory used %s, max %s: purging', usageMb, this.maxSizeMb);
		for (const key in this.records)
		{
			if (!this.records[key].isValid())
			{
				delete this.records[key];
			}
		}
		if (usageMb < this.maxSizeMb)
		{
			return;
		}
		for (const key in this.records)
		{
			delete this.records[key];
			if (usageMb < this.maxSizeMb)
			{
				return;
			}
		}
	}
}

module.exports = {Record, Cache}

<commit_msg>Use map for cache records.<commit_after>'use strict';
/* jshint camelcase: false */

/**
 * nodecached server: memory functions.
 * (C) 2013 Alex Fernández.
 */

// requires
const Log = require('log');
const packageJson = require('../package.json');
const prototypes = require('prototypes');

// globals
const log = new Log('info');

// constants
const MAX_RELATIVE_SECONDS = 60*60*24*30;

/**
 * A record to store in memory. Params:
 *	- value: the value to store.
 *	- expirationSeconds: the number of seconds that the item must live, at most.
 *	- flags: to store with the value.
 */
class Record {
	constructor(value, expirationSeconds, flags)
	{
		this.flags = flags;
		this.expiration = 0;
		this.value = value;
		if (expirationSeconds)
		{
			if (expirationSeconds < MAX_RELATIVE_SECONDS)
			{
				this.expiration = Date.now() + 1000 * expirationSeconds;
			}
			else
			{
				this.expiration = 1000 * expirationSeconds;
			}
		}
	}

	/**
	 * Find out if the record is still valid.
	 */
	isValid()
	{
		if (!this.expiration)
		{
			return true;
		}
		log.debug('Now: %s, expiration: %s', Date.now(), this.expiration);
		return (Date.now() < this.expiration);
	}
}

/**
 * An in-memory cache object.
 * Options can be an integer defining the size of the cache in items.
 * Otherwise it is an object that may hold:
 *	- maxSizeMb: max size of the cache in MB.
 * Compatible with jscache API: https://github.com/monsur/jscache.
 */
class Cache {
	constructor(options = {})
	{
		this.records = new Map();
		this.maxRecords = parseInt(options.maxRecords) || 0;
		this.maxSizeMb = options.maxSizeMb || 0
		this.totalItems = 0;
		this.port = options.port || 0
	}

	/**
	 * Find out if the cache contains the given key.
	 */
	contains(key)
	{
		if (!this.records.has(key))
		{
			return false;
		}
		const record = this.records.get(key);
		if (!record.isValid())
		{
			return false;
		}
		return true;
	}

	/**
	 * Get a value from the cache. Params:
	 *	- key: the key to look up.
	 * Returns the current value, or null if not in the cache.
	 */
	get(key)
	{
		if (!this.contains(key))
		{
			return null;
		}
		return this.records.get(key).value;
	}

	/**
	 * Get multiple values from the cache. Params:
	 *	- keys: an array of keys.
	 *	Returns a map with the current value for each key, if present.
	 */
	getMulti(keys)
	{
		const result = {};
		keys.forEach(function(key)
		{
			if (this.contains(key))
			{
				result[key] = this.records.get(key).value;
			}
		});
		return result;
	}

	/**
	 * Set a record in the cache. Params:
	 *	- key: the key to look up.
	 *	- value: to store.
	 *	- expirationSeconds: time to live, or 0 for unlimited.
	 *	- flags: for the value.
	 * Returns true if the item was stored.
	 */
	set(key, value, expirationSeconds, flags)
	{
		const record = new Record(value, expirationSeconds, flags);
		this.records.set(key, record);
		this.totalItems++;
		process.nextTick(() => this.purge());
		return true;
	}

	/**
	 * Add a record to the cache, only if it is not present. Params equal to set().
	 */
	add(key)
	{
		if (this.contains(key))
		{
			return false;
		}
		return this.set.apply(this, arguments);
	}

	/**
	 * Replace a record in the cache, only if already present. Params equal to set().
	 */
	replace(key)
	{
		if (!this.contains(key))
		{
			return false;
		}
		return this.set.apply(this, arguments);
	}

	/**
	 * Append to a record in the cache, only if already present. Params equal to set().
	 * Expiration seconds and flags ignored.
	 */
	append(key, value)
	{
		return this.getStringRecord(key, function(record)
		{
			record.value += value;
		});
	}

	/**
	 * Append to a record in the cache, only if already present. Params equal to set().
	 * Expiration seconds and flags ignored.
	 */
	prepend(key, value)
	{
		return this.getStringRecord(key, function(record)
		{
			record.value = value + record.value;
		});
	}

	/**
	 * Get the record with the value as a string.
	 */
	getStringRecord(key, modify)
	{
		if (!this.contains(key))
		{
			return false;
		}
		const record = this.records.get(key);
		if (typeof record.value != 'string')
		{
			record.value = JSON.stringify(record.value);
		}
		modify(record);
		return true;
	}

	/**
	 * Delete a record from the cache. Params:
	 *	- key: the key to look up.
	 * Returns true if the item was deleted, false if not in the cache.
	 */
	delete(key)
	{
		if (!this.contains(key))
		{
			return false;
		}
		this.records.delete(key);
		process.nextTick(() => this.purge());
		return true;
	}

	/**
	 * Increment a key with a value. Only valid for numbers. Params:
	 *	- key: the key to look up.
	 * Returns false if not found, new value otherwise.
	 */
	incr(key, value)
	{
		if (!this.contains(key))
		{
			return false;
		}
		const record = this.records.get(key);
		if (!prototypes.isNumber(record.value))
		{
			throw new Error('cannot increment or decrement non-numeric value');
		}
		const original = parseInt(record.value, 10);
		let result = original + value;
		if (result < 0)
		{
			result = 0;
		}
		record.value = result;
		return record.value;
	}

	/**
	 * Decrement a key with a value. Only valid for numbers. Params:
	 *	- key: the key to look up.
	 * Returns false if not found, new value otherwise.
	 */
	decr(key, value)
	{
		return this.incr(key, -value);
	}

	/**
	 * Change the expiration seconds of a record. Params:
	 *	- key: the key to look up.
	 *	- expirationSeconds: time to live, or 0 for unlimited.
	 */
	touch(key, expirationSeconds)
	{
		if (!this.contains(key))
		{
			return false;
		}
		const record = this.records.get(key);
		log.debug('touching: %j', record);
		record.expirationSeconds = expirationSeconds;
		process.nextTick(() => this.purge());
		return true;
	}

	/**
	 * Return an object with stats. Params:
	 *	- type: of stats wanted, ignored by now.
	 */
	stats(type)
	{
		if (type)
		{
			log.error('Unsupported stats type %s', type);
		}
		return {
			pid: process.pid,
			uptime: process.uptime(),
			time: Math.floor(Date.now() / 1000),
			version: this.version(),
			curr_items: this.records.size,
			total_items: this.totalItems,
			bytes: process.memoryUsage().rss,
			max_bytes: this.maxSizeMb * 1024 * 1024,
			tcpport: this.port,
			num_threads: 1,
			cas_enabled: 'no',
			evictions: 'on',
		};
	}

	/**
	 * Flush all items.
	 */
	flush()
	{
		this.records.clear();
		log.notice('All items flushed');
		return true;
	}

	/**
	 * Flush all after an optional expiration in seconds.
	 */
	flush_all(expiration)
	{
		setTimeout(this.flush, expiration * 1000);
		return true;
	}

	/**
	 * Return the current version string.
	 */
	version()
	{
		return packageJson.name + '-' + packageJson.version;
	}

	/**
	 * Set verbosity level. Not implemented.
	 */
	verbosity()
	{
		return true;
	}

	/**
	 * Get a complete record from the cache. Params:
	 *	- key: the key to look up.
	 * Returns the current record (with flags, expiration and value), or null if not in the cache.
	 * Warning: returns even expired records, use with care.
	 */
	getRecord(key)
	{
		return this.records.get(key);
	}

	/**
	 * Get item directly. Params:
	 *	- key: the key to look up.
	 * Part of jscache API.
	 */
	getItem(key)
	{
		if (!this.contains(key))
		{
			return null;
		}
		return this.records.get(key).value;
	}

	/**
	 * Set an item directly. Params:
	 *	- key: the key to look up.
	 *	- value: to set.
	 *	- itemOptions: may contain:
	 *		- expirationAbsolute: the time to expire.
	 *		- expirationSeconds: seconds to expire.
	 * Part of jscache API.
	 */
	setItem(key, value, itemOptions)
	{
		let lifetime = 0;
		if (itemOptions)
		{
			if (itemOptions.expirationSeconds)
			{
				lifetime = itemOptions.expirationSeconds;
			}
			if (itemOptions.expirationAbsolute)
			{
				lifetime = (Date.now() - itemOptions.expirationAbsolute.getTime()) / 1000;
			}
		}
		log.debug('storing: %j', value);
		this.set(key, value, lifetime, 0);
	}

	/**
	 * Remove an item. Params:
	 *	- key: the key to look up.
	 * Part of jscache API.
	 */
	removeItem(key)
	{
		return this.delete(key);
	}

	/**
	 * Get how many items are stored.
	 * Part of jscache API.
	 */
	size()
	{
		return this.records.size;
	}

	/**
	 * Purge memory, if needed.
	 */
	purge()
	{
		if (this.maxRecords)
		{
			this.purgeRecords()
		}
		if (this.maxSizeMb)
		{
			this.purgeMemory()
		}
	}

	purgeRecords()
	{
		if (this.records.size < this.maxRecords)
		{
			return;
		}
		for (const key in this.records.keys())
		{
			this.records.delete(key);
			if (this.records.size < this.maxRecords)
			{
				return;
			}
		}
	}

	purgeMemory()
	{
		const usageMb = process.memoryUsage().rss / 1024 / 1024;
		if (usageMb < this.maxSizeMb)
		{
			return;
		}
		log.info('Memory used %s, max %s: purging', usageMb, this.maxSizeMb);
		for (const key in this.records.keys())
		{
			if (!this.records.get(key).isValid())
			{
				this.records.delete(key);
			}
		}
		if (usageMb < this.maxSizeMb)
		{
			return;
		}
		for (const key in this.records)
		{
			this.records.delete(key);
			if (usageMb < this.maxSizeMb)
			{
				return;
			}
		}
	}
}

module.exports = {Record, Cache}

<|endoftext|>


<commit_before>'use strict';

/**
 * Mapping from models into high-level devices.
 */
const AirPurifier = require('./devices/air-purifier');
const Gateway = require('./devices/gateway');

const Vacuum = require('./devices/vacuum');

const PowerPlug = require('./devices/power-plug');
const PowerStrip = require('./devices/power-strip');

const Humidifier = require('./devices/humidifier');

module.exports = {
	'zhimi.airpurifier.m1': AirPurifier,
	'zhimi.airpurifier.v1': AirPurifier,
	'zhimi.airpurifier.v2': AirPurifier,
	'zhimi.airpurifier.v3': AirPurifier,
	'zhimi.airpurifier.v6': AirPurifier,

	'zhimi.humidifier.v1': Humidifier,

	'chuangmi.plug.m1': PowerPlug,
	'chuangmi.plug.v1': require('./devices/chuangmi.plug.v1'),
	'chuangmi.plug.v2': PowerPlug,

	'rockrobo.vacuum.v1': Vacuum,

	'lumi.gateway.v2': Gateway,
	'lumi.gateway.v3': Gateway,
	'lumi.acpartner.v1': Gateway,

	'qmi.powerstrip.v1': PowerStrip,
	'zimi.powerstrip.v2': PowerStrip,

	'yeelink.light.lamp1': require('./devices/yeelight.lamp'),
	'yeelink.light.mono1': require('./devices/yeelight.mono'),
	'yeelink.light.color1': require('./devices/yeelight.color'),
	'philips.light.bulb': require('./devices/philips-light-bulb')

};
<commit_msg>Restore missing model<commit_after>'use strict';

/**
 * Mapping from models into high-level devices.
 */
const AirPurifier = require('./devices/air-purifier');
const Gateway = require('./devices/gateway');

const Vacuum = require('./devices/vacuum');

const PowerPlug = require('./devices/power-plug');
const PowerStrip = require('./devices/power-strip');

const Humidifier = require('./devices/humidifier');

module.exports = {
	'zhimi.airpurifier.m1': AirPurifier,
	'zhimi.airpurifier.v1': AirPurifier,
	'zhimi.airpurifier.v2': AirPurifier,
	'zhimi.airpurifier.v3': AirPurifier,
	'zhimi.airpurifier.v6': AirPurifier,

	'zhimi.humidifier.v1': Humidifier,

	'chuangmi.plug.m1': PowerPlug,
	'chuangmi.plug.v1': require('./devices/chuangmi.plug.v1'),
	'chuangmi.plug.v2': PowerPlug,

	'rockrobo.vacuum.v1': Vacuum,

	'lumi.gateway.v2': Gateway,
	'lumi.gateway.v3': Gateway,
	'lumi.acpartner.v1': Gateway,

	'qmi.powerstrip.v1': PowerStrip,
	'zimi.powerstrip.v2': PowerStrip,

	'yeelink.light.lamp1': require('./devices/yeelight.lamp'),
	'yeelink.light.mono1': require('./devices/yeelight.mono'),
	'yeelink.light.color1': require('./devices/yeelight.color'),
	'philips.light.sread1': require('./devices/eyecare-lamp2'),
	'philips.light.bulb': require('./devices/philips-light-bulb')

};
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2011-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*jslint nomen:true, node:true*/

/**
Main module in the Mojito package that is responsible for creating the mojito
instance to the attached to the `express` app.

Developers should not access this module directly, but instead should
instantiate their app the express way.

Usage:

    var app = require('express'),
        mojito = require('mojito'),
        app;

    app = express(); // Mojito instance will be created during that call

    app.use(mojito.middleware());

    app.listen(app.get('port'));

@module mojito
**/

'use strict';

var debug = require('debug')('mojito'),
    express = require('express'),
    libpath = require('path'),
    appProto = express.application,
    defaultConfiguration = appProto.defaultConfiguration,
    liblogger = require('./logger'),
    libmiddleware = require('./middleware'),
    libstore = require('./store'),
    Mojito;

//  ----------------------------------------------------------------------------
//  Util
//  ----------------------------------------------------------------------------
// If more helpers are needed, they could potentially be moved to their own
// `util.js` module later.

/**
Copied from modown-yui.utils
**/
function extend(obj) {
    Array.prototype.slice.call(arguments, 1).forEach(function (source) {
        var key;

        if (!source) { return; }

        for (key in source) {
            if (source.hasOwnProperty(key)) {
                obj[key] = source[key];
            }
        }
    });

    return obj;
}

//  ----------------------------------------------------------------------------
//  Mojito Global
//  ----------------------------------------------------------------------------

/**
Mojito extension for Express.

@protected
@class Mojito
@constructor
@param {express.application} app `express` application instance
@uses *express, *middleware, *logger
**/
function Mojito(app) {

    this._app = app;
    this._config = {};
    extend(Mojito, libmiddleware, liblogger);
    app.mojito = {};
    this._init(app);

    return this;
}

/**
Creates a new instance of mojito and attach to `app`.

@method _init
@protected
@param {express.application} app optional

**/
Mojito.prototype._init = function (app) {

    app = app || this._app;

    var my = this,
        appConfig, // application.json
        options,   //
        pack,      // package.json
        root,      // the app root directory
        store;     // reference to resource store

    // debug('applying mojito.defaultConfiguration()');

    if (!app.mojito) {
        throw new Error('`app.mojito` was not initialized correctly.');
    }

    // TODO: `root` is required for the resource store, which will be replaced
    // by `locator` at some point. `locator` will have access to the appRoot
    // folder.
    // The application root directory
    root = process.cwd();

    store = libstore.createStore({
        root: root,
        preload: 'skip', // only need appConfig and package.json
        context: { }
    });
    appConfig = store.getAppConfig();

    options = {};
    options.port = appConfig.appPort || process.env.PORT || 8666;
    options.context = { runtime: 'server' }; // TODO: use correct static context here
    options.mojitoRoot = __dirname;
    options.root = root;

    // create new store, configure mojito, and optimize
    store = libstore.createStore(options);

    my._configureAppInstance(app, store, options);

    // set port from application.json
    app.set('port', options.port);
};

/**
Configures YUI with both the Mojito framework and all the YUI modules in the
application.

@method _configureYUI
@protected
@param {Object} Y YUI object to configure
@param {Object} store Resource Store which knows what to load
@return {Array} array of YUI module names
**/
Mojito.prototype._configureYUI = function (Y, store) {
    var my = this,
        modules,
        load,
        lang;

    modules = store.yui.getModulesConfig('server', false);
    Y.applyConfig(modules);

    load = Object.keys(modules.modules);

    // NOTE:  Not all of these module names are guaranteed to be valid,
    // but the loader tolerates them anyways.
    for (lang in store.yui.langs) {
        if (store.yui.langs.hasOwnProperty(lang) && lang) {
            load.push('lang/datatype-date-format_' + lang);
        }
    }

    return load;
};

/**
Adds Mojito framework components to the Express application instance.

@method _configureAppInstance
@protected
@param {express.application} app The Express application instance to Mojito-enable.
@param {ResourceStore} store
@param {Object} options
    @param {Integer} options.port port to listen on
    @param {Object} options.context static context set at startup
    @param {String} options.mojitoRoot
    @param {String} options.root the app root dir
**/
Mojito.prototype._configureAppInstance = function (app, store, options) {
    var my = this,
        Y,
        appConfig,
        yuiConfig,
        modules = [],
        debugConfig;

    appConfig = store.getStaticAppConfig();
    yuiConfig = (appConfig.yui && appConfig.yui.config) || {};

    // redefining "combine" and/or "base" in the server side have side effects
    // and might try to load yui from CDN, so we bypass them.
    // TODO: report bug.
    // is there a better alternative for this delete?
    // maybe not, but it might introduce a perf penalty
    // in v8 engine, and we can't use the undefined trick
    // because loader is doing hasOwnProperty :(
    delete yuiConfig.combine;
    delete yuiConfig.base;

    // in case we want to collect some performance metrics,
    // we can do that by defining the "perf" object in:
    // application.json (master)
    // You can also use the option --perf path/filename.log when
    // running mojito start to dump metrics to disk.
    if (appConfig.perf) {
        yuiConfig.perf = appConfig.perf;
        yuiConfig.perf.logFile = options.perf ||
                                    libpath.join(options.root, 'perf.log');
    }

    // getting yui module, or yui/debug if needed, and applying
    // the default configuration from application.json->yui-config
    Y = require('yui' + (yuiConfig.filter === 'debug' ? '/debug' : '')).YUI(yuiConfig, {
        useSync: true
    });

    liblogger.configureLogger(Y);
    modules = my._configureYUI(Y, store);

    // attaching all modules available for this application for the server side
    Y.applyConfig({ useSync: true });
    Y.use.apply(Y, modules);
    Y.applyConfig({ useSync: false });

    // expose the middlewares to `mojito` function
    // mojitoFn.exposeMiddleware();

    // stash for runtime
    extend(app.mojito, {
        store: store,
        Y: Y,
        context: options.context,
        options: options
    });

    debug('Mojito ready to serve.');

}; // _configureAppInstance

/**
Hook into `express` default configuration init phase.
**/
appProto.defaultConfiguration = function () {
    defaultConfiguration.apply(this, arguments);

    if (!this.mojito) {
        var mojito = new Mojito(this);
    } else {
        debug('skipping creation of `app.mojito` because it is already defined');
    }
};

//  ----------------------------------------------------------------------------
//  EXPORT(S)
//  ----------------------------------------------------------------------------

/**

Export Mojito extension as a function.

**/

exports = module.exports = Mojito;

<commit_msg>Added doc about how objects are exposed via the `app.mojito` instance<commit_after>/**
 * Copyright (c) 2011-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*jslint nomen:true, node:true*/

/**
Main module in the Mojito package that is responsible for creating the mojito
instance to the attached to the `express` app.

Developers should not access this module directly, but instead should
instantiate their app the express way.

Usage:

    var app = require('express'),
        mojito = require('mojito'),
        app;

    app = express(); // Mojito instance will be created during that call

    app.use(mojito.middleware());

    app.listen(app.get('port'));

@module mojito
**/

'use strict';

var debug = require('debug')('mojito'),
    express = require('express'),
    libpath = require('path'),
    appProto = express.application,
    defaultConfiguration = appProto.defaultConfiguration,
    liblogger = require('./logger'),
    libmiddleware = require('./middleware'),
    libstore = require('./store'),
    Mojito;

//  ----------------------------------------------------------------------------
//  Util
//  ----------------------------------------------------------------------------
// If more helpers are needed, they could potentially be moved to their own
// `util.js` module later.

/**
Copied from modown-yui.utils
**/
function extend(obj) {
    Array.prototype.slice.call(arguments, 1).forEach(function (source) {
        var key;

        if (!source) { return; }

        for (key in source) {
            if (source.hasOwnProperty(key)) {
                obj[key] = source[key];
            }
        }
    });

    return obj;
}

//  ----------------------------------------------------------------------------
//  Mojito Global
//  ----------------------------------------------------------------------------

/**
Mojito extension for Express.

@protected
@class Mojito
@constructor
@param {express.application} app `express` application instance
@uses *express, *middleware, *logger
**/
function Mojito(app) {

    this._app = app;
    this._config = {};
    extend(Mojito, libmiddleware, liblogger);
    app.mojito = {};
    this._init(app);

    return this;
}

/**
Creates a new instance of mojito and attach to `app`.

@method _init
@protected
@param {express.application} app optional

**/
Mojito.prototype._init = function (app) {

    app = app || this._app;

    var my = this,
        appConfig, // application.json
        options,   //
        pack,      // package.json
        root,      // the app root directory
        store;     // reference to resource store

    // debug('applying mojito.defaultConfiguration()');

    if (!app.mojito) {
        throw new Error('`app.mojito` was not initialized correctly.');
    }

    // TODO: `root` is required for the resource store, which will be replaced
    // by `locator` at some point. `locator` will have access to the appRoot
    // folder.
    // The application root directory
    root = process.cwd();

    store = libstore.createStore({
        root: root,
        preload: 'skip', // only need appConfig and package.json
        context: { }
    });
    appConfig = store.getAppConfig();

    options = {};
    options.port = appConfig.appPort || process.env.PORT || 8666;
    options.context = { runtime: 'server' }; // TODO: use correct static context here
    options.mojitoRoot = __dirname;
    options.root = root;

    // create new store, configure mojito, and optimize
    store = libstore.createStore(options);

    my._configureAppInstance(app, store, options);

    // set port from application.json
    app.set('port', options.port);
};

/**
Configures YUI with both the Mojito framework and all the YUI modules in the
application.

@method _configureYUI
@protected
@param {Object} Y YUI object to configure
@param {Object} store Resource Store which knows what to load
@return {Array} array of YUI module names
**/
Mojito.prototype._configureYUI = function (Y, store) {
    var my = this,
        modules,
        load,
        lang;

    modules = store.yui.getModulesConfig('server', false);
    Y.applyConfig(modules);

    load = Object.keys(modules.modules);

    // NOTE:  Not all of these module names are guaranteed to be valid,
    // but the loader tolerates them anyways.
    for (lang in store.yui.langs) {
        if (store.yui.langs.hasOwnProperty(lang) && lang) {
            load.push('lang/datatype-date-format_' + lang);
        }
    }

    return load;
};

/**
Adds Mojito framework components to the Express application instance.

@method _configureAppInstance
@protected
@param {express.application} app The Express application instance to Mojito-enable.
@param {ResourceStore} store
@param {Object} options
    @param {Integer} options.port port to listen on
    @param {Object} options.context static context set at startup
    @param {String} options.mojitoRoot
    @param {String} options.root the app root dir
**/
Mojito.prototype._configureAppInstance = function (app, store, options) {
    var my = this,
        Y,
        appConfig,
        yuiConfig,
        modules = [],
        debugConfig;

    appConfig = store.getStaticAppConfig();
    yuiConfig = (appConfig.yui && appConfig.yui.config) || {};

    // redefining "combine" and/or "base" in the server side have side effects
    // and might try to load yui from CDN, so we bypass them.
    // TODO: report bug.
    // is there a better alternative for this delete?
    // maybe not, but it might introduce a perf penalty
    // in v8 engine, and we can't use the undefined trick
    // because loader is doing hasOwnProperty :(
    delete yuiConfig.combine;
    delete yuiConfig.base;

    // in case we want to collect some performance metrics,
    // we can do that by defining the "perf" object in:
    // application.json (master)
    // You can also use the option --perf path/filename.log when
    // running mojito start to dump metrics to disk.
    if (appConfig.perf) {
        yuiConfig.perf = appConfig.perf;
        yuiConfig.perf.logFile = options.perf ||
                                    libpath.join(options.root, 'perf.log');
    }

    // getting yui module, or yui/debug if needed, and applying
    // the default configuration from application.json->yui-config
    Y = require('yui' + (yuiConfig.filter === 'debug' ? '/debug' : '')).YUI(yuiConfig, {
        useSync: true
    });

    liblogger.configureLogger(Y);
    modules = my._configureYUI(Y, store);

    // attaching all modules available for this application for the server side
    Y.applyConfig({ useSync: true });
    Y.use.apply(Y, modules);
    Y.applyConfig({ useSync: false });

    // TODO: This might change when `express-yui` and `locator` are integrated
    // and how they are exposed to the `app.mojito` instance.
    // stash for runtime
    extend(app.mojito, {
        store: store,
        Y: Y,
        context: options.context,
        options: options
    });

    debug('Mojito ready to serve.');

}; // _configureAppInstance

/**
Hook into `express` default configuration init phase.
**/
appProto.defaultConfiguration = function () {
    defaultConfiguration.apply(this, arguments);

    if (!this.mojito) {
        var mojito = new Mojito(this);
    } else {
        debug('skipping creation of `app.mojito` because it is already defined');
    }
};

//  ----------------------------------------------------------------------------
//  EXPORT(S)
//  ----------------------------------------------------------------------------

/**

Export Mojito extension as a function.

**/

exports = module.exports = Mojito;

<|endoftext|>


<commit_before>var config   = require(__dirname + '/../config.js');
var http     = require('http');

var db       = require(__dirname + '/../database/database.js');
var Movie    = require(__dirname + '/../database/movieDb.js');
var utils    = require(__dirname + '/utils.js');

var tmdb     = require('moviedb')(config.theMovieDbApiKey);

/*
 * Cron Function to update RSS-Feed-Entries into our Database
 */
var updateFeeds = function(callback) {

    utils.log('updating Movie-Feeds...');
    rssGet(function(rssItems) {
        utils.log('retrieved ' + rssItems.length + ' items from feeds');
        var movies = createMovieSetFromRss(rssItems);
        for(var i in movies) {
            db.movie.exists(movies[i], function(exists, movieEntry, movieInput) {
                // merge movies if already in database
                if(exists) {
                    var merged = db.movie.mergeMovies(movieEntry, movieInput);
                    movieEntry = merged.movie;
                    if(merged.newlinks) {
                        utils.log('Updating: ' + movieEntry.title);
                        movieEntry.lastUpdate = new Date(movieEntry.sites[movieEntry.sites.length - 1].pubdate);
                        movieEntry.save(function(e){callback(e)});
                    } else utils.log('No new links found for ' + movieEntry.title);
                }
                // add new movie
                else {
                    utils.log('Adding: ' + movieEntry.title);
                    movieEntry.lastUpdate = new Date(movieEntry.sites[movieEntry.sites.length - 1].pubdate);
                    movieEntry.save(function(e){callback(e)});
                }
            });
        }
    });
};
exports.updateFeeds = updateFeeds;

/*
 * picks one movie from db which has no Info
 * and adds gets info which is later added back to the db
 */
var cronUpdateInfo = function() {
    db.movie.findNew(function(err, movie) {
        if (movie !== null) {
            utils.log('Movie to udpate: ' + movie);
            catchAllInfos(movie, function() {});
        }
    });
};
exports.cronUpdateInfo = cronUpdateInfo;

var updateInfoById = function(_id, callback) {
    Movie.model.findById(_id, function(err, movie){
        movie.info.year = Math.max.apply(Math, movie.search.years);
        catchAllInfos(movie, function(){
            callback(true);
        });
    });
};
exports.updateInfoById = updateInfoById;

/**
 * Takes the data from the RSS Feeds and creates
 * database-models out of it
 */
var createMovieSetFromRss = function(items) {
    var movies = {};
    for (var i in items) {
        var title = getFilmTitleFromString(items[i].title);
        title = title.toLowerCase();
        var year  = yearFromString(items[i].title);
        // TODO: If there are two movies with same titles
        // but different year, this puts them into same
        // movie frame; this can be fixed
        if (movies[title] === undefined) {
            movies[title] = new Movie.model({
                title: title,
                search: {
                    years: [year],
                    titles: [title]
                },
                info: {
                    year: year,
                    lastUpdated: undefined
                }
            });
        }
        movies[title].sites.push(items[i]);
        // get resolution of download-page
        var res = getResFromString(items[i].title);
        // add quality to list, if not existent
        if (res && movies[title].resolutions.indexOf(res) === -1)
            movies[title].resolutions.push(res);
    }
    return movies;
};
exports.createMovieSetFromRss = createMovieSetFromRss;

/*
 * Reads Info for one movie from tomatoes or TheMovieDb and saves it to the database
 *
 * TODO: get additional info from omdbapi via http://www.omdbapi.com/?i=9874923432
 */
var catchAllInfos = function(movie, callback) {
    utils.log('fetching info for: ' + movie.title);
    // Retrieve Data from TheMovieDb
    tmdb.searchMovie({
        query: movie.title
    }, function(err, res) {
        var correctMovie = null;
        movie.info.lastUpdated = new Date();
        utils.log(res);
        if(res.results.length === 0) {
            movie.save(function(err, product, numberAffected) {
                if(err) callback(err);
                else callback(true);
            });
        } else {
            var success = false;
            for(var i in res.results) {
                if(parseInt(String(res.results[i].release_date).substring(0, 4), 10) === movie.info.year) {
                    success = true;
                    correctMovie = res.results[i];
                    movie.info.ids.tmdb = correctMovie.id;
                    tmdb.movieInfo({
                        id: correctMovie.id
                    }, function(err, res) {
                        utils.log('-'); utils.log(res);
                        movie.info.runtime = (res.runtime!==null?res.runtime:0);
                        movie.info.synopsis = res.overview;
                        movie.info.ids.imdb = res.imdb_id;
                        movie.title = res.original_title;
                        movie.info.posters.thumbnail = 'http://image.tmdb.org/t/p/w500' + res.poster_path;
                        utils.log('going to save the following entry...');
                        utils.log(movie);
                        movie.save(function(err, product, numberAffected) {
                            if(err) callback(err);
                            else callback(true);
                        });
                    });
                    break;
                }
            }
            if(!success) { // if no date matches, then take first result
                correctMovie = res.results[0];
                movie.info.ids.tmdb = correctMovie.id;
                tmdb.movieInfo({
                    id: correctMovie.id
                }, function(err, res) {
                    movie.info.runtime = res.runtime;
                    movie.info.synopsis = res.overview;
                    movie.info.ids.imdb = res.imdb_id;
                    movie.title = res.original_title;
                    movie.info.posters.thumbnail = 'http://image.tmdb.org/t/p/w500' + res.poster_path;
                    utils.log('going to save the following entry...');
                    utils.log(movie);
                    movie.save(function(err, product, numberAffected) {
                        if(err) callback(err);
                        else callback(true);
                    });
                });
            }
        }
    });
};
exports.catchAllInfos = catchAllInfos;

/*
 * Finds a Year in a String
 *
 * Year must be preceded by space or period
 * Year must be followed by space or period
 * Finds years from 1900-2099
 * exp: "Fathers.Day.2011.DC.BDRiP"
 * exp: "Fathers Day 2011 DC BDRiP"
 */
var yearFromString = function(title) {
    title = title.replace(/\./g, " "); // for "Fathers.Day.2011.DC.BDRiP" strings
    var year = title.match(/(.+)\s((19|20)\d{2})\s?/i);
    if(year !== null && year[2] !== undefined) {
        return parseInt(year[2], 10);
    }
    return 1900;
};
exports.yearFromString = yearFromString;

/**
 * Extracts Film Title from Movie-Filename-String
 *
 * Takes everything from the Start of the string to the first year-string
 * If there's no year, then the whole string is returned with stripped periods
 *
 * @param string string A string containing a Movie-Title. Must have a year
 * @returns {String} The Title of the Movie; If no year, then returns string
 */
var getFilmTitleFromString = function(string) {
    string = string.replace(/\./g, " "); // for "Fathers.Day.2011.DC.BDRiP" strings
    string = string.replace(/\’/g, "");
    var title = string.match(/(.+)\s(19|20)\d{2}\s?/i); // everything til the first 4-digit year followed by space or nothing
    if(title !== null) {
        return title[1];
    }
    return string;
};
exports.getFilmTitleFromString = getFilmTitleFromString;

/*
 * Extracts Resolution/Format from given String
 *
 * only returns first occurence
 *
 * TODO: Cam, TS with .CAM. and .TS.
 */
var getResFromString = function(string) {
    if(typeof string !== 'string') return false;
    var formats = utils.resolutions;
    var regex = new RegExp(formats.join('|'), 'i');
    var results = regex.exec(string);
    return(results === null) ? false : results[0];
};
exports.getResFromString = getResFromString;

/*
 * retrieves RSS content from all RSS-Movie-Feeds
 *
 */
var rssGet = function(callback) {
    utils.log('start reading feeds...');
    var Scrapper = require(__dirname + '/scrapper.js');
    var items = [];
    var feedCount = config.feeds.Movies.feeds.length;
    var feedcounter = 0;
    var processItems = function(err, rss) {
        if (err === null) {
            var i, title, regex;
            for (i in rss.items) {
                for (title in config.feeds.Movies.queries) {
                    regex = new RegExp(config.feeds.Movies.queries[title].title, 'g');
                    if (regex.exec(rss.items[i].title)) {
                        items.push(rss.items[i]);
                    }
                }
            }
        }
        feedcounter++;
        if (feedcounter === feedCount) {
            callback(items);
        }
    };
    utils.log('reading from total of: ' + feedCount + ' feeds');
    for (var i = 0; i < feedCount; i++) {
        utils.log(config.feeds.Movies.feeds[i]);
        Scrapper.scrap(config.feeds.Movies.feeds[i], function(err, rss) {
            utils.log(rss);
            utils.log(err);
            processItems(err, rss);
        });
    }
};

/*
 * Clean-up/remove old entries after n-days
 */
exports.cleanUp = function(n, callback) {
   utils.log('-');
   var expire = new Date(new Date().setDate(new Date().getDate()-n)).toISOString();
   utils.log('removing all entries that were last updated before ' + n + ' days...');
   utils.log('> removing everything older than ' + expire);
   Movie.model.find({lastUpdate : {$lt : expire}}).remove(function(err){
      utils.log(err);
      utils.log('removed!');
      utils.log('-');
   });
   callback();
};

// moviedb return for movieInfo
/*

{ adult: false,
  backdrop_path: '/4IamAvByBWnclmbwlnZB8otMyB7.jpg',
  belongs_to_collection: null,
  budget: 0,
  genres: [ { id: 35, name: 'Comedy' }, { id: 18, name: 'Drama' } ],
  homepage: '',
  id: 243684,
  imdb_id: 'tt3179568',
  original_language: 'en',
  original_title: 'Men, Women & Children',
  overview: 'Follows the story of a group of high school teenagers and their parents as they attempt to navigate the many ways the internet has changed their relationships, their communication, their self-image, and their love lives.',
  popularity: 4.07571172700321,
  poster_path: '/5odPwDRx5rYaA4GX85w7cUgYDrX.jpg',
  production_companies:
   [ { name: 'Right of Way Films', id: 32157 },
     { name: 'Paramount Pictures', id: 4 } ],
  production_countries: [ { iso_3166_1: 'US', name: 'United States of America' } ],
  release_date: '2014-10-17',
  revenue: 0,
  runtime: 116,
  spoken_languages: [ { iso_639_1: 'en', name: 'English' } ],
  status: 'Released',
  tagline: 'Discover how little you know about the people you know.',
  title: 'Men, Women & Children',
  video: false,
  vote_average: 6.4,
  vote_count: 30 }

*/

/**
 * deprecated IMDB-Functions
 * IMDB has no API to officialy retrieve Movie-information
 *
 */

/*
exports.getImdbFromTitle = function(title, callback) {
    var imdb = require('imdb-api');
    imdb.getReq({
        name: title
    }, function(err, info) {
        callback(err, info);
    });
};

exports.getImdbFromString = function(string, callback) {
    var imdb = require('imdb-api');
    var title = getFilmTitleFromString(string);
    if (title) {
        imdb.getReq({
            name: title
        }, function(err, info) {
            callback(err, info);
        });
    }
    else {
        callback(false);
    }
};
*/<commit_msg>integrate passport<commit_after>var config   = require(__dirname + '/../config.js');
var http     = require('http');

var db       = require(__dirname + '/../database/database.js');
var Movie    = require(__dirname + '/../database/movieDb.js');
var utils    = require(__dirname + '/utils.js');

var tmdb     = require('moviedb')(config.theMovieDbApiKey);

/*
 * Cron Function to update RSS-Feed-Entries into our Database
 */
var updateFeeds = function(callback) {

    utils.log('updating Movie-Feeds...');
    rssGet(function(rssItems) {
        utils.log('retrieved ' + rssItems.length + ' items from feeds');
        var movies = createMovieSetFromRss(rssItems);
        for(var i in movies) {
            db.movie.exists(movies[i], function(exists, movieEntry, movieInput) {
                // merge movies if already in database
                if(exists) {
                    var merged = db.movie.mergeMovies(movieEntry, movieInput);
                    movieEntry = merged.movie;
                    if(merged.newlinks) {
                        utils.log('Updating: ' + movieEntry.title);
                        movieEntry.lastUpdate = new Date(movieEntry.sites[movieEntry.sites.length - 1].pubdate);
                        movieEntry.save(function(e){callback(e)});
                    } else utils.log('No new links found for ' + movieEntry.title);
                }
                // add new movie
                else {
                    utils.log('Adding: ' + movieEntry.title);
                    movieEntry.lastUpdate = new Date();
                    movieEntry.save(function(e){callback(e)});
                }
            });
        }
    });
};
exports.updateFeeds = updateFeeds;

/*
 * picks one movie from db which has no Info
 * and adds gets info which is later added back to the db
 */
var cronUpdateInfo = function() {
    db.movie.findNew(function(err, movie) {
        if (movie !== null) {
            utils.log('Movie to udpate: ' + movie);
            catchAllInfos(movie, function() {});
        }
    });
};
exports.cronUpdateInfo = cronUpdateInfo;

var updateInfoById = function(_id, callback) {
    Movie.model.findById(_id, function(err, movie){
        movie.info.year = Math.max.apply(Math, movie.search.years);
        catchAllInfos(movie, function(){
            callback(true);
        });
    });
};
exports.updateInfoById = updateInfoById;

/**
 * Takes the data from the RSS Feeds and creates
 * database-models out of it
 */
var createMovieSetFromRss = function(items) {
    var movies = {};
    for (var i in items) {
        var title = getFilmTitleFromString(items[i].title);
        title = title.toLowerCase();
        var year  = yearFromString(items[i].title);
        // TODO: If there are two movies with same titles
        // but different year, this puts them into same
        // movie frame; this can be fixed
        if (movies[title] === undefined) {
            movies[title] = new Movie.model({
                title: title,
                search: {
                    years: [year],
                    titles: [title]
                },
                info: {
                    year: year,
                    lastUpdated: undefined
                }
            });
        }
        movies[title].sites.push(items[i]);
        // get resolution of download-page
        var res = getResFromString(items[i].title);
        // add quality to list, if not existent
        if (res && movies[title].resolutions.indexOf(res) === -1)
            movies[title].resolutions.push(res);
    }
    return movies;
};
exports.createMovieSetFromRss = createMovieSetFromRss;

/*
 * Reads Info for one movie from tomatoes or TheMovieDb and saves it to the database
 *
 * TODO: get additional info from omdbapi via http://www.omdbapi.com/?i=9874923432
 */
var catchAllInfos = function(movie, callback) {
    utils.log('fetching info for: ' + movie.title);
    // Retrieve Data from TheMovieDb
    tmdb.searchMovie({
        query: movie.title
    }, function(err, res) {
        var correctMovie = null;
        movie.info.lastUpdated = new Date();
        utils.log(res);
        if(res.results.length === 0) {
            movie.save(function(err, product, numberAffected) {
                if(err) callback(err);
                else callback(true);
            });
        } else {
            var success = false;
            for(var i in res.results) {
                if(parseInt(String(res.results[i].release_date).substring(0, 4), 10) === movie.info.year) {
                    success = true;
                    correctMovie = res.results[i];
                    movie.info.ids.tmdb = correctMovie.id;
                    tmdb.movieInfo({
                        id: correctMovie.id
                    }, function(err, res) {
                        utils.log('-'); utils.log(res);
                        movie.info.runtime = (res.runtime!==null?res.runtime:0);
                        movie.info.synopsis = res.overview;
                        movie.info.ids.imdb = res.imdb_id;
                        movie.title = res.original_title;
                        movie.info.posters.thumbnail = 'http://image.tmdb.org/t/p/w500' + res.poster_path;
                        utils.log('going to save the following entry...');
                        utils.log(movie);
                        movie.save(function(err, product, numberAffected) {
                            if(err) callback(err);
                            else callback(true);
                        });
                    });
                    break;
                }
            }
            if(!success) { // if no date matches, then take first result
                correctMovie = res.results[0];
                movie.info.ids.tmdb = correctMovie.id;
                tmdb.movieInfo({
                    id: correctMovie.id
                }, function(err, res) {
                    movie.info.runtime = res.runtime;
                    movie.info.synopsis = res.overview;
                    movie.info.ids.imdb = res.imdb_id;
                    movie.title = res.original_title;
                    movie.info.posters.thumbnail = 'http://image.tmdb.org/t/p/w500' + res.poster_path;
                    utils.log('going to save the following entry...');
                    utils.log(movie);
                    movie.save(function(err, product, numberAffected) {
                        if(err) callback(err);
                        else callback(true);
                    });
                });
            }
        }
    });
};
exports.catchAllInfos = catchAllInfos;

/*
 * Finds a Year in a String
 *
 * Year must be preceded by space or period
 * Year must be followed by space or period
 * Finds years from 1900-2099
 * exp: "Fathers.Day.2011.DC.BDRiP"
 * exp: "Fathers Day 2011 DC BDRiP"
 */
var yearFromString = function(title) {
    title = title.replace(/\./g, " "); // for "Fathers.Day.2011.DC.BDRiP" strings
    var year = title.match(/(.+)\s((19|20)\d{2})\s?/i);
    if(year !== null && year[2] !== undefined) {
        return parseInt(year[2], 10);
    }
    return 1900;
};
exports.yearFromString = yearFromString;

/**
 * Extracts Film Title from Movie-Filename-String
 *
 * Takes everything from the Start of the string to the first year-string
 * If there's no year, then the whole string is returned with stripped periods
 *
 * @param string string A string containing a Movie-Title. Must have a year
 * @returns {String} The Title of the Movie; If no year, then returns string
 */
var getFilmTitleFromString = function(string) {
    string = string.replace(/\./g, " "); // for "Fathers.Day.2011.DC.BDRiP" strings
    string = string.replace(/\’/g, "");
    var title = string.match(/(.+)\s(19|20)\d{2}\s?/i); // everything til the first 4-digit year followed by space or nothing
    if(title !== null) {
        return title[1];
    }
    return string;
};
exports.getFilmTitleFromString = getFilmTitleFromString;

/*
 * Extracts Resolution/Format from given String
 *
 * only returns first occurence
 *
 * TODO: Cam, TS with .CAM. and .TS.
 */
var getResFromString = function(string) {
    if(typeof string !== 'string') return false;
    var formats = utils.resolutions;
    var regex = new RegExp(formats.join('|'), 'i');
    var results = regex.exec(string);
    return(results === null) ? false : results[0];
};
exports.getResFromString = getResFromString;

/*
 * retrieves RSS content from all RSS-Movie-Feeds
 *
 */
var rssGet = function(callback) {
    utils.log('start reading feeds...');
    var Scrapper = require(__dirname + '/scrapper.js');
    var items = [];
    var feedCount = config.feeds.Movies.feeds.length;
    var feedcounter = 0;
    var processItems = function(err, rss) {
        if (err === null) {
            var i, title, regex;
            for (i in rss.items) {
                for (title in config.feeds.Movies.queries) {
                    regex = new RegExp(config.feeds.Movies.queries[title].title, 'g');
                    if (regex.exec(rss.items[i].title)) {
                        items.push(rss.items[i]);
                    }
                }
            }
        }
        feedcounter++;
        if (feedcounter === feedCount) {
            callback(items);
        }
    };
    utils.log('reading from total of: ' + feedCount + ' feeds');
    for (var i = 0; i < feedCount; i++) {
        utils.log(config.feeds.Movies.feeds[i]);
        Scrapper.scrap(config.feeds.Movies.feeds[i], function(err, rss) {
            utils.log(rss);
            utils.log(err);
            processItems(err, rss);
        });
    }
};

/*
 * Clean-up/remove old entries after n-days
 */
exports.cleanUp = function(n, callback) {
   utils.log('-');
   var expire = new Date(new Date().setDate(new Date().getDate()-n)).toISOString();
   utils.log('removing all entries that were last updated before ' + n + ' days...');
   utils.log('> removing everything older than ' + expire);
   Movie.model.find({lastUpdate : {$lt : expire}}).remove(function(err){
      utils.log(err);
      utils.log('removed!');
      utils.log('-');
   });
   callback();
};

// moviedb return for movieInfo
/*

{ adult: false,
  backdrop_path: '/4IamAvByBWnclmbwlnZB8otMyB7.jpg',
  belongs_to_collection: null,
  budget: 0,
  genres: [ { id: 35, name: 'Comedy' }, { id: 18, name: 'Drama' } ],
  homepage: '',
  id: 243684,
  imdb_id: 'tt3179568',
  original_language: 'en',
  original_title: 'Men, Women & Children',
  overview: 'Follows the story of a group of high school teenagers and their parents as they attempt to navigate the many ways the internet has changed their relationships, their communication, their self-image, and their love lives.',
  popularity: 4.07571172700321,
  poster_path: '/5odPwDRx5rYaA4GX85w7cUgYDrX.jpg',
  production_companies:
   [ { name: 'Right of Way Films', id: 32157 },
     { name: 'Paramount Pictures', id: 4 } ],
  production_countries: [ { iso_3166_1: 'US', name: 'United States of America' } ],
  release_date: '2014-10-17',
  revenue: 0,
  runtime: 116,
  spoken_languages: [ { iso_639_1: 'en', name: 'English' } ],
  status: 'Released',
  tagline: 'Discover how little you know about the people you know.',
  title: 'Men, Women & Children',
  video: false,
  vote_average: 6.4,
  vote_count: 30 }

*/

/**
 * deprecated IMDB-Functions
 * IMDB has no API to officialy retrieve Movie-information
 *
 */

/*
exports.getImdbFromTitle = function(title, callback) {
    var imdb = require('imdb-api');
    imdb.getReq({
        name: title
    }, function(err, info) {
        callback(err, info);
    });
};

exports.getImdbFromString = function(string, callback) {
    var imdb = require('imdb-api');
    var title = getFilmTitleFromString(string);
    if (title) {
        imdb.getReq({
            name: title
        }, function(err, info) {
            callback(err, info);
        });
    }
    else {
        callback(false);
    }
};
*/<|endoftext|>


<commit_before>_renderVideo = _renderVideo || {}

_renderVideo.native = function (parent, options, err) {
  let vid = document.createElement('video')
  vid.poster = options.poster
  vid.className = 'videobackground__frame'
  vid.setAttribute('autoplay', 'true')
  if(options.loop) vid.setAttribute('loop', 'true')
  options.sources.forEach((el) => vid.appendChild(createSource.apply(null, el)))
  parent.appendChild(vid)
  return { element: vid }
}

function createSource (src, type) {
  return _.extend(
    document.createElement('source'),
    { src, type }
  )
}

<commit_msg>Check the volume option in the 'native' player<commit_after>_renderVideo = _renderVideo || {}

_renderVideo.native = function (parent, options, err) {
  let vid = document.createElement('video')
  vid.poster = options.poster
  vid.className = 'videobackground__frame'
  vid.setAttribute('autoplay', 'true')
  vid.volume = options.volume
  if(options.loop) vid.setAttribute('loop', 'true')
  options.sources.forEach((el) => vid.appendChild(createSource.apply(null, el)))
  parent.appendChild(vid)
  return { element: vid }
}

function createSource (src, type) {
  return _.extend(
    document.createElement('source'),
    { src, type }
  )
}

<|endoftext|>


<commit_before>var _ = require("underscore")._;

ArgParser = function(opts, parserOpts) {
  parserOpts = parserOpts || {};
  this.print = parserOpts.printFunc || function(str) {
    require("sys").puts(str);
    process.exit(0);
  };
  this.script = parserOpts.script || process.argv[0] + " "
        + require('path').basename(process.argv[1])
  this.printHelp = parserOpts.printHelp;
  this.help = parserOpts.help || "";
  if(this.printHelp == undefined)
    this.printHelp = true;

  this.commands = {};

  this.opts = opts || [];
}

ArgParser.prototype = {
  command : function(command, opts, callback, help) {
    this.commands[command] = {
      opts : opts,
      callback: callback,
      help: help,
      name: command
    }
  },

  parseArgs : function(argv) {
    var argv = argv || process.argv.slice(2);

    var command;
    if(JSON.stringify(this.commands) != "{}") {
      if(argv.length && new Arg(argv[0]).isValue())
        command = argv[0];

      if(!command) {
        // no command but command expected e.g. 'git --version'
        this.opts.command = {
          position: 0,
          help: 'one of: ' + _(this.commands).keys().join(", ")
        }
      }
      else {
        // command specified e.g. 'git add -p'
        command = this.commands[command];
        this.opts = _(command.opts).extend(this.opts);
        this.script += " " + command.name;

        if(command.help)
          this.help = command.help;
      }
    }
    if(this.opts.length == undefined) {
      // options is a hash not an array
      this.opts = _(this.opts).map(function(opt, name) {
        opt.name = name;
        return opt;
      });
    }
    this.options = this.opts.map(function(opt) {
      return new Option(opt);
    });

    var options = this.parse(argv);

    if(command && command.callback)
      command.callback(options);
    return options;
  },

  parse : function(args) {
    if(this.printHelp && (args.indexOf("--help") != -1
         || args.indexOf("-h") != -1))
      this.print(this.helpString());

    var ret = {};
    this.options.forEach(function(option) {
      ret[option.name] = option.default;
    }, this);

    args = args.concat([""]).map(function(arg) {
      return new Arg(arg);
    });
    var positionals = [];
    var that = this;

    args.reduce(function(arg, val) {
      /* word */
      if(arg.isValue()) {
        positionals.push(arg.value);
      }
      /* -c */
      else if(arg.chars) {
        /* -cfv */
        (arg.chars).forEach(function(ch) {
          ret[this.optName(ch)] = this.default(ch);
        }, that);
        /* -c 3 */
        if(val.isValue()) {
          if(that.expectsValue(arg.lastChar)) {
            ret[that.optName(arg.lastChar)] = val.value;
            return new Arg(""); // skip next turn - swallow arg
          }
        }
      }
      /* --config=tests.json */
      else if(arg.lg) {
        var value = arg.value;
        /* --debug */
        if(value == undefined)
          value = that.default(arg.lg)
        ret[that.optName(arg.lg)] = value;
      }
      return val;
    });

    positionals.forEach(function(pos, index) {
      ret[this.optName(index)] = pos;
    }, this);

    // exit if required arg isn't present
    this.options.forEach(function(option) {
      if(option.required && !ret[option.name])
        this.print(option.name + " argument is required");
    }, this);

    return ret;
  },

  helpString : function() {
    var str = "Usage: " + this.script;

    var positionals = this.options.filter(function(opt) {
      return opt.position != undefined;
    }).sort(function(opt1, opt2) {
      return opt1.position > opt2.position;
    });
    // assume there are no gaps in the specified pos. args
    positionals.forEach(function(pos) {
      str += " <" + (pos.name || "arg" + pos.position) + ">";
    });
    str += " [options]\n\n";

    positionals.forEach(function(pos) {
      str += "<" + pos.name + ">\t\t" + (pos.help || "") + "\n";
    });
    str += "\noptions:\n"

    this.options.forEach(function(option) {
      if(option.position == undefined)
        str += option.string + "\t\t" + (option.help || "") + "\n";
    });
    return str + "\n" + this.help;
  },

  option : function(arg) {
    // get the specified option for this parsed arg
    var match = new Option({});
    this.options.forEach(function(option) {
      if(option.matches(arg))
        match = option;
    });
    return match;
  },

  optName : function(arg) {
    var option = this.option(arg);
    return this.option(arg).name || arg;
  },

  default : function(arg) {
    return this.option(arg).default || true;
  },

  expectsValue : function(arg) {
    return this.option(arg).expectsValue();
  },
}

/* an option is what's specified by the user in options hash */
Option = function(opt) {
  this.string = opt.string || (opt.name ? "--" + opt.name : "");
  var matches = /^(?:\-(\w+?)(?:\s+([^-][^\s]*))?)?\,?\s*(?:\-\-(.+?)(?:=(.+))?)?$/
                .exec(this.string);
  this.sh = matches[1]; // e.g. -v
  this.metavar = matches[2] || matches[4]; // e.g. PATH from '--config=PATH'
  this.lg = matches[3]; // e.g. --verbose

  this.name = opt.name || this.lg || this.sh;
  this.default = opt.default;
  this.help = opt.help;
  this.position = opt.position;
  this.required = opt.required;
}

Option.prototype = {
  matches : function(arg) {
    return this.lg == arg || this.sh == arg || this.position == arg;
  },

  expectsValue : function() {
    return this.metavar || this.default;
  }
}

/* an arg is an item that's actually parsed from the command line */
Arg = function(str) {
  // "-l", "log.txt", or "--logfile=log.txt"
  this.str = str;
}

Arg.prototype = {
  shRegex : /^\-(\w+?)$/,
  lgRegex : /^\-\-(.+?)(?:=(.+))?$/,
  valRegex : /^[^\-]/,

  get chars() {
    var matches = this.shRegex.exec(this.str);
    return matches && matches[1].split("");
  },

  get value() {
    if(this.str) {
      var val = this.valRegex.test(this.str) ? this.str
                  : this.lgRegex.exec(this.str)[2];
      try { // try to infer type by JSON parsing the string
        val = JSON.parse(val)
      } catch(e) {}
      return val;
    }
  },

  get lg() {
    var matches = this.lgRegex.exec(this.str);
    return matches && matches[1];
  },

  get lastChar() {
    return this.str[this.str.length - 1];
  },

  isValue : function() {
    return this.str && this.valRegex.test(this.str);
  }
}

module.exports = function(opts, parserOpts) {
  return new ArgParser(opts, parserOpts);
}
<commit_msg>'options' shortcut to parseArgs()<commit_after>var _ = require("underscore")._;

ArgParser = function(opts, parserOpts) {
  parserOpts = parserOpts || {};
  this.print = parserOpts.printFunc || function(str) {
    require("sys").puts(str);
    process.exit(0);
  };
  this.script = parserOpts.script || process.argv[0] + " "
        + require('path').basename(process.argv[1])
  this.printHelp = parserOpts.printHelp;
  this.help = parserOpts.help || "";
  if(this.printHelp == undefined)
    this.printHelp = true;

  this.commands = {};

  this.opts = opts || [];
}

ArgParser.prototype = {
  get options() { return this.parseArgs(); },

  command : function(command, opts, callback, help) {
    this.commands[command] = {
      opts : opts,
      callback: callback,
      help: help,
      name: command
    }
  },

  parseArgs : function(argv) {
    var argv = argv || process.argv.slice(2);

    var command;
    if(JSON.stringify(this.commands) != "{}") {
      if(argv.length && new Arg(argv[0]).isValue())
        command = argv[0];

      if(!command) {
        // no command but command expected e.g. 'git --version'
        this.opts.command = {
          position: 0,
          help: 'one of: ' + _(this.commands).keys().join(", ")
        }
      }
      else {
        // command specified e.g. 'git add -p'
        command = this.commands[command];
        this.opts = _(command.opts).extend(this.opts);
        this.script += " " + command.name;

        if(command.help)
          this.help = command.help;
      }
    }
    if(this.opts.length == undefined) {
      // opts is a hash not an array
      this.opts = _(this.opts).map(function(opt, name) {
        opt.name = name;
        return opt;
      });
    }
    this.opts = this.opts.map(function(opt) {
      return new Opt(opt);
    });

    var options = this.parse(argv);

    if(command && command.callback)
      command.callback(options);
    return options;
  },

  parse : function(args) {
    if(this.printHelp && (args.indexOf("--help") != -1
         || args.indexOf("-h") != -1))
      this.print(this.helpString());

    var ret = {};
    this.opts.forEach(function(opt) {
      ret[opt.name] = opt.default;
    }, this);

    args = args.concat([""]).map(function(arg) {
      return new Arg(arg);
    });
    var positionals = [];
    var that = this;

    args.reduce(function(arg, val) {
      /* word */
      if(arg.isValue()) {
        positionals.push(arg.value);
      }
      /* -c */
      else if(arg.chars) {
        /* -cfv */
        (arg.chars).forEach(function(ch) {
          ret[this.optName(ch)] = this.default(ch);
        }, that);
        /* -c 3 */
        if(val.isValue()) {
          if(that.expectsValue(arg.lastChar)) {
            ret[that.optName(arg.lastChar)] = val.value;
            return new Arg(""); // skip next turn - swallow arg
          }
        }
      }
      /* --config=tests.json */
      else if(arg.lg) {
        var value = arg.value;
        /* --debug */
        if(value == undefined)
          value = that.default(arg.lg)
        ret[that.optName(arg.lg)] = value;
      }
      return val;
    });

    positionals.forEach(function(pos, index) {
      ret[this.optName(index)] = pos;
    }, this);

    // exit if required arg isn't present
    this.opts.forEach(function(opt) {
      if(opt.required && !ret[opt.name])
        this.print(opt.name + " argument is required");
    }, this);

    return ret;
  },

  helpString : function() {
    var str = "Usage: " + this.script;

    var positionals = this.opts.filter(function(opt) {
      return opt.position != undefined;
    }).sort(function(opt1, opt2) {
      return opt1.position > opt2.position;
    });
    // assume there are no gaps in the specified pos. args
    positionals.forEach(function(pos) {
      str += " <" + (pos.name || "arg" + pos.position) + ">";
    });
    str += " [options]\n\n";

    positionals.forEach(function(pos) {
      str += "<" + pos.name + ">\t\t" + (pos.help || "") + "\n";
    });
    str += "\noptions:\n"

    this.opts.forEach(function(opt) {
      if(opt.position == undefined)
        str += opt.string + "\t\t" + (opt.help || "") + "\n";
    });
    return str + "\n" + this.help;
  },

  opt : function(arg) {
    // get the specified option for this parsed arg
    var match = new Opt({});
    this.opts.forEach(function(opt) {
      if(opt.matches(arg))
        match = opt;
    });
    return match;
  },

  optName : function(arg) {
    var opt = this.opt(arg);
    return this.opt(arg).name || arg;
  },

  default : function(arg) {
    return this.opt(arg).default || true;
  },

  expectsValue : function(arg) {
    return this.opt(arg).expectsValue();
  },
}

/* an opt is what's specified by the user in opts hash */
Opt = function(opt) {
  this.string = opt.string || (opt.name ? "--" + opt.name : "");
  var matches = /^(?:\-(\w+?)(?:\s+([^-][^\s]*))?)?\,?\s*(?:\-\-(.+?)(?:=(.+))?)?$/
                .exec(this.string);
  this.sh = matches[1]; // e.g. -v
  this.metavar = matches[2] || matches[4]; // e.g. PATH from '--config=PATH'
  this.lg = matches[3]; // e.g. --verbose

  this.name = opt.name || this.lg || this.sh;
  this.default = opt.default;
  this.help = opt.help;
  this.position = opt.position;
  this.required = opt.required;
}

Opt.prototype = {
  matches : function(arg) {
    return this.lg == arg || this.sh == arg || this.position == arg;
  },

  expectsValue : function() {
    return this.metavar || this.default;
  }
}

/* an arg is an item that's actually parsed from the command line */
Arg = function(str) {
  // "-l", "log.txt", or "--logfile=log.txt"
  this.str = str;
}

Arg.prototype = {
  shRegex : /^\-(\w+?)$/,
  lgRegex : /^\-\-(.+?)(?:=(.+))?$/,
  valRegex : /^[^\-]/,

  get chars() {
    var matches = this.shRegex.exec(this.str);
    return matches && matches[1].split("");
  },

  get value() {
    if(this.str) {
      var val = this.valRegex.test(this.str) ? this.str
                  : this.lgRegex.exec(this.str)[2];
      try { // try to infer type by JSON parsing the string
        val = JSON.parse(val)
      } catch(e) {}
      return val;
    }
  },

  get lg() {
    var matches = this.lgRegex.exec(this.str);
    return matches && matches[1];
  },

  get lastChar() {
    return this.str[this.str.length - 1];
  },

  isValue : function() {
    return this.str && this.valRegex.test(this.str);
  }
}

module.exports = function(opts, parserOpts) {
  return new ArgParser(opts, parserOpts);
}
<|endoftext|>


<commit_before>var querystring= require('querystring'),
    crypto= require('crypto'),
    https= require('https'),
    http= require('http'),
    URL= require('url'),
    OAuthUtils= require('./_utils'),
    OAuthRoutes= require('./authRoutes');

exports.OAuth2= function(clientId, clientSecret, baseSite, authorizePath, accessTokenPath, customHeaders) {
  this._clientId= clientId;
  this._clientSecret= clientSecret;
  this._baseSite= baseSite;
  this._authorizeUrl= authorizePath || "/oauth/authorize";
  this._accessTokenUrl= accessTokenPath || "/oauth/access_token";
  this._accessTokenName= "access_token";
  this._authMethod= "Bearer";
  this._customHeaders = customHeaders || {};
  this._useAuthorizationHeaderForGET= false;
}

// This 'hack' method is required for sites that don't use
// 'access_token' as the name of the access token (for requests).
// ( http://tools.ietf.org/html/draft-ietf-oauth-v2-16#section-7 )
// it isn't clear what the correct value should be atm, so allowing
// for specific (temporary?) override for now.
exports.OAuth2.prototype.setAccessTokenName= function ( name ) {
  this._accessTokenName= name;
}

// Sets the authorization method for Authorization header.
// e.g. Authorization: Bearer <token>  # "Bearer" is the authorization method.
exports.OAuth2.prototype.setAuthMethod = function ( authMethod ) {
  this._authMethod = authMethod;
};

// If you use the OAuth2 exposed 'get' method (and don't construct your own _request call )
// this will specify whether to use an 'Authorize' header instead of passing the access_token as a query parameter
exports.OAuth2.prototype.useAuthorizationHeaderforGET = function(useIt) {
  this._useAuthorizationHeaderForGET= useIt;
}

exports.OAuth2.prototype._getAccessTokenUrl= function() {
  return this._baseSite + this._accessTokenUrl; /* + "?" + querystring.stringify(params); */
}

// Build the authorization header. In particular, build the part after the colon.
// e.g. Authorization: Bearer <token>  # Build "Bearer <token>"
exports.OAuth2.prototype.buildAuthHeader= function(token) {
  return this._authMethod + ' ' + token;
};

exports.OAuth2.prototype._chooseHttpLibrary= function( parsedUrl ) {
  var http_library= https;
  // As this is OAUth2, we *assume* https unless told explicitly otherwise.
  if( parsedUrl.protocol != "https:" ) {
    http_library= http;
  }
  return http_library;
};

exports.OAuth2.prototype._request= function(method, url, headers, post_body, access_token, callback) {

  var parsedUrl= URL.parse( url, true );
  if( parsedUrl.protocol == "https:" && !parsedUrl.port ) {
    parsedUrl.port= 443;
  }

  var http_library= this._chooseHttpLibrary( parsedUrl );

  var realHeaders= {};
  for( var key in this._customHeaders ) {
    realHeaders[key]= this._customHeaders[key];
  }
  if( headers ) {
    for(var key in headers) {
      realHeaders[key] = headers[key];
    }
  }
  realHeaders['Host']= parsedUrl.host;

  if (!realHeaders['User-Agent']) {
    realHeaders['User-Agent'] = 'Node-oauth';
  }

  if( post_body ) {
      if ( Buffer.isBuffer(post_body) ) {
          realHeaders["Content-Length"]= post_body.length;
      } else {
          realHeaders["Content-Length"]= Buffer.byteLength(post_body);
      }
  } else {
      realHeaders["Content-length"]= 0;
  }

  if( access_token && !('Authorization' in realHeaders)) {
    if( ! parsedUrl.query ) parsedUrl.query= {};
    parsedUrl.query[this._accessTokenName]= access_token;
  }

  var queryStr= querystring.stringify(parsedUrl.query);
  if( queryStr ) queryStr=  "?" + queryStr;
  var options = {
    host:parsedUrl.hostname,
    port: parsedUrl.port,
    path: parsedUrl.pathname + queryStr,
    method: method,
    headers: realHeaders
  };

  this._executeRequest( http_library, options, post_body, callback );
}

exports.OAuth2.prototype._executeRequest= function( http_library, options, post_body, callback ) {
  // Some hosts *cough* google appear to close the connection early / send no content-length header
  // allow this behaviour.
  var allowEarlyClose= OAuthUtils.isAnEarlyCloseHost(options.host);
  var callbackCalled= false;
  function passBackControl( response, result ) {
    if(!callbackCalled) {
      callbackCalled=true;
      if( !(response.statusCode >= 200 && response.statusCode <= 299) && (response.statusCode != 301) && (response.statusCode != 302) ) {
        callback({ statusCode: response.statusCode, data: result });
      } else {
        callback(null, result, response);
      }
    }
  }

  var result= "";

  var request = http_library.request(options);
  request.on('response', function (response) {
    response.on("data", function (chunk) {
      result+= chunk
    });
    response.on("close", function (err) {
      if( allowEarlyClose ) {
        passBackControl( response, result );
      }
    });
    response.addListener("end", function () {
      passBackControl( response, result );
    });
  });
  request.on('error', function(e) {
    callbackCalled= true;
    callback(e);
  });

  if( (options.method == 'POST' || options.method == 'PUT') && post_body ) {
     request.write(post_body);
  }
  request.end();
}

exports.OAuth2.prototype.getAuthorizeUrl= function( params ) {
  var params= params || {};
  params['client_id'] = this._clientId;
  return this._baseSite + this._authorizeUrl + "?" + querystring.stringify(params);
}

exports.OAuth2.prototype.getOAuthAccessToken= function(code, params, callback) {
  var params= params || {};
  params['client_id'] = this._clientId;
  params['client_secret'] = this._clientSecret;
  var codeParam = (params.grant_type === 'refresh_token') ? 'refresh_token' : 'code';
  params[codeParam]= code;

  var post_data= querystring.stringify( params );
  var post_headers= {
       'Content-Type': 'application/x-www-form-urlencoded'
   };

  this._request("POST", this._getAccessTokenUrl(), post_headers, post_data, null, function(error, data, response) {
    if( error )  callback(error);
    else {
      var results;
      try {
        // As of http://tools.ietf.org/html/draft-ietf-oauth-v2-07
        // responses should be in JSON
        results= JSON.parse( data );
      }
      catch(e) {
        // .... However both Facebook + Github currently use rev05 of the spec
        // and neither seem to specify a content-type correctly in their response headers :(
        // clients of these services will suffer a *minor* performance cost of the exception
        // being thrown
        results= querystring.parse( data );
      }
      var access_token= results["access_token"];
      var refresh_token= results["refresh_token"];
      delete results["refresh_token"];
      callback(null, access_token, refresh_token, results); // callback results =-=
    }
  });
}

// Deprecated
exports.OAuth2.prototype.getProtectedResource= function(url, access_token, callback) {
  this._request("GET", url, {}, "", access_token, callback );
}

exports.OAuth2.prototype.get= function(url, access_token, callback) {
  if( this._useAuthorizationHeaderForGET ) {
    var headers= {'Authorization': this.buildAuthHeader(access_token) }
    access_token= null;
  }
  else {
    headers= {};
  }
  this._request("GET", url, headers, "", access_token, callback );
}
<commit_msg>Fixed auth route bug<commit_after>var querystring= require('querystring'),
    crypto= require('crypto'),
    https= require('https'),
    http= require('http'),
    URL= require('url');

exports.OAuth2= function(clientId, clientSecret, baseSite, authorizePath, accessTokenPath, customHeaders) {
  this._clientId= clientId;
  this._clientSecret= clientSecret;
  this._baseSite= baseSite;
  this._authorizeUrl= authorizePath || "/oauth/authorize";
  this._accessTokenUrl= accessTokenPath || "/oauth/access_token";
  this._accessTokenName= "access_token";
  this._authMethod= "Bearer";
  this._customHeaders = customHeaders || {};
  this._useAuthorizationHeaderForGET= false;
}

// This 'hack' method is required for sites that don't use
// 'access_token' as the name of the access token (for requests).
// ( http://tools.ietf.org/html/draft-ietf-oauth-v2-16#section-7 )
// it isn't clear what the correct value should be atm, so allowing
// for specific (temporary?) override for now.
exports.OAuth2.prototype.setAccessTokenName= function ( name ) {
  this._accessTokenName= name;
}

// Sets the authorization method for Authorization header.
// e.g. Authorization: Bearer <token>  # "Bearer" is the authorization method.
exports.OAuth2.prototype.setAuthMethod = function ( authMethod ) {
  this._authMethod = authMethod;
};

// If you use the OAuth2 exposed 'get' method (and don't construct your own _request call )
// this will specify whether to use an 'Authorize' header instead of passing the access_token as a query parameter
exports.OAuth2.prototype.useAuthorizationHeaderforGET = function(useIt) {
  this._useAuthorizationHeaderForGET= useIt;
}

exports.OAuth2.prototype._getAccessTokenUrl= function() {
  return this._baseSite + this._accessTokenUrl; /* + "?" + querystring.stringify(params); */
}

// Build the authorization header. In particular, build the part after the colon.
// e.g. Authorization: Bearer <token>  # Build "Bearer <token>"
exports.OAuth2.prototype.buildAuthHeader= function(token) {
  return this._authMethod + ' ' + token;
};

exports.OAuth2.prototype._chooseHttpLibrary= function( parsedUrl ) {
  var http_library= https;
  // As this is OAUth2, we *assume* https unless told explicitly otherwise.
  if( parsedUrl.protocol != "https:" ) {
    http_library= http;
  }
  return http_library;
};

exports.OAuth2.prototype._request= function(method, url, headers, post_body, access_token, callback) {

  var parsedUrl= URL.parse( url, true );
  if( parsedUrl.protocol == "https:" && !parsedUrl.port ) {
    parsedUrl.port= 443;
  }

  var http_library= this._chooseHttpLibrary( parsedUrl );

  var realHeaders= {};
  for( var key in this._customHeaders ) {
    realHeaders[key]= this._customHeaders[key];
  }
  if( headers ) {
    for(var key in headers) {
      realHeaders[key] = headers[key];
    }
  }
  realHeaders['Host']= parsedUrl.host;

  if (!realHeaders['User-Agent']) {
    realHeaders['User-Agent'] = 'Node-oauth';
  }

  if( post_body ) {
      if ( Buffer.isBuffer(post_body) ) {
          realHeaders["Content-Length"]= post_body.length;
      } else {
          realHeaders["Content-Length"]= Buffer.byteLength(post_body);
      }
  } else {
      realHeaders["Content-length"]= 0;
  }

  if( access_token && !('Authorization' in realHeaders)) {
    if( ! parsedUrl.query ) parsedUrl.query= {};
    parsedUrl.query[this._accessTokenName]= access_token;
  }

  var queryStr= querystring.stringify(parsedUrl.query);
  if( queryStr ) queryStr=  "?" + queryStr;
  var options = {
    host:parsedUrl.hostname,
    port: parsedUrl.port,
    path: parsedUrl.pathname + queryStr,
    method: method,
    headers: realHeaders
  };

  this._executeRequest( http_library, options, post_body, callback );
}

exports.OAuth2.prototype._executeRequest= function( http_library, options, post_body, callback ) {
  // Some hosts *cough* google appear to close the connection early / send no content-length header
  // allow this behaviour.
  var allowEarlyClose= OAuthUtils.isAnEarlyCloseHost(options.host);
  var callbackCalled= false;
  function passBackControl( response, result ) {
    if(!callbackCalled) {
      callbackCalled=true;
      if( !(response.statusCode >= 200 && response.statusCode <= 299) && (response.statusCode != 301) && (response.statusCode != 302) ) {
        callback({ statusCode: response.statusCode, data: result });
      } else {
        callback(null, result, response);
      }
    }
  }

  var result= "";

  var request = http_library.request(options);
  request.on('response', function (response) {
    response.on("data", function (chunk) {
      result+= chunk
    });
    response.on("close", function (err) {
      if( allowEarlyClose ) {
        passBackControl( response, result );
      }
    });
    response.addListener("end", function () {
      passBackControl( response, result );
    });
  });
  request.on('error', function(e) {
    callbackCalled= true;
    callback(e);
  });

  if( (options.method == 'POST' || options.method == 'PUT') && post_body ) {
     request.write(post_body);
  }
  request.end();
}

exports.OAuth2.prototype.getAuthorizeUrl= function( params ) {
  var params= params || {};
  params['client_id'] = this._clientId;
  return this._baseSite + this._authorizeUrl + "?" + querystring.stringify(params);
}

exports.OAuth2.prototype.getOAuthAccessToken= function(code, params, callback) {
  var params= params || {};
  params['client_id'] = this._clientId;
  params['client_secret'] = this._clientSecret;
  var codeParam = (params.grant_type === 'refresh_token') ? 'refresh_token' : 'code';
  params[codeParam]= code;

  var post_data= querystring.stringify( params );
  var post_headers= {
       'Content-Type': 'application/x-www-form-urlencoded'
   };

  this._request("POST", this._getAccessTokenUrl(), post_headers, post_data, null, function(error, data, response) {
    if( error )  callback(error);
    else {
      var results;
      try {
        // As of http://tools.ietf.org/html/draft-ietf-oauth-v2-07
        // responses should be in JSON
        results= JSON.parse( data );
      }
      catch(e) {
        // .... However both Facebook + Github currently use rev05 of the spec
        // and neither seem to specify a content-type correctly in their response headers :(
        // clients of these services will suffer a *minor* performance cost of the exception
        // being thrown
        results= querystring.parse( data );
      }
      var access_token= results["access_token"];
      var refresh_token= results["refresh_token"];
      delete results["refresh_token"];
      callback(null, access_token, refresh_token, results); // callback results =-=
    }
  });
}

// Deprecated
exports.OAuth2.prototype.getProtectedResource= function(url, access_token, callback) {
  this._request("GET", url, {}, "", access_token, callback );
}

exports.OAuth2.prototype.get= function(url, access_token, callback) {
  if( this._useAuthorizationHeaderForGET ) {
    var headers= {'Authorization': this.buildAuthHeader(access_token) }
    access_token= null;
  }
  else {
    headers= {};
  }
  this._request("GET", url, headers, "", access_token, callback );
}
<|endoftext|>