<commit_before>import Ember from 'ember';
import ENV from 'weather-app/config/environment';

export default Ember.Controller.extend({

  locationCity: 'Red Hook',
  locationState: 'NY',

  currentDate: new Date(),

  currentWeather: {},
  currentTemperature: 0,
  currentTemperatureMin: 0,
  currentTemperatureMax: 0,

  weathers: [],

  actions: {
    //Gets called asap to set up the current location, time, and weather.
    setupMethod: function(){
      var me = this;

      //For updating the clock
      setInterval(function(){
        me.set('currentDate', new Date());
      }, 60000);

      navigator.geolocation.getCurrentPosition(function(res){

        //Get current weather
        //forecast doesn't always return today ¯\_(ツ)_/¯
        me.getCurrentWeather(
          res.coords.latitude,
          res.coords.longitude,
          function(success, weather){
            var currentWeather = me.get('currentWeather');
            weather.forecasts = currentWeather.forecasts;
            me.set('currentWeather', weather);
          }
        );

        //Get forecast weather
        me.getForecast(
          res.coords.latitude,
          res.coords.longitude,
          function(success, weather){
            var weathers = me.get('weathers');
            var totalDays = me.get('totalDays');

            var i = 0;
            var todaysDate = new Date();
            if(weather[0].date.getDate() === todaysDate.getDate()){
              var currentWeather = me.get('currentWeather');
              Ember.set(currentWeather, 'forecasts', weather[0].forecasts);
              me.set('currentWeather', currentWeather);
              i++;
            }
            for(i; i < weather.length; i++){
              if(weathers.length <= 4){
                weathers.pushObject(weather[i]);
              }
            }
          }
        );

        //Get current location
        me.getCurrentLocation(
          res.coords.latitude,
          res.coords.longitude,
          function(city, state){
            me.set('locationCity', city);
            me.set('locationState', state);
          }
        );

      });
    },

    /**
      *Show the breakdown for the current day
    **/
    showBreakdown: function(){
      var weatherObject = this.get('currentWeather');
      $('#weather-card-' + weatherObject.date.getDate() + '-modal').modal('show');
    }

  },

  /**
    *Get the current weather.
    *@param {float} latitude The latitude to look up.
    *@param {float} longitude The longitude to look up.
    *@param {function} callback Callback function, returns a weather object.
  **/
  getCurrentWeather: function(latitude, longitude, callback){
    var me = this;
    var weatherUrl = 'http://api.openweathermap.org/data/2.5/weather';
    weatherUrl += '?lat=' + latitude;
    weatherUrl += '&lon=' + longitude;
    weatherUrl += '&units=imperial';
    weatherUrl += '&appid=' + ENV.APP.openWeatherMapKey;

    $.ajax({
      url: weatherUrl,
      success: function(response){
        var weatherObj = me.parseWeather(response);
        if(callback){
          callback(true, weatherObj);
        }
      }
    });
  },

  /**
    *Get the forecast.
    *@param {float} latitude The latitude to look up.
    *@param {float} longitude The longitude to look up.
    *@param {function} callback Callback function, returns success and an array
    *                           of weather objects.
  **/
  getForecast: function(latitude, longitude, callback){
    var me = this;
    var weatherUrl = 'http://api.openweathermap.org/data/2.5/forecast';
    weatherUrl += '?lat=' + latitude;
    weatherUrl += '&lon=' + longitude;
    weatherUrl += '&units=imperial';
    weatherUrl += '&appid=' + ENV.APP.openWeatherMapKey;

    $.ajax({
      url: weatherUrl,
      success: function(response){
        var weatherObjects = {};
        //Create weather objects and group by day
        for(var i = 0; i < response.list.length; i++){
          var weather = response.list[i];

          //Create weather object to make things a bit easier
          var weatherObj = me.parseWeather(weather);

          //Group by the day and exclude
          if(!weatherObjects[weatherObj.date.getDate()]){
            weatherObjects[weatherObj.date.getDate()] = {forecasts: []};
          }

          //Push into the forecasts array
          var weatherObject = weatherObjects[weatherObj.date.getDate()];
          weatherObject.forecasts.push(weatherObj);
        }

        //Get averages
        for(var i in weatherObjects){
          var weatherObject = weatherObjects[i];

          var averageTemp    = 0;
          var averageTempMax = 0;
          var averageTempMin = 0;

          for(var j = 0; j < weatherObject.forecasts.length; j++){
            var forecast = weatherObject.forecasts[j];
            averageTemp    += forecast.temp;
            averageTempMax += forecast.tempMax;
            averageTempMin += forecast.tempMin;
          }
          averageTemp    = Math.round(averageTemp / j);
          averageTempMax = Math.round(averageTempMax / j);
          averageTempMin = Math.round(averageTempMin / j);

          weatherObject.averageTemp    = averageTemp;
          weatherObject.averageTempMax = averageTempMax;
          weatherObject.averageTempMin = averageTempMin;
          weatherObject.currentTemp    = Math.round(weatherObject.forecasts[0].temp);

          weatherObject.date = weatherObject.forecasts[0].date;
          weatherObject.weatherId = weatherObject.forecasts[0].weatherId;
        }

        var weatherArray = [];
        for(var i in weatherObjects){
          weatherArray.push(weatherObjects[i]);
        }
        if(callback){
          callback(true, weatherArray);
        }
      }
    });
  },

  /**
    *Gets the city and state of a lat lng.
    *@param {float} latitude Latitude to look up.
    *@param {float} longitude Longitude to look up.
    *@param {function} callback Callback function, returns city and state.
  **/
  getCurrentLocation: function(latitude, longitude, callback){
    var reverseGeolocationUrl = 'https://maps.googleapis.com/maps/api/geocode/json';
    reverseGeolocationUrl += '?latlng=' + latitude + ',' + longitude;
    reverseGeolocationUrl += '&key=' + ENV.APP.googleKey;

    $.ajax({
      url: reverseGeolocationUrl,
      success: function(response){
        var city = null;
        var state = null;

        for(var i = 0; i < response.results.length; i++){
          var result = response.results[i];
          for(var j = 0; j < result.address_components.length; j++){
            var addressComponent = result.address_components[j];
            if(!state || !city){
              for(var k = 0; k < addressComponent.types.length; k++){
                var addressType = addressComponent.types[k];
                if(!city){
                  if(addressType === 'locality'){
                    city = addressComponent.short_name;
                  }
                }
                if(!state){
                  if(addressType === 'administrative_area_level_1'){
                    state = addressComponent.short_name;
                  }
                }
              }
            }
          }
        }

        if(callback){
          callback(city, state);
        }
      }
    });
  },

  //Would probably be better to put this into a model or a serializer somewhere
  //so we can utilize actual data objects of the framework.
  /**
    *Parse a weather response from the weather api into something a bit nicer
    *for what we are using it for.
    *@param {object} weatherObject A weather object from an api call.
    *@return {object} A parsed weather object.
  **/
  parseWeather: function(weatherObject){

    var weatherDate = new Date();
    if(weatherObject.dt_txt){
      weatherDate = new Date(weatherObject.dt_txt);
    }
    var weather = {
      temp: Math.round(weatherObject.main.temp),
      tempMax: Math.round(weatherObject.main.temp_max),
      tempMin: Math.round(weatherObject.main.temp_min),
      cloudPercentage: weatherObject.clouds.all,
      date: weatherDate,
      humidity: weatherObject.main.humidity,
      rain: 0,
      weatherId: weatherObject.weather[0].id,
      wind: weatherObject.wind,
      windDirection: weatherObject.wind.deg
    };

    //Rain isn't always returned if it isn't going to rain
    if(weatherObject.rain){
      if(weatherObject.rain['3h']){
        weather.rain = weatherObject.rain['3h'].toFixed(2);
      }
    }
    if(weatherObject.wind){
      if(weatherObject.wind.speed){
        weatherObject.wind.speed = Math.round(weatherObject.wind.speed);
      }
    }

    return weather;
  }
});
<commit_msg>those obo errors<commit_after>import Ember from 'ember';
import ENV from 'weather-app/config/environment';

export default Ember.Controller.extend({

  locationCity: 'Red Hook',
  locationState: 'NY',

  currentDate: new Date(),

  currentWeather: {},
  currentTemperature: 0,
  currentTemperatureMin: 0,
  currentTemperatureMax: 0,

  weathers: [],

  actions: {
    //Gets called asap to set up the current location, time, and weather.
    setupMethod: function(){
      var me = this;

      //For updating the clock
      setInterval(function(){
        me.set('currentDate', new Date());
      }, 60000);

      navigator.geolocation.getCurrentPosition(function(res){

        //Get current weather
        //forecast doesn't always return today ¯\_(ツ)_/¯
        me.getCurrentWeather(
          res.coords.latitude,
          res.coords.longitude,
          function(success, weather){
            var currentWeather = me.get('currentWeather');
            weather.forecasts = currentWeather.forecasts;
            me.set('currentWeather', weather);
          }
        );

        //Get forecast weather
        me.getForecast(
          res.coords.latitude,
          res.coords.longitude,
          function(success, weather){
            var weathers = me.get('weathers');
            var totalDays = me.get('totalDays');

            var i = 0;
            var todaysDate = new Date();
            if(weather[0].date.getDate() === todaysDate.getDate()){
              var currentWeather = me.get('currentWeather');
              Ember.set(currentWeather, 'forecasts', weather[0].forecasts);
              me.set('currentWeather', currentWeather);
              i++;
            }
            for(i; i < weather.length; i++){
              if(weathers.get('length') < 4){
                weathers.pushObject(weather[i]);
              }
            }
          }
        );

        //Get current location
        me.getCurrentLocation(
          res.coords.latitude,
          res.coords.longitude,
          function(city, state){
            me.set('locationCity', city);
            me.set('locationState', state);
          }
        );

      });
    },

    /**
      *Show the breakdown for the current day
    **/
    showBreakdown: function(){
      var weatherObject = this.get('currentWeather');
      $('#weather-card-' + weatherObject.date.getDate() + '-modal').modal('show');
    }

  },

  /**
    *Get the current weather.
    *@param {float} latitude The latitude to look up.
    *@param {float} longitude The longitude to look up.
    *@param {function} callback Callback function, returns a weather object.
  **/
  getCurrentWeather: function(latitude, longitude, callback){
    var me = this;
    var weatherUrl = 'http://api.openweathermap.org/data/2.5/weather';
    weatherUrl += '?lat=' + latitude;
    weatherUrl += '&lon=' + longitude;
    weatherUrl += '&units=imperial';
    weatherUrl += '&appid=' + ENV.APP.openWeatherMapKey;

    $.ajax({
      url: weatherUrl,
      success: function(response){
        var weatherObj = me.parseWeather(response);
        if(callback){
          callback(true, weatherObj);
        }
      }
    });
  },

  /**
    *Get the forecast.
    *@param {float} latitude The latitude to look up.
    *@param {float} longitude The longitude to look up.
    *@param {function} callback Callback function, returns success and an array
    *                           of weather objects.
  **/
  getForecast: function(latitude, longitude, callback){
    var me = this;
    var weatherUrl = 'http://api.openweathermap.org/data/2.5/forecast';
    weatherUrl += '?lat=' + latitude;
    weatherUrl += '&lon=' + longitude;
    weatherUrl += '&units=imperial';
    weatherUrl += '&appid=' + ENV.APP.openWeatherMapKey;

    $.ajax({
      url: weatherUrl,
      success: function(response){
        var weatherObjects = {};
        //Create weather objects and group by day
        for(var i = 0; i < response.list.length; i++){
          var weather = response.list[i];

          //Create weather object to make things a bit easier
          var weatherObj = me.parseWeather(weather);

          //Group by the day and exclude
          if(!weatherObjects[weatherObj.date.getDate()]){
            weatherObjects[weatherObj.date.getDate()] = {forecasts: []};
          }

          //Push into the forecasts array
          var weatherObject = weatherObjects[weatherObj.date.getDate()];
          weatherObject.forecasts.push(weatherObj);
        }

        //Get averages
        for(var i in weatherObjects){
          var weatherObject = weatherObjects[i];

          var averageTemp    = 0;
          var averageTempMax = 0;
          var averageTempMin = 0;

          for(var j = 0; j < weatherObject.forecasts.length; j++){
            var forecast = weatherObject.forecasts[j];
            averageTemp    += forecast.temp;
            averageTempMax += forecast.tempMax;
            averageTempMin += forecast.tempMin;
          }
          averageTemp    = Math.round(averageTemp / j);
          averageTempMax = Math.round(averageTempMax / j);
          averageTempMin = Math.round(averageTempMin / j);

          weatherObject.averageTemp    = averageTemp;
          weatherObject.averageTempMax = averageTempMax;
          weatherObject.averageTempMin = averageTempMin;
          weatherObject.currentTemp    = Math.round(weatherObject.forecasts[0].temp);

          weatherObject.date = weatherObject.forecasts[0].date;
          weatherObject.weatherId = weatherObject.forecasts[0].weatherId;
        }

        var weatherArray = [];
        for(var i in weatherObjects){
          weatherArray.push(weatherObjects[i]);
        }
        if(callback){
          callback(true, weatherArray);
        }
      }
    });
  },

  /**
    *Gets the city and state of a lat lng.
    *@param {float} latitude Latitude to look up.
    *@param {float} longitude Longitude to look up.
    *@param {function} callback Callback function, returns city and state.
  **/
  getCurrentLocation: function(latitude, longitude, callback){
    var reverseGeolocationUrl = 'https://maps.googleapis.com/maps/api/geocode/json';
    reverseGeolocationUrl += '?latlng=' + latitude + ',' + longitude;
    reverseGeolocationUrl += '&key=' + ENV.APP.googleKey;

    $.ajax({
      url: reverseGeolocationUrl,
      success: function(response){
        var city = null;
        var state = null;

        for(var i = 0; i < response.results.length; i++){
          var result = response.results[i];
          for(var j = 0; j < result.address_components.length; j++){
            var addressComponent = result.address_components[j];
            if(!state || !city){
              for(var k = 0; k < addressComponent.types.length; k++){
                var addressType = addressComponent.types[k];
                if(!city){
                  if(addressType === 'locality'){
                    city = addressComponent.short_name;
                  }
                }
                if(!state){
                  if(addressType === 'administrative_area_level_1'){
                    state = addressComponent.short_name;
                  }
                }
              }
            }
          }
        }

        if(callback){
          callback(city, state);
        }
      }
    });
  },

  //Would probably be better to put this into a model or a serializer somewhere
  //so we can utilize actual data objects of the framework.
  /**
    *Parse a weather response from the weather api into something a bit nicer
    *for what we are using it for.
    *@param {object} weatherObject A weather object from an api call.
    *@return {object} A parsed weather object.
  **/
  parseWeather: function(weatherObject){

    var weatherDate = new Date();
    if(weatherObject.dt_txt){
      weatherDate = new Date(weatherObject.dt_txt);
    }
    var weather = {
      temp: Math.round(weatherObject.main.temp),
      tempMax: Math.round(weatherObject.main.temp_max),
      tempMin: Math.round(weatherObject.main.temp_min),
      cloudPercentage: weatherObject.clouds.all,
      date: weatherDate,
      humidity: weatherObject.main.humidity,
      rain: 0,
      weatherId: weatherObject.weather[0].id,
      wind: weatherObject.wind,
      windDirection: weatherObject.wind.deg
    };

    //Rain isn't always returned if it isn't going to rain
    if(weatherObject.rain){
      if(weatherObject.rain['3h']){
        weather.rain = weatherObject.rain['3h'].toFixed(2);
      }
    }
    if(weatherObject.wind){
      if(weatherObject.wind.speed){
        weatherObject.wind.speed = Math.round(weatherObject.wind.speed);
      }
    }

    return weather;
  }
});
<|endoftext|>


<commit_before>var buttons = require('sdk/ui/button/action');
var tabs = require("sdk/tabs");

var button = buttons.ActionButton({
  id: "tweet-that",
  label: "Tweet that link",
  icon: {
    "16": "./icon-16.png",
    "32": "./icon-32.png",
    "64": "./icon-64.png"
  },
  onClick: handleClick
});

function handleClick(state) {
  var url = tabs.activeTab.url;
  var title = tabs.activeTab.title;

  tabs.open({
    url: "https://twitter.com/intent/tweet?url="
      + encodeURIComponent(url)
      + "&text=" + encodeURIComponent(title),
    inBackground: true,
    onOpen: function (tab) {
      tab.activate();
    }
  });
}
<commit_msg>use a panel instead of a new tab<commit_after>var { ToggleButton } = require('sdk/ui/button/toggle');
var tabs = require("sdk/tabs");

var button = ToggleButton({
  id: "tweet-that",
  label: "Tweet that link",
  icon: {
    "16": "./icon-16.png",
    "32": "./icon-32.png",
    "64": "./icon-64.png"
  },
  onClick: handleClick
});

var panel = require("sdk/panel").Panel({
  width: 320,
  height: 250,
  onHide: function() {
    button.state('window', {checked: false});
  }
});

function handleClick(state) {
  var twitter_uri = "https://twitter.com/intent/tweet?url=" +
    encodeURIComponent(tabs.activeTab.url) +
    "&text=" + encodeURIComponent(tabs.activeTab.title);
  panel.contentURL = twitter_uri;
  panel.show({ position: button });
}
<|endoftext|>


<commit_before>'use strict';

const BasicDBDriver = require('./basic-driver.js');

/**
 * Symbol used to indicate that there is an error on the query object.
 *
 * @private
 * @type {Symbol}
 */
const HAS_ERROR = Symbol();

/**
 * Symbol used to store list of temporary anchor tables created for the
 * transaction on the connection object.
 *
 * @private
 * @type {Symbol}
 */
const ANCHOR_TABLES = Symbol();

/**
 * Symbol used to mark a connection that must be destroyed upon release no matter
 * what.
 *
 * @private
 * @type {Symbol}
 */
const DESTROY = Symbol();

/**
 * MySQL database driver.
 *
 * @private
 * @memberof module:x2node-dbos
 * @inner
 * @extends {module:x2node-dbos~BasicDBDriver}
 * @implements {module:x2node-dbos.DBDriver}
 */
class MySQLDBDriver extends BasicDBDriver {

	constructor(options) {
		super(options);

		this._charset = ((options && options.databaseCharacterSet) || 'utf8');
	}

	supportsRowLocksWithAggregates() { return true; }

	safeLikePatternFromExpr(expr) {

		return `REPLACE(REPLACE(REPLACE(${expr}, '\\\\', '\\\\\\\\'),` +
			` '%', '\\%'), '_', '\\_')`;
	}

	stringSubstring(expr, from, len) {

		return 'SUBSTRING(' + expr +
			', ' + (
				(typeof from) === 'number' ?
					String(from + 1) : '(' + String(from) + ') + 1'
			) +
			(len !== undefined ? ', ' + String(len) : '') + ')';
	}

	nullableConcat() {

		return 'CONCAT(' + Array.from(arguments).join(', ') + ')';
	}

	castToString(expr) {

		return `CAST(${expr} AS CHAR)`;
	}

	patternMatch(expr, pattern, invert, caseSensitive) {

		return expr +
			' COLLATE ' + this._charset +
			(caseSensitive ? '_bin' : '_general_ci') +
			(invert ? ' NOT' : '') + ' LIKE ' + pattern;
	}

	regexpMatch(expr, regexp, invert, caseSensitive) {

		return expr +
			' COLLATE ' + this._charset +
			(caseSensitive ? '_bin' : '_general_ci') +
			(invert ? ' NOT' : '') + ' REGEXP ' + regexp;
	}

	/*datetimeToString(expr) {

		return 'DATE_FORMAT(' + expr + ', \'%Y-%m%dT%TZ\')';
	}*/

	makeRangedSelect(selectStmt, offset, limit) {

		return selectStmt + ' LIMIT ' +
			(offset > 0 ? String(offset) + ', ' : '') + limit;
	}

	makeSelectWithLocks(selectStmt, exclusiveLockTables, sharedLockTables) {

		return selectStmt + (
			exclusiveLockTables && (exclusiveLockTables.length > 0) ?
				' FOR UPDATE' : (
					sharedLockTables && (sharedLockTables.length > 0) ?
						' LOCK IN SHARE MODE' : ''
				)
		);
	}

	buildLockTables() {

		throw new Error(
			'Internal X2 error: transaction scope table locks are' +
				' not supported by MySQL.');
	}

	buildDeleteWithJoins(
		fromTableName, fromTableAlias, refTables, filterExpr, filterExprParen) {

		const hasRefTables = (refTables && (refTables.length > 0));
		const hasFilter = filterExpr;

		return 'DELETE ' + fromTableAlias +
			' FROM ' + fromTableName + ' AS ' + fromTableAlias +
			(
				hasRefTables ?
					', ' + refTables.map(
						t => t.tableName + ' AS ' + t.tableAlias).join(', ') :
					''
			) +
			(
				hasRefTables || hasFilter ?
					' WHERE ' + (
						(
							hasRefTables ?
								refTables.map(
									t => t.joinCondition).join(' AND ') :
								''
						) + (
							hasFilter && hasRefTables ?
								' AND ' + (
									filterExprParen ?
										'(' + filterExpr + ')' : filterExpr
								) :
								''
						) + (
							hasFilter && !hasRefTables ? filterExpr : ''
						)
					) :
					''
			);
	}

	buildUpdateWithJoins(
		updateTableName, updateTableAlias, sets, refTables, filterExpr,
		filterExprParen) {

		const hasRefTables = (refTables && (refTables.length > 0));
		const hasFilter = filterExpr;

		return 'UPDATE ' + updateTableName + ' AS ' + updateTableAlias +
			(
				hasRefTables ?
					', ' + refTables.map(
						t => t.tableName + ' AS ' + t.tableAlias).join(', ') :
					''
			) +
			' SET ' + sets.map(
				s => updateTableAlias + '.' + s.columnName + ' = ' + s.value)
						.join(', ') +
			(
				hasRefTables || hasFilter ?
					' WHERE ' + (
						(
							hasRefTables ?
								refTables.map(
									t => t.joinCondition).join(' AND ') :
								''
						) + (
							hasFilter && hasRefTables ?
								' AND ' + (
									filterExprParen ?
										'(' + filterExpr + ')' : filterExpr
								) :
							''
						) + (
							hasFilter && !hasRefTables ? filterExpr : ''
						)
					) :
					''
			);
	}

	buildUpsert(tableName, insertColumns, insertValues, uniqueColumn, sets) {

		return `INSERT INTO ${tableName} (${insertColumns})` +
			` VALUES (${insertValues}) ON DUPLICATE KEY UPDATE ${sets}`;
	}

	connect(source, handler) {

		if ((typeof source.getConnection) === 'function') {
			source.getConnection((err, connection) => {
				if (err)
					handler.onError(err);
				else
					handler.onSuccess(connection);
			});
		} else {
			source.connect(err => {
				if (err)
					handler.onError(err);
				else
					handler.onSuccess(source);
			});
		}
	}

	releaseConnection(source, connection, err) {

		if ((typeof source.getConnection) === 'function') {
			if (err || connection[DESTROY])
				connection.destroy();
			else
				connection.release();
		} else {
			connection.end();
		}
	}

	startTransaction(connection, handler) {

		connection.query('START TRANSACTION', err => {
			if (err)
				handler.onError(err);
			else
				handler.onSuccess();
		});
	}

	rollbackTransaction(connection, handler) {

		this._finishTransaction(connection, 'ROLLBACK', handler);
	}

	commitTransaction(connection, handler) {

		this._finishTransaction(connection, 'COMMIT', handler);
	}

	_finishTransaction(connection, command, handler) {

		const trace = (handler.trace || function() {});

		let sql;
		trace(sql = command);
		connection.query(sql, err => {
			if (err) {
				connection[DESTROY] = true;
				handler.onError(err);
			} else {
				const anchorTables = connection[ANCHOR_TABLES];
				if (!anchorTables || (anchorTables.length === 0))
					return handler.onSuccess();
				trace(
					sql = 'DROP TEMPORARY TABLE IF EXISTS ' +
						anchorTables.join(', ')
				);
				connection.query(sql, err => {
					if (err)
						connection[DESTROY] = true;
					handler.onSuccess();
				});
			}
		});
	}

	setSessionVariable(connection, varName, valueExpr, handler) {

		connection.query(`SET @${varName} = ${valueExpr}`, err => {

			if (err)
				handler.onError(err);
			else
				handler.onSuccess();
		});
	}

	getSessionVariable(connection, varName, type, handler) {

		connection.query(`SELECT @${varName}`, (err, result) => {

			if (err)
				return handler.onError(err);

			const valRaw = result[0]['@' + varName];

			if (valRaw === null)
				return handler.onSuccess();

			switch (type) {
			case 'number':
				handler.onSuccess(Number(valRaw));
				break;
			case 'boolean':
				handler.onSuccess(valRaw ? true : false);
				break;
			default:
				handler.onSuccess(valRaw);
			}
		});
	}

	selectIntoAnchorTable(
		connection, anchorTableName, topTableName, idColumnName, idExpr,
		statementStump, handler) {

		const trace = (handler.trace || function() {});

		let sql;
		trace(
			sql = `CREATE TEMPORARY TABLE IF NOT EXISTS ${anchorTableName}` +
				' (UNIQUE(id), ord INTEGER UNSIGNED NOT NULL UNIQUE)' +
				` AS SELECT ${idColumnName} AS id, 0 AS ord` +
				` FROM ${topTableName} WHERE ${idColumnName} IS NULL`
		);
		connection.query(sql, (err, result) => {

			if (err)
				return handler.onError(err);

			let anchorTables = connection[ANCHOR_TABLES];
			if (!anchorTables)
				connection[ANCHOR_TABLES] = anchorTables = [];
			anchorTables.push(anchorTableName);

			if (this._options.mariaDB || this._isMariaDB(connection))
				trace(sql = (
					`INSERT INTO ${anchorTableName} (id, ord) ` +
						statementStump.replace(
							/\bSELECT\s+\{\*\}\s+FROM\b/i,
							`SELECT ${idExpr} AS id, ` +
								'(@x2node.ord := @x2node.ord + 1) AS ord FROM ' +
								'(SELECT @x2node.ord := 0) AS init,'
						)
				));
			else
				trace(sql = (
					`INSERT INTO ${anchorTableName} (id, ord) ` +
						'SELECT q.id AS id, ' +
						'(@x2node.ord := @x2node.ord + 1) AS ord FROM ' +
						'(SELECT @x2node.ord := 0) AS init, ' +
						'(' + statementStump.replace(
							/\bSELECT\s+\{\*\}\s+FROM\b/i,
							`SELECT ${idExpr} AS id FROM`) + ') AS q'
				));
			connection.query(sql, (err, result) => {

				if (err)
					return handler.onError(err);

				handler.onSuccess(result.affectedRows);
			});
		});
	}

	executeQuery(connection, statement, handler) {

		const query = connection.query(statement);

		query[HAS_ERROR] = false;

		query.on('error', err => {
			if (query[HAS_ERROR])
				return;
			query[HAS_ERROR] = true;
			handler.onError(err);
		});

		query.on('end', () => {
			if (!query[HAS_ERROR])
				handler.onSuccess();
		});

		if (handler.onHeader)
			query.on('fields', fields => {
				if (query[HAS_ERROR])
					return;
				try {
					handler.onHeader(fields.map(field => field.name));
				} catch (err) {
					query[HAS_ERROR] = true;
					handler.onError(err);
				}
			});

		if (handler.onRow)
			query.on('result', row => {
				if (query[HAS_ERROR])
					return;
				try {
					handler.onRow(row);
				} catch (err) {
					query[HAS_ERROR] = true;
					handler.onError(err);
				}
			});
	}

	executeUpdate(connection, statement, handler) {

		connection.query(statement, (err, result) => {

			if (err)
				handler.onError(err);
			else
				handler.onSuccess(result.affectedRows);
		});
	}

	executeInsert(connection, statement, handler) {

		connection.query(statement, (err, result) => {

			if (err)
				handler.onError(err);
			else
				handler.onSuccess(result.insertId);
		});
	}

	createVersionTableIfNotExists(connection, tableName, handler) {

		const trace = (handler.trace || function() {});
		let sql;
		trace(
			sql = `CREATE TABLE IF NOT EXISTS ${tableName} (` +
				'name VARCHAR(64) PRIMARY KEY, ' +
				'modified_on TIMESTAMP(3) DEFAULT 0, ' +
				'version INTEGER UNSIGNED NOT NULL)'
		);

		connection.query(sql, err => {

			if (err)
				handler.onError(err);
			else
				handler.onSuccess();
		});
	}

	updateVersionTable(
		connection, tableName, itemNames, modificationTimestamp, handler) {

		const filterExpr = 'name' + (
			itemNames.length === 1 ?
				' = ' + this.stringLiteral(itemNames[0]) :
				' IN (' + itemNames.map(v => this.stringLiteral(v)).join(', ') +
					')'
		);

		const trace = (handler.trace || function() {});
		let sql;
		trace(
			sql = `UPDATE ${tableName} SET ` +
				`modified_on = '${modificationTimestamp}', ` +
				`version = version + 1 WHERE ${filterExpr}`
		);
		connection.query(sql, (err, result) => {

			if (err)
				return handler.onError(err);

			if (result.affectedRows === itemNames.length)
				return handler.onSuccess();

			sql = `INSERT INTO ${tableName} (name, modified_on, version) VALUES`;
			for (let i = 0, len = itemNames.length; i < len; i++) {
				if (i > 0)
					sql += ',';
				sql += ' (' + this.stringLiteral(itemNames[i]) +
					', \'' + modificationTimestamp + '\', 1)';
			}
			sql += ' ON DUPLICATE KEY UPDATE modified_on = \'' +
				modificationTimestamp + '\', version = version + 1';
			trace(sql);
			connection.query(sql, err => {

				if (err)
					return handler.onError(err);

				handler.onSuccess();
			});
		});
	}

	_isMariaDB(connection) {

		const handshakePacket = (
			(
				connection._protocol &&
				connection._protocol._handshakeInitializationPacket
			) ||
			connection._handshakePacket
		);

		return (
			handshakePacket && /-MariaDB$/.test(handshakePacket.serverVersion)
		);
	}
}

module.exports = MySQLDBDriver;
<commit_msg>Removed unused argument.<commit_after>'use strict';

const BasicDBDriver = require('./basic-driver.js');

/**
 * Symbol used to indicate that there is an error on the query object.
 *
 * @private
 * @type {Symbol}
 */
const HAS_ERROR = Symbol();

/**
 * Symbol used to store list of temporary anchor tables created for the
 * transaction on the connection object.
 *
 * @private
 * @type {Symbol}
 */
const ANCHOR_TABLES = Symbol();

/**
 * Symbol used to mark a connection that must be destroyed upon release no matter
 * what.
 *
 * @private
 * @type {Symbol}
 */
const DESTROY = Symbol();

/**
 * MySQL database driver.
 *
 * @private
 * @memberof module:x2node-dbos
 * @inner
 * @extends {module:x2node-dbos~BasicDBDriver}
 * @implements {module:x2node-dbos.DBDriver}
 */
class MySQLDBDriver extends BasicDBDriver {

	constructor(options) {
		super(options);

		this._charset = ((options && options.databaseCharacterSet) || 'utf8');
	}

	supportsRowLocksWithAggregates() { return true; }

	safeLikePatternFromExpr(expr) {

		return `REPLACE(REPLACE(REPLACE(${expr}, '\\\\', '\\\\\\\\'),` +
			` '%', '\\%'), '_', '\\_')`;
	}

	stringSubstring(expr, from, len) {

		return 'SUBSTRING(' + expr +
			', ' + (
				(typeof from) === 'number' ?
					String(from + 1) : '(' + String(from) + ') + 1'
			) +
			(len !== undefined ? ', ' + String(len) : '') + ')';
	}

	nullableConcat() {

		return 'CONCAT(' + Array.from(arguments).join(', ') + ')';
	}

	castToString(expr) {

		return `CAST(${expr} AS CHAR)`;
	}

	patternMatch(expr, pattern, invert, caseSensitive) {

		return expr +
			' COLLATE ' + this._charset +
			(caseSensitive ? '_bin' : '_general_ci') +
			(invert ? ' NOT' : '') + ' LIKE ' + pattern;
	}

	regexpMatch(expr, regexp, invert, caseSensitive) {

		return expr +
			' COLLATE ' + this._charset +
			(caseSensitive ? '_bin' : '_general_ci') +
			(invert ? ' NOT' : '') + ' REGEXP ' + regexp;
	}

	/*datetimeToString(expr) {

		return 'DATE_FORMAT(' + expr + ', \'%Y-%m%dT%TZ\')';
	}*/

	makeRangedSelect(selectStmt, offset, limit) {

		return selectStmt + ' LIMIT ' +
			(offset > 0 ? String(offset) + ', ' : '') + limit;
	}

	makeSelectWithLocks(selectStmt, exclusiveLockTables, sharedLockTables) {

		return selectStmt + (
			exclusiveLockTables && (exclusiveLockTables.length > 0) ?
				' FOR UPDATE' : (
					sharedLockTables && (sharedLockTables.length > 0) ?
						' LOCK IN SHARE MODE' : ''
				)
		);
	}

	buildLockTables() {

		throw new Error(
			'Internal X2 error: transaction scope table locks are' +
				' not supported by MySQL.');
	}

	buildDeleteWithJoins(
		fromTableName, fromTableAlias, refTables, filterExpr, filterExprParen) {

		const hasRefTables = (refTables && (refTables.length > 0));
		const hasFilter = filterExpr;

		return 'DELETE ' + fromTableAlias +
			' FROM ' + fromTableName + ' AS ' + fromTableAlias +
			(
				hasRefTables ?
					', ' + refTables.map(
						t => t.tableName + ' AS ' + t.tableAlias).join(', ') :
					''
			) +
			(
				hasRefTables || hasFilter ?
					' WHERE ' + (
						(
							hasRefTables ?
								refTables.map(
									t => t.joinCondition).join(' AND ') :
								''
						) + (
							hasFilter && hasRefTables ?
								' AND ' + (
									filterExprParen ?
										'(' + filterExpr + ')' : filterExpr
								) :
								''
						) + (
							hasFilter && !hasRefTables ? filterExpr : ''
						)
					) :
					''
			);
	}

	buildUpdateWithJoins(
		updateTableName, updateTableAlias, sets, refTables, filterExpr,
		filterExprParen) {

		const hasRefTables = (refTables && (refTables.length > 0));
		const hasFilter = filterExpr;

		return 'UPDATE ' + updateTableName + ' AS ' + updateTableAlias +
			(
				hasRefTables ?
					', ' + refTables.map(
						t => t.tableName + ' AS ' + t.tableAlias).join(', ') :
					''
			) +
			' SET ' + sets.map(
				s => updateTableAlias + '.' + s.columnName + ' = ' + s.value)
						.join(', ') +
			(
				hasRefTables || hasFilter ?
					' WHERE ' + (
						(
							hasRefTables ?
								refTables.map(
									t => t.joinCondition).join(' AND ') :
								''
						) + (
							hasFilter && hasRefTables ?
								' AND ' + (
									filterExprParen ?
										'(' + filterExpr + ')' : filterExpr
								) :
							''
						) + (
							hasFilter && !hasRefTables ? filterExpr : ''
						)
					) :
					''
			);
	}

	buildUpsert(tableName, insertColumns, insertValues, uniqueColumn, sets) {

		return `INSERT INTO ${tableName} (${insertColumns})` +
			` VALUES (${insertValues}) ON DUPLICATE KEY UPDATE ${sets}`;
	}

	connect(source, handler) {

		if ((typeof source.getConnection) === 'function') {
			source.getConnection((err, connection) => {
				if (err)
					handler.onError(err);
				else
					handler.onSuccess(connection);
			});
		} else {
			source.connect(err => {
				if (err)
					handler.onError(err);
				else
					handler.onSuccess(source);
			});
		}
	}

	releaseConnection(source, connection, err) {

		if ((typeof source.getConnection) === 'function') {
			if (err || connection[DESTROY])
				connection.destroy();
			else
				connection.release();
		} else {
			connection.end();
		}
	}

	startTransaction(connection, handler) {

		connection.query('START TRANSACTION', err => {
			if (err)
				handler.onError(err);
			else
				handler.onSuccess();
		});
	}

	rollbackTransaction(connection, handler) {

		this._finishTransaction(connection, 'ROLLBACK', handler);
	}

	commitTransaction(connection, handler) {

		this._finishTransaction(connection, 'COMMIT', handler);
	}

	_finishTransaction(connection, command, handler) {

		const trace = (handler.trace || function() {});

		let sql;
		trace(sql = command);
		connection.query(sql, err => {
			if (err) {
				connection[DESTROY] = true;
				handler.onError(err);
			} else {
				const anchorTables = connection[ANCHOR_TABLES];
				if (!anchorTables || (anchorTables.length === 0))
					return handler.onSuccess();
				trace(
					sql = 'DROP TEMPORARY TABLE IF EXISTS ' +
						anchorTables.join(', ')
				);
				connection.query(sql, err => {
					if (err)
						connection[DESTROY] = true;
					handler.onSuccess();
				});
			}
		});
	}

	setSessionVariable(connection, varName, valueExpr, handler) {

		connection.query(`SET @${varName} = ${valueExpr}`, err => {

			if (err)
				handler.onError(err);
			else
				handler.onSuccess();
		});
	}

	getSessionVariable(connection, varName, type, handler) {

		connection.query(`SELECT @${varName}`, (err, result) => {

			if (err)
				return handler.onError(err);

			const valRaw = result[0]['@' + varName];

			if (valRaw === null)
				return handler.onSuccess();

			switch (type) {
			case 'number':
				handler.onSuccess(Number(valRaw));
				break;
			case 'boolean':
				handler.onSuccess(valRaw ? true : false);
				break;
			default:
				handler.onSuccess(valRaw);
			}
		});
	}

	selectIntoAnchorTable(
		connection, anchorTableName, topTableName, idColumnName, idExpr,
		statementStump, handler) {

		const trace = (handler.trace || function() {});

		let sql;
		trace(
			sql = `CREATE TEMPORARY TABLE IF NOT EXISTS ${anchorTableName}` +
				' (UNIQUE(id), ord INTEGER UNSIGNED NOT NULL UNIQUE)' +
				` AS SELECT ${idColumnName} AS id, 0 AS ord` +
				` FROM ${topTableName} WHERE ${idColumnName} IS NULL`
		);
		connection.query(sql, err => {

			if (err)
				return handler.onError(err);

			let anchorTables = connection[ANCHOR_TABLES];
			if (!anchorTables)
				connection[ANCHOR_TABLES] = anchorTables = [];
			anchorTables.push(anchorTableName);

			if (this._options.mariaDB || this._isMariaDB(connection))
				trace(sql = (
					`INSERT INTO ${anchorTableName} (id, ord) ` +
						statementStump.replace(
							/\bSELECT\s+\{\*\}\s+FROM\b/i,
							`SELECT ${idExpr} AS id, ` +
								'(@x2node.ord := @x2node.ord + 1) AS ord FROM ' +
								'(SELECT @x2node.ord := 0) AS init,'
						)
				));
			else
				trace(sql = (
					`INSERT INTO ${anchorTableName} (id, ord) ` +
						'SELECT q.id AS id, ' +
						'(@x2node.ord := @x2node.ord + 1) AS ord FROM ' +
						'(SELECT @x2node.ord := 0) AS init, ' +
						'(' + statementStump.replace(
							/\bSELECT\s+\{\*\}\s+FROM\b/i,
							`SELECT ${idExpr} AS id FROM`) + ') AS q'
				));
			connection.query(sql, (err, result) => {

				if (err)
					return handler.onError(err);

				handler.onSuccess(result.affectedRows);
			});
		});
	}

	executeQuery(connection, statement, handler) {

		const query = connection.query(statement);

		query[HAS_ERROR] = false;

		query.on('error', err => {
			if (query[HAS_ERROR])
				return;
			query[HAS_ERROR] = true;
			handler.onError(err);
		});

		query.on('end', () => {
			if (!query[HAS_ERROR])
				handler.onSuccess();
		});

		if (handler.onHeader)
			query.on('fields', fields => {
				if (query[HAS_ERROR])
					return;
				try {
					handler.onHeader(fields.map(field => field.name));
				} catch (err) {
					query[HAS_ERROR] = true;
					handler.onError(err);
				}
			});

		if (handler.onRow)
			query.on('result', row => {
				if (query[HAS_ERROR])
					return;
				try {
					handler.onRow(row);
				} catch (err) {
					query[HAS_ERROR] = true;
					handler.onError(err);
				}
			});
	}

	executeUpdate(connection, statement, handler) {

		connection.query(statement, (err, result) => {

			if (err)
				handler.onError(err);
			else
				handler.onSuccess(result.affectedRows);
		});
	}

	executeInsert(connection, statement, handler) {

		connection.query(statement, (err, result) => {

			if (err)
				handler.onError(err);
			else
				handler.onSuccess(result.insertId);
		});
	}

	createVersionTableIfNotExists(connection, tableName, handler) {

		const trace = (handler.trace || function() {});
		let sql;
		trace(
			sql = `CREATE TABLE IF NOT EXISTS ${tableName} (` +
				'name VARCHAR(64) PRIMARY KEY, ' +
				'modified_on TIMESTAMP(3) DEFAULT 0, ' +
				'version INTEGER UNSIGNED NOT NULL)'
		);

		connection.query(sql, err => {

			if (err)
				handler.onError(err);
			else
				handler.onSuccess();
		});
	}

	updateVersionTable(
		connection, tableName, itemNames, modificationTimestamp, handler) {

		const filterExpr = 'name' + (
			itemNames.length === 1 ?
				' = ' + this.stringLiteral(itemNames[0]) :
				' IN (' + itemNames.map(v => this.stringLiteral(v)).join(', ') +
					')'
		);

		const trace = (handler.trace || function() {});
		let sql;
		trace(
			sql = `UPDATE ${tableName} SET ` +
				`modified_on = '${modificationTimestamp}', ` +
				`version = version + 1 WHERE ${filterExpr}`
		);
		connection.query(sql, (err, result) => {

			if (err)
				return handler.onError(err);

			if (result.affectedRows === itemNames.length)
				return handler.onSuccess();

			sql = `INSERT INTO ${tableName} (name, modified_on, version) VALUES`;
			for (let i = 0, len = itemNames.length; i < len; i++) {
				if (i > 0)
					sql += ',';
				sql += ' (' + this.stringLiteral(itemNames[i]) +
					', \'' + modificationTimestamp + '\', 1)';
			}
			sql += ' ON DUPLICATE KEY UPDATE modified_on = \'' +
				modificationTimestamp + '\', version = version + 1';
			trace(sql);
			connection.query(sql, err => {

				if (err)
					return handler.onError(err);

				handler.onSuccess();
			});
		});
	}

	_isMariaDB(connection) {

		const handshakePacket = (
			(
				connection._protocol &&
				connection._protocol._handshakeInitializationPacket
			) ||
			connection._handshakePacket
		);

		return (
			handshakePacket && /-MariaDB$/.test(handshakePacket.serverVersion)
		);
	}
}

module.exports = MySQLDBDriver;
<|endoftext|>


<commit_before>// Load in dependencies
var EventEmitter = require('events').EventEmitter;
var inherits = require('inherits');

// Define selector constants
var SELECTORS = {
  info: {
    albumArtId: 'playingAlbumArt',
    albumSelector: '.player-album',
    artistId: 'player-artist',
    containerId: 'playerSongInfo',
    infoWrapperClass: 'now-playing-info-wrapper',
    titleId: 'player-song-title'
  },
  forward: {
    buttonSelector: '#player sj-icon-button[data-id="forward"]'
  },
  playPause: {
    buttonSelector: '#player sj-icon-button[data-id="play-pause"]',
    dataId: 'play-pause',
    playingClass: 'playing'
  },
  rating: {
    // DEV: `.player-rating-container` doesn't exist until a song is playing
    containerSelector: '#playerSongInfo',
    thumbsSelector: '#player .player-rating-container [icon^="sj:thumb-"][data-rating]',
    thumbsUpSelector: '#player .player-rating-container [icon^="sj:thumb-"][data-rating="5"]',
    thumbsDownSelector: '#player .player-rating-container [icon^="sj:thumb-"][data-rating="1"]',
    thumbSelectorFormat: '#player .player-rating-container [icon^="sj:thumb-"][data-rating="{rating}"]'
  },
  repeat: {
    dataId: 'repeat',
    buttonSelector: '#player sj-icon-button[data-id="repeat"]'
  },
  rewind: {
    buttonSelector: '#player sj-icon-button[data-id="rewind"]'
  },
  shuffle: {
    dataId: 'shuffle',
    buttonSelector: '#player sj-icon-button[data-id="shuffle"]'
  },
  playback: {
    sliderId: 'material-player-progress'
  },
  volume: {
    sliderId: 'material-vslider'
  }
};

// Define bind method
function bind(context, fn) {
  return function bindFn () {
    return fn.apply(context, arguments);
  };
}

// Define our constructor
function GoogleMusic(win) {
  // If win was not provided, complain
  if (!win) {
    throw new Error('`win` was not provided to the `GoogleMusic` constructor');
  }

  // Inherit from EventEmitter
  EventEmitter.call(this);

  // Localize reference to window and document
  this.win = win;
  this.doc = win.document;

  // For each of the prototype sections
  var proto = GoogleMusic._protoObj;
  for (var protoKey in proto) {
    if (proto.hasOwnProperty(protoKey)) {
      // Define a key on our object
      this[protoKey] = {};

      // For each of the keys on the section, define a function that invokes on this original context
      var section = proto[protoKey];
      for (var sectionKey in section) {
        if (section.hasOwnProperty(sectionKey)) {
          this[protoKey][sectionKey] = bind(this, section[sectionKey]);
        }
      }

      // If there was an `init` method, run it
      if (this[protoKey].init) {
        this[protoKey].init();
      }
    }
  }
}
// Inherit from EventEmitter normally
inherits(GoogleMusic, EventEmitter);

// Define a "prototype" that will have magical invocation
var proto = GoogleMusic._protoObj = {};

// Create a volume API
proto.volume = {
  // Query required elements
  init: function () {
    this.volume._sliderEl = this.doc.getElementById(SELECTORS.volume.sliderId);
  },

  // Get the current volume level.
  getVolume: function () {
    return parseInt(this.volume._sliderEl.getAttribute('aria-valuenow'), 10);
  },

  // Set the volume level (0 - 100).
  setVolume: function (vol) {
    var current = this.volume.getVolume();

    if (vol > current) {
      this.volume.increaseVolume(vol - current);
    } else if (vol < current) {
      this.volume.decreaseVolume(current - vol);
    }
  },

  // Increase the volume by an amount (default of 5)
  increaseVolume: function (amount) {
    if (amount === undefined) {
      amount = 5;
    }

    for (var i = 0; i < amount; i += 5) {
      this.volume._sliderEl.increment();
    }
  },

  // Decrease the volume by an amount (default of 1)
  decreaseVolume: function (amount) {
    if (amount === undefined) {
      amount = 5;
    }

    for (var i = 0; i < amount; i += 5) {
      this.volume._sliderEl.decrement();
    }
  }
};

// Create a playback API and constants
GoogleMusic.Playback = {
  // Playback states
  STOPPED: 0,
  PAUSED: 1,
  PLAYING: 2,

  // Repeat modes
  LIST_REPEAT: 'LIST_REPEAT',
  SINGLE_REPEAT: 'SINGLE_REPEAT',
  NO_REPEAT: 'NO_REPEAT',

  // Shuffle modes
  ALL_SHUFFLE: 'ALL_SHUFFLE',
  NO_SHUFFLE: 'NO_SHUFFLE'
};
proto.playback = {
  // Query references to the media playback elements
  init: function () {
    this.playback._sliderEl = this.doc.getElementById(SELECTORS.playback.sliderId);
    this.playback._playPauseEl = this.doc.querySelector(SELECTORS.playPause.buttonSelector);
    this.playback._forwardEl = this.doc.querySelector(SELECTORS.forward.buttonSelector);
    this.playback._rewindEl = this.doc.querySelector(SELECTORS.rewind.buttonSelector);
    this.playback._shuffleEl = this.doc.querySelector(SELECTORS.shuffle.buttonSelector);
    this.playback._repeatEl = this.doc.querySelector(SELECTORS.repeat.buttonSelector);
  },

  // Time functions
  getPlaybackTime: function () {
    return parseInt(this.playback._sliderEl.getAttribute('aria-valuenow'), 10);
  },

  setPlaybackTime: function (milliseconds) {
    // Set playback value on the element and trigger a change event
    this.playback._sliderEl.value = milliseconds;
    var evt = new UIEvent('change');
    this.playback._sliderEl.dispatchEvent(evt);
  },

  // Playback functions
  playPause: function () { this.playback._playPauseEl.click(); },
  forward: function () { this.playback._forwardEl.click(); },
  rewind: function () { this.playback._rewindEl.click(); },

  getShuffle: function () { return this.playback._shuffleEl.getAttribute('value'); },
  toggleShuffle: function () { this.playback._shuffleEl.click(); },

  getRepeat: function () {
    return this.playback._repeatEl.getAttribute('value');
  },

  toggleRepeat: function (mode) {
    if (!mode) {
      // Toggle between repeat modes once
      this.playback._repeatEl.click();
    } else {
      // Toggle between repeat modes until the desired mode is activated
      while (this.playback.getRepeat() !== mode) {
        this.playback._repeatEl.click();
      }
    }
  },

  // Taken from the Google Play Music page
  toggleVisualization: function () {
    this.win.SJBpost('toggleVisualization');
  }
};

// Create a rating API
proto.rating = {
  // Determine if a thumb is selected or not
  _isElSelected: function (el) {
    // If the target is not outlined in its shadow DOM, then it's selected
    // jscs:disable maximumLineLength
    // DEV: Access shadow DOM via `$`
    //   Selected thumbs up:
    //   <core-icon relative="" id="icon" src="{{src}}" icon="{{icon}}" aria-label="thumb-up" role="img"></core-icon>
    //   Unselected thumbs down:
    //   <core-icon relative="" id="icon" src="{{src}}" icon="{{icon}}" aria-label="thumb-down-outline" role="img"></core-icon>
    // jscs:enable maximumLineLength
    return el.$.icon.getAttribute('aria-label').indexOf('-outline') === -1;
  },
  // Get current rating
  getRating: function () {
    var thumbEls = this.doc.querySelectorAll(SELECTORS.rating.thumbsSelector);
    var i = 0;
    var len = thumbEls.length;
    for (; i < len; i++) {
      var el = thumbEls[i];
      if (this.rating._isElSelected(el)) {
        return el.dataset.rating;
      }
    }
    return '0';
  },

  // Thumbs up
  toggleThumbsUp: function () {
    var el = this.doc.querySelector(SELECTORS.rating.thumbsUpSelector);

    if (el) {
      el.click();
    }
  },

  // Thumbs down
  toggleThumbsDown: function () {
    var el = this.doc.querySelector(SELECTORS.rating.thumbsDownSelector);

    if (el) {
      el.click();
    }
  },

  // Set a rating
  setRating: function (rating) {
    var selector = SELECTORS.rating.thumbSelectorFormat.replace('{rating}', rating);
    var el = this.doc.querySelector(selector);

    if (el && !this.rating._isElSelected(el)) {
      el.click();
    }
  }
};

// Miscellaneous functions
proto.extras = {
  // Get a shareable URL of the song on Google Play Music
  getSongURL: function () {
    var albumEl = this.doc.querySelector('.player-album');
    var artistEl = this.doc.querySelector('.player-artist');

    var urlTemplate = 'https://play.google.com/music/m/';
    var url = null;

    var parseID = function (id) {
      return id.substring(0, id.indexOf('/'));
    };

    if (albumEl === null && artistEl === null) {
      return null;
    }

    var albumId = parseID(albumEl.dataset.id);
    var artistId = parseID(artistEl.dataset.id);

    if (albumId) {
      url = urlTemplate + albumId;
    } else if (artistId) {
      url = urlTemplate + artistId;
    }

    return url;
  }
};

proto.hooks = {
  init: function () {
    // Save context for bindings
    var that = this;

    // Define mutation observer for reuse
    var MutationObserver = this.win.MutationObserver || this.win.WebKitMutationObserver;

    var lastTitle = '';
    var lastArtist = '';
    var lastAlbum = '';

    var addObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        for (var i = 0; i < m.addedNodes.length; i++) {
          var target = m.addedNodes[i];
          if (target.classList.contains(SELECTORS.info.infoWrapperClass)) {
            var title = that.doc.getElementById(SELECTORS.info.titleId);
            var artist = that.doc.getElementById(SELECTORS.info.artistId);
            var album = that.doc.querySelector(SELECTORS.info.albumSelector);
            var art = that.doc.getElementById(SELECTORS.info.albumArtId);
            var duration = parseInt(that.doc.getElementById(SELECTORS.playback.sliderId).getAttribute('aria-valuemax'), 10);

            title = (title) ? title.innerText : 'Unknown';
            artist = (artist) ? artist.innerText : 'Unknown';
            album = (album) ? album.innerText : 'Unknown';
            art = (art) ? art.src : null;

            // The art may be a protocol-relative URL, so normalize it to HTTPS
            if (art && art.slice(0, 2) === '//') {
              art = 'https:' + art;
            }

            // Make sure that this is the first of the notifications for the
            // insertion of the song information elements.
            if (lastTitle !== title || lastArtist !== artist || lastAlbum !== album) {
              that.emit('change:song', {
                title: title,
                artist: artist,
                album: album,
                art: art,
                duration: duration
              });

              lastTitle = title;
              lastArtist = artist;
              lastAlbum = album;
            }
          }
        }
      });
    });

    var shuffleObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        var id = target.dataset.id;

        if (id === SELECTORS.shuffle.dataId) {
          that.emit('change:shuffle', target.value);
        }
      });
    });

    var repeatObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        var id = target.dataset.id;

        if (id === SELECTORS.repeat.dataId) {
          that.emit('change:repeat', target.value);
        }
      });
    });

    var playbackObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        var id = target.dataset.id;

        if (id === SELECTORS.playPause.dataId) {
          var mode;
          var playing = target.classList.contains(SELECTORS.playPause.playingClass);

          if (playing) {
            mode = GoogleMusic.Playback.PLAYING;
          } else {
            // If there is a current song, then the player is paused
            if (that.doc.getElementById(SELECTORS.info.containerId).childNodes.length) {
              mode = GoogleMusic.Playback.PAUSED;
            } else {
              mode = GoogleMusic.Playback.STOPPED;
            }
          }

          that.emit('change:playback', mode);
        }
      });
    });

    var playbackTimeObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        var id = target.id;

        if (id === SELECTORS.shuffle.buttonSelector) {
          var currentTime = parseInt(target.getAttribute('aria-valuenow'), 10);
          var totalTime = parseInt(target.getAttribute('aria-valuemax'), 10);
          that.emit('change:playback-time', {current: currentTime, total: totalTime});
        }
      });
    });

    var ratingObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        // If we are looking at a rating button and it's selected, emit a notification
        // DEV: We can receive the container easily
        if (target.dataset.rating !== undefined && that.rating._isElSelected(target)) {
          that.emit('change:rating', target.dataset.rating);
        }
      });
    });

    addObserver.observe(this.doc.getElementById(SELECTORS.info.containerId), {
      childList: true,
      subtree: true
    });
    shuffleObserver.observe(this.doc.querySelector(SELECTORS.shuffle.buttonSelector), {
      attributes: true
    });
    repeatObserver.observe(this.doc.querySelector(SELECTORS.repeat.buttonSelector), {
      attributes: true
    });
    playbackObserver.observe(this.doc.querySelector(SELECTORS.playPause.buttonSelector), {
      attributes: true
    });
    playbackTimeObserver.observe(this.doc.getElementById(SELECTORS.playback.sliderId), {
      attributes: true
    });
    ratingObserver.observe(this.doc.querySelector(SELECTORS.rating.containerSelector), {
      attributes: true,
      subtree: true
    });
  }
};

// Expose selectors as a class property
GoogleMusic.SELECTORS = SELECTORS;

// Export our constructor
module.exports = GoogleMusic;
<commit_msg>Fixed up selectors<commit_after>// Load in dependencies
var EventEmitter = require('events').EventEmitter;
var inherits = require('inherits');

// Define selector constants
var SELECTORS = {
  info: {
    albumArtId: 'playingAlbumArt',
    albumSelector: '.player-album',
    artistId: 'player-artist',
    containerId: 'playerSongInfo',
    infoWrapperClass: 'now-playing-info-wrapper',
    titleId: 'player-song-title'
  },
  forward: {
    buttonSelector: '#player sj-icon-button[data-id="forward"]'
  },
  playPause: {
    buttonSelector: '#player sj-icon-button[data-id="play-pause"]',
    dataId: 'play-pause',
    playingClass: 'playing'
  },
  rating: {
    // DEV: `.player-rating-container` doesn't exist until a song is playing
    containerSelector: '#playerSongInfo',
    thumbsSelector: '#player .player-rating-container [icon^="sj:thumb-"][data-rating]',
    thumbsUpSelector: '#player .player-rating-container [icon^="sj:thumb-"][data-rating="5"]',
    thumbsDownSelector: '#player .player-rating-container [icon^="sj:thumb-"][data-rating="1"]',
    thumbSelectorFormat: '#player .player-rating-container [icon^="sj:thumb-"][data-rating="{rating}"]'
  },
  repeat: {
    dataId: 'repeat',
    buttonSelector: '#player sj-icon-button[data-id="repeat"]'
  },
  rewind: {
    buttonSelector: '#player sj-icon-button[data-id="rewind"]'
  },
  shuffle: {
    dataId: 'shuffle',
    buttonSelector: '#player sj-icon-button[data-id="shuffle"]'
  },
  playback: {
    sliderId: 'material-player-progress'
  },
  volume: {
    sliderId: 'material-vslider'
  }
};

// Define bind method
function bind(context, fn) {
  return function bindFn () {
    return fn.apply(context, arguments);
  };
}

// Define our constructor
function GoogleMusic(win) {
  // If win was not provided, complain
  if (!win) {
    throw new Error('`win` was not provided to the `GoogleMusic` constructor');
  }

  // Inherit from EventEmitter
  EventEmitter.call(this);

  // Localize reference to window and document
  this.win = win;
  this.doc = win.document;

  // For each of the prototype sections
  var proto = GoogleMusic._protoObj;
  for (var protoKey in proto) {
    if (proto.hasOwnProperty(protoKey)) {
      // Define a key on our object
      this[protoKey] = {};

      // For each of the keys on the section, define a function that invokes on this original context
      var section = proto[protoKey];
      for (var sectionKey in section) {
        if (section.hasOwnProperty(sectionKey)) {
          this[protoKey][sectionKey] = bind(this, section[sectionKey]);
        }
      }

      // If there was an `init` method, run it
      if (this[protoKey].init) {
        this[protoKey].init();
      }
    }
  }
}
// Inherit from EventEmitter normally
inherits(GoogleMusic, EventEmitter);

// Define a "prototype" that will have magical invocation
var proto = GoogleMusic._protoObj = {};

// Create a volume API
proto.volume = {
  // Query required elements
  init: function () {
    this.volume._sliderEl = this.doc.getElementById(SELECTORS.volume.sliderId);
  },

  // Get the current volume level.
  getVolume: function () {
    return parseInt(this.volume._sliderEl.getAttribute('aria-valuenow'), 10);
  },

  // Set the volume level (0 - 100).
  setVolume: function (vol) {
    var current = this.volume.getVolume();

    if (vol > current) {
      this.volume.increaseVolume(vol - current);
    } else if (vol < current) {
      this.volume.decreaseVolume(current - vol);
    }
  },

  // Increase the volume by an amount (default of 5)
  increaseVolume: function (amount) {
    if (amount === undefined) {
      amount = 5;
    }

    for (var i = 0; i < amount; i += 5) {
      this.volume._sliderEl.increment();
    }
  },

  // Decrease the volume by an amount (default of 1)
  decreaseVolume: function (amount) {
    if (amount === undefined) {
      amount = 5;
    }

    for (var i = 0; i < amount; i += 5) {
      this.volume._sliderEl.decrement();
    }
  }
};

// Create a playback API and constants
GoogleMusic.Playback = {
  // Playback states
  STOPPED: 0,
  PAUSED: 1,
  PLAYING: 2,

  // Repeat modes
  LIST_REPEAT: 'LIST_REPEAT',
  SINGLE_REPEAT: 'SINGLE_REPEAT',
  NO_REPEAT: 'NO_REPEAT',

  // Shuffle modes
  ALL_SHUFFLE: 'ALL_SHUFFLE',
  NO_SHUFFLE: 'NO_SHUFFLE'
};
proto.playback = {
  // Query references to the media playback elements
  init: function () {
    this.playback._sliderEl = this.doc.getElementById(SELECTORS.playback.sliderId);
    this.playback._playPauseEl = this.doc.querySelector(SELECTORS.playPause.buttonSelector);
    this.playback._forwardEl = this.doc.querySelector(SELECTORS.forward.buttonSelector);
    this.playback._rewindEl = this.doc.querySelector(SELECTORS.rewind.buttonSelector);
    this.playback._shuffleEl = this.doc.querySelector(SELECTORS.shuffle.buttonSelector);
    this.playback._repeatEl = this.doc.querySelector(SELECTORS.repeat.buttonSelector);
  },

  // Time functions
  getPlaybackTime: function () {
    return parseInt(this.playback._sliderEl.getAttribute('aria-valuenow'), 10);
  },

  setPlaybackTime: function (milliseconds) {
    // Set playback value on the element and trigger a change event
    this.playback._sliderEl.value = milliseconds;
    var evt = new UIEvent('change');
    this.playback._sliderEl.dispatchEvent(evt);
  },

  // Playback functions
  playPause: function () { this.playback._playPauseEl.click(); },
  forward: function () { this.playback._forwardEl.click(); },
  rewind: function () { this.playback._rewindEl.click(); },

  getShuffle: function () { return this.playback._shuffleEl.getAttribute('value'); },
  toggleShuffle: function () { this.playback._shuffleEl.click(); },

  getRepeat: function () {
    return this.playback._repeatEl.getAttribute('value');
  },

  toggleRepeat: function (mode) {
    if (!mode) {
      // Toggle between repeat modes once
      this.playback._repeatEl.click();
    } else {
      // Toggle between repeat modes until the desired mode is activated
      while (this.playback.getRepeat() !== mode) {
        this.playback._repeatEl.click();
      }
    }
  },

  // Taken from the Google Play Music page
  toggleVisualization: function () {
    this.win.SJBpost('toggleVisualization');
  }
};

// Create a rating API
proto.rating = {
  // Determine if a thumb is selected or not
  _isElSelected: function (el) {
    // If the target is not outlined in its shadow DOM, then it's selected
    // jscs:disable maximumLineLength
    // DEV: Access shadow DOM via `$`
    //   Selected thumbs up:
    //   <core-icon relative="" id="icon" src="{{src}}" icon="{{icon}}" aria-label="thumb-up" role="img"></core-icon>
    //   Unselected thumbs down:
    //   <core-icon relative="" id="icon" src="{{src}}" icon="{{icon}}" aria-label="thumb-down-outline" role="img"></core-icon>
    // jscs:enable maximumLineLength
    return el.$.icon.getAttribute('aria-label').indexOf('-outline') === -1;
  },
  // Get current rating
  getRating: function () {
    var thumbEls = this.doc.querySelectorAll(SELECTORS.rating.thumbsSelector);
    var i = 0;
    var len = thumbEls.length;
    for (; i < len; i++) {
      var el = thumbEls[i];
      if (this.rating._isElSelected(el)) {
        return el.dataset.rating;
      }
    }
    return '0';
  },

  // Thumbs up
  toggleThumbsUp: function () {
    var el = this.doc.querySelector(SELECTORS.rating.thumbsUpSelector);

    if (el) {
      el.click();
    }
  },

  // Thumbs down
  toggleThumbsDown: function () {
    var el = this.doc.querySelector(SELECTORS.rating.thumbsDownSelector);

    if (el) {
      el.click();
    }
  },

  // Set a rating
  setRating: function (rating) {
    var selector = SELECTORS.rating.thumbSelectorFormat.replace('{rating}', rating);
    var el = this.doc.querySelector(selector);

    if (el && !this.rating._isElSelected(el)) {
      el.click();
    }
  }
};

// Miscellaneous functions
proto.extras = {
  // Get a shareable URL of the song on Google Play Music
  getSongURL: function () {
    var albumEl = this.doc.querySelector('.player-album');
    var artistEl = this.doc.querySelector('.player-artist');

    var urlTemplate = 'https://play.google.com/music/m/';
    var url = null;

    var parseID = function (id) {
      return id.substring(0, id.indexOf('/'));
    };

    if (albumEl === null && artistEl === null) {
      return null;
    }

    var albumId = parseID(albumEl.dataset.id);
    var artistId = parseID(artistEl.dataset.id);

    if (albumId) {
      url = urlTemplate + albumId;
    } else if (artistId) {
      url = urlTemplate + artistId;
    }

    return url;
  }
};

proto.hooks = {
  init: function () {
    // Save context for bindings
    var that = this;

    // Define mutation observer for reuse
    var MutationObserver = this.win.MutationObserver || this.win.WebKitMutationObserver;

    var lastTitle = '';
    var lastArtist = '';
    var lastAlbum = '';

    var addObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        for (var i = 0; i < m.addedNodes.length; i++) {
          var target = m.addedNodes[i];
          if (target.classList.contains(SELECTORS.info.infoWrapperClass)) {
            var title = that.doc.getElementById(SELECTORS.info.titleId);
            var artist = that.doc.getElementById(SELECTORS.info.artistId);
            var album = that.doc.querySelector(SELECTORS.info.albumSelector);
            var art = that.doc.getElementById(SELECTORS.info.albumArtId);
            var duration = parseInt(that.doc.getElementById(SELECTORS.playback.sliderId).getAttribute('aria-valuemax'), 10);

            title = (title) ? title.innerText : 'Unknown';
            artist = (artist) ? artist.innerText : 'Unknown';
            album = (album) ? album.innerText : 'Unknown';
            art = (art) ? art.src : null;

            // The art may be a protocol-relative URL, so normalize it to HTTPS
            if (art && art.slice(0, 2) === '//') {
              art = 'https:' + art;
            }

            // Make sure that this is the first of the notifications for the
            // insertion of the song information elements.
            if (lastTitle !== title || lastArtist !== artist || lastAlbum !== album) {
              that.emit('change:song', {
                title: title,
                artist: artist,
                album: album,
                art: art,
                duration: duration
              });

              lastTitle = title;
              lastArtist = artist;
              lastAlbum = album;
            }
          }
        }
      });
    });

    var shuffleObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        var id = target.dataset.id;

        if (id === SELECTORS.shuffle.dataId) {
          that.emit('change:shuffle', target.value);
        }
      });
    });

    var repeatObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        var id = target.dataset.id;

        if (id === SELECTORS.repeat.dataId) {
          that.emit('change:repeat', target.value);
        }
      });
    });

    var playbackObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        var id = target.dataset.id;

        if (id === SELECTORS.playPause.dataId) {
          var mode;
          var playing = target.classList.contains(SELECTORS.playPause.playingClass);

          if (playing) {
            mode = GoogleMusic.Playback.PLAYING;
          } else {
            // If there is a current song, then the player is paused
            if (that.doc.getElementById(SELECTORS.info.containerId).childNodes.length) {
              mode = GoogleMusic.Playback.PAUSED;
            } else {
              mode = GoogleMusic.Playback.STOPPED;
            }
          }

          that.emit('change:playback', mode);
        }
      });
    });

    var playbackTimeObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        var id = target.id;

        if (id === SELECTORS.playback.sliderId) {
          var currentTime = parseInt(target.getAttribute('aria-valuenow'), 10);
          var totalTime = parseInt(target.getAttribute('aria-valuemax'), 10);
          that.emit('change:playback-time', {current: currentTime, total: totalTime});
        }
      });
    });

    var ratingObserver = new MutationObserver(function (mutations) {
      mutations.forEach(function (m) {
        var target = m.target;
        // If we are looking at a rating button and it's selected, emit a notification
        // DEV: We can receive the container easily
        if (target.dataset.rating !== undefined && that.rating._isElSelected(target)) {
          that.emit('change:rating', target.dataset.rating);
        }
      });
    });

    addObserver.observe(this.doc.getElementById(SELECTORS.info.containerId), {
      childList: true,
      subtree: true
    });
    shuffleObserver.observe(this.doc.querySelector(SELECTORS.shuffle.buttonSelector), {
      attributes: true
    });
    repeatObserver.observe(this.doc.querySelector(SELECTORS.repeat.buttonSelector), {
      attributes: true
    });
    playbackObserver.observe(this.doc.querySelector(SELECTORS.playPause.buttonSelector), {
      attributes: true
    });
    playbackTimeObserver.observe(this.doc.getElementById(SELECTORS.playback.sliderId), {
      attributes: true
    });
    ratingObserver.observe(this.doc.querySelector(SELECTORS.rating.containerSelector), {
      attributes: true,
      subtree: true
    });
  }
};

// Expose selectors as a class property
GoogleMusic.SELECTORS = SELECTORS;

// Export our constructor
module.exports = GoogleMusic;
<|endoftext|>


<commit_before>'use strict';

/**
 * @module main
 */

/* core modules */
var util = require('util');
var http = require('http');

/* local modules */
var tools = require('./tools.js');
var Request = require('./request.js');

// private section

/**
 * Wraps the requests that don't have a request body
 *
 * @private
 */
var noRequestBody = function(options, file, callback) {
	if (!callback) {
		callback = file;
		file = false;
	}

	var request = new Request(options);
	tools.checkCallback(callback, request.options.url);

	request.send(function(err, res) {
		if (err) {
			callback(err);
			return;
		}

		if (options.stream === true) {
			callback(null, request.stdResult());
			return;
		}

		switch (file) {
			case false:
				// buffer
				request.saveBuffer(function(err, stdResult) {
					if (err) {
						callback(err);
						return;
					}

					callback(null, stdResult);
				});
				break;

			case null:
				// simulate writing to /dev/null, carefully under node.js v0.10
				callback(null, request.stdResult());
				res.response.resume();
				break;

			default:
				// save to file
				if (typeof file !== 'string') {
					throw tools.formatError('Expecting a file path for saving the object from URL: %s.', options.url);
				}

				request.saveFile(file, function(err, stdResult) {
					if (err) {
						callback(err);
						return;
					}

					callback(null, stdResult);
				});
				break;
		}
	});
};

// public section

/**
 * Constructs a Request object for wrapping your own HTTP method / functionality
 *
 * @param {module:request~options} options The options Object taken by the {@link module:request~Request} constructor
 * @returns {module:request~Request} A new instance of the {@link module:request~Request} class
 */
exports.createHttpClient = function(options) {
	return new Request(options);
};

/**
 * HTTP GET method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'GET' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 */
exports.get = function(options, file, callback) {
	options = tools.shortHand(options, 'get');
	noRequestBody(options, file, callback);
};

/**
 * HTTP HEAD method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'HEAD' and it can not be overridden
 * @param {module:main~callback} callback Completion callback
 */
exports.head = function(options, callback) {
	options = tools.shortHand(options, 'head');
	var request = new Request(options);
	tools.checkCallback(callback, request.options.url);

	request.send(function(err, res) {
		if (err) {
			callback(err);
			return;
		}

		callback(null, request.stdResult());
		res.response.resume();
	});
};

/**
 * HTTP DELETE method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'DELETE' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 */
exports.delete = function(options, file, callback) {
	options = tools.shortHand(options, 'delete');
	noRequestBody(options, file, callback);
};

/**
 * Sets the http.Agent.defaultMaxSockets value
 *
 * @param {Number} value
 */
exports.setMaxSockets = function(value) {
	value = tools.absInt(value);
	if (!value) { // fallback to the default
		value = 5;
	}
	http.Agent.defaultMaxSockets = value;
};

// documentation section

/**
 * Describes the callback passed to each HTTP method wrapper
 *
 * @callback module:main~callback
 * @param {module:request~stdError} error The standard error or *null* on success
 * @param {module:request~stdResult} result The standard result object if error is null
 */
<commit_msg>Added some examples for the main module.<commit_after>'use strict';

/**
 * @module main
 */

/* core modules */
var util = require('util');
var http = require('http');

/* local modules */
var tools = require('./tools.js');
var Request = require('./request.js');

// private section

/**
 * Wraps the requests that don't have a request body
 *
 * @private
 */
var noRequestBody = function(options, file, callback) {
	if (!callback) {
		callback = file;
		file = false;
	}

	var request = new Request(options);
	tools.checkCallback(callback, request.options.url);

	request.send(function(err, res) {
		if (err) {
			callback(err);
			return;
		}

		if (options.stream === true) {
			callback(null, request.stdResult());
			return;
		}

		switch (file) {
			case false:
				// buffer
				request.saveBuffer(function(err, stdResult) {
					if (err) {
						callback(err);
						return;
					}

					callback(null, stdResult);
				});
				break;

			case null:
				// simulate writing to /dev/null, carefully under node.js v0.10
				callback(null, request.stdResult());
				res.response.resume();
				break;

			default:
				// save to file
				if (typeof file !== 'string') {
					throw tools.formatError('Expecting a file path for saving the object from URL: %s.', options.url);
				}

				request.saveFile(file, function(err, stdResult) {
					if (err) {
						callback(err);
						return;
					}

					callback(null, stdResult);
				});
				break;
		}
	});
};

// public section

/**
 * Constructs a Request object for wrapping your own HTTP method / functionality
 *
 * @param {module:request~options} options The options Object taken by the {@link module:request~Request} constructor
 * @returns {module:request~Request} A new instance of the {@link module:request~Request} class
 */
exports.createHttpClient = function(options) {
	return new Request(options);
};

/**
 * HTTP GET method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'GET' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, buffered response
http.get('http://localhost/get', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// save the response to file with a progress callback
http.get({
	url: 'http://localhost/get',
	progress: function (current, total) {
		console.log('downloaded %d bytes from %d', current, total);
	}
}, 'get.bin', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.file);
	}
});

// with explicit options, use a HTTP proxy, and limit the number of redirects
http.get({
	url: 'http://localhost/get',
	proxy: {
		host: 'localhost',
		port: 3128
	},
	maxRedirects: 2
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});
 */
exports.get = function(options, file, callback) {
	options = tools.shortHand(options, 'get');
	noRequestBody(options, file, callback);
};

/**
 * HTTP HEAD method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'HEAD' and it can not be overridden
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, default options
http.head('http://localhost/head', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers);
	}
});
 */
exports.head = function(options, callback) {
	options = tools.shortHand(options, 'head');
	var request = new Request(options);
	tools.checkCallback(callback, request.options.url);

	request.send(function(err, res) {
		if (err) {
			callback(err);
			return;
		}

		callback(null, request.stdResult());
		res.response.resume();
	});
};

/**
 * HTTP DELETE method wrapper
 *
 * @param {module:request~options} options The options Object taken by the {@link Request} constructor, filtered by {@link module:tools.shortHand}. options.method = 'DELETE' and it can not be overridden
 * @param {String|null} file Optional; specify a filesystem path to save the response body as file instead of Buffer. Passing null turns of the data listeners
 * @param {module:main~callback} callback Completion callback
 *
 * @example
// shorthand syntax, buffered response
http.delete('http://localhost/delete', function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.buffer.toString());
	}
});

// with explicit options object, save to file the response body
http.delete({
	url: 'http://localhost/delete'
}, function (err, res) {
	if (err) {
		console.error(err);
	} else {
		console.log(res.code, res.headers, res.file);
	}
});
 */
exports.delete = function(options, file, callback) {
	options = tools.shortHand(options, 'delete');
	noRequestBody(options, file, callback);
};

/**
 * Sets the http.Agent.defaultMaxSockets value
 *
 * @param {Number} value
 *
 * @example
// use up to 128 concurrent sockets
http.setMaxSockets(128);
 */
exports.setMaxSockets = function(value) {
	value = tools.absInt(value);
	if (!value) { // fallback to the default
		value = 5;
	}
	http.Agent.defaultMaxSockets = value;
};

// documentation section

/**
 * Describes the callback passed to each HTTP method wrapper
 *
 * @callback module:main~callback
 * @param {module:request~stdError} error The standard error or *null* on success
 * @param {module:request~stdResult} result The standard result object if error is null
 */
<|endoftext|>


<commit_before>const data = require('sdk/self').data;
const contextMenu = require('sdk/context-menu');
const prefsModule = require('sdk/simple-prefs');
const tabs = require('sdk/tabs');
const _ = require("sdk/l10n").get;

var widget;

var panel = require('sdk/panel').Panel({
  width: 620,
  height: 400,
  contentURL: data.url('easyshare.html'),
  contentScriptFile: data.url('easyshare.js'),
  contentScriptOptions: {warning: _('warning_nourl')},
  onShow: function () {
    if (widget) widget.contentURL = data.url('icons/widget.gif');
  },
  onHide: function () {
    if (widget) widget.contentURL = data.url('icons/widget.png');
  }
});

var showPanel = function (data) {
  panel.show();
  panel.port.emit('show', data);
};

var showWidget = function () {
  widget = require('sdk/widget').Widget({
    id: 'e-share',
    label: 'Easyshare',
    tooltip: _('widget_tooltip'),
    contentURL: data.url('icons/widget.png'),
    onClick: function () {
      let d = {
        thumb: '<img id="thumb" class="easyshare-placeholder" src="images/easyshare.png">',
        title: tabs.activeTab.title,
        url: tabs.activeTab.url,
        text: '',
        tags: ''
      }
      showPanel(d);
    }
  });
};

var removeWidget = function () {
  if (widget) {
    widget.destroy();
    widget = null;
  }
};

var contextMenuSetItems = function () {
  contextMenu.Item({
    label: _('ctx_menu_shareimage'),
    image: data.url('icons/ctxmenu.png'),
    data: 'image',
    context: contextMenu.SelectorContext('img'),
    contentScriptFile: data.url('data.js'),
    onMessage: function (message) {
      showPanel(message);
    }
  });
  contextMenu.Item({
    label: _('ctx_menu_sharevideo'),
    image: data.url('icons/ctxmenu.png'),
    data: 'video',
    context: [contextMenu.PageContext(),
              contextMenu.URLContext(/^\b(https?):\/\/www\.youtube\.com\/watch\?.*/)],
    contentScriptFile: data.url('data.js'),
    onMessage: function (message) {
      showPanel(message);
    }
  });
  contextMenu.Item({
    label: _('ctx_menu_shareselection'),
    image: data.url('icons/ctxmenu.png'),
    data: 'selection',
    context: contextMenu.SelectionContext(),
    contentScriptFile: data.url('data.js'),
    onMessage: function (message) {
      showPanel(message);
    }
  });
};

var handlePreferences = function (e) {
  panel.port.emit('prefs', prefsModule.prefs);
  let activated = prefsModule.prefs['pref_widget'];
  switch (e) {
    case 'onLoad':
      prefsModule.on('', function () { handlePreferences('onChange') });
      if (activated) {
        showWidget();
      }
      break;
    case 'onChange':
      if (!widget && activated) {
        showWidget();
      } else if (widget && !activated) {
        removeWidget();
      }
      break;
  }
};

exports.main = function (options, callbacks) {
  handlePreferences('onLoad');
  contextMenuSetItems();
  if (options.loadReason == 'install' || options.loadReason == 'upgrade') {
    tabs.open('http://arlogn.github.io/easyshare/index.html?q=' + options.loadReason);
    console.log('Easyshare: ' + options.loadReason);
  }
};
<commit_msg>main function revamped<commit_after>"use strict";

const panel = require("./panel");
const cmenu = require("./contextmenu");
const { Cc, Ci } = require("chrome");
const appInfo = Cc["@mozilla.org/xre/app-info;1"]
                .getService(Ci.nsIXULAppInfo);

exports.main = function(options) {

  let pnl = panel.init();

  if (parseInt(appInfo.version.split(".")[0]) >= 29) {
    // New SDK Australis UI action button
    const button = require("./button");
    button.init(pnl);
  } else {
    // Widget for Firefox <29
    const widget = require("./widget");
    widget.init(pnl);
  }

  cmenu.init(pnl);

  if (options.loadReason === "install" || options.loadReason === "upgrade") {
    require("sdk/tabs").open("http://arlogn.github.io/easyshare/index.html?q=" + options.loadReason);
  }
};<|endoftext|>


<commit_before>//Testing Module For Rush
var LISTENER_HOSTNAME = 'RelayA',
    LISTENER_PORT = '8030';
var http = require('http');
var global = require('../my_globals').C;
var async = require('async');
var os = require('os');
var server,
    end_point_req,
    end_point_res,
    client_res,
    client_req,
    relayer_header = {},
    options;
var oneway = function (method) {
    return function (callback) {
        console.log('\nONEWAY TEST FOR METHOD ' + method);
        //Create EndPoint server
        server = http.createServer(
            function (req, res) {
                exports.end_point_req = req;
                //verify Server Side
                console.log('Verify Server Side');
                if (req.url == '/testpath') {
                    console.log('OK- request URL: ' + req.url);
                }
                else {
                    console.log('FAIL- request URL:' + req.url);
                }
                if (req.method == method) {
                    console.log('OK- request METHOD:' + req.method);
                }
                else {
                    console.log('FAIL request METHOD:' + req.method);
                }
                if (req.method == 'POST' || req.method == 'PUT') {
                    var post_data = '';
                    req.on('data', function (chunk) {
                        chunk ? post_data += chunk : '';
                    });
                    req.on('end', function (chunk) {
                        chunk ? post_data += chunk : '';
                        //verify POST DATA
                        if (post_data == "POST/PUT TEST DATA") {
                            console.log('OK- POST/PUT DATA received OK')
                        }
                        else {
                            console.log('FAIL- WRONG POST/PUT DATA: ' + post_data);
                        }
                        //END OF THE SERVER FLOW for PUT y POST
                        callback && callback();
                    })
                }
                if (req.headers['testheader'] == 'FOO_TEST_VAL') {
                    console.log('OK HEADER FOUND: ' + req.headers['testheader']);
                }
                else {
                    console.log('FAIL HEADER: ' + req.headers['testheader']);
                }
                res.writeHead(200);
                res.write('TEST-oneway');
                res.end();
                exports.end_point_res = res;
                //Destroy Server
                server.close();
                if (req.method == 'GET' || req.method == 'DELETE') {
                    callback && callback();
                }
            }).listen(8765);
        //Launch a oneway request
        relayer_header[global.HEAD_RELAYER_HOST] = 'http://' + os.hostname() + ':8765/testpath';
        relayer_header['testheader'] = 'FOO_TEST_VAL';
        options =
        {
            hostname:LISTENER_HOSTNAME,
            port:LISTENER_PORT,
            method:method,
            headers:relayer_header
        };
        client_req = http.request(options, client_request_test_handler);
        if (method == 'POST' || method == 'PUT') {
            //Send post data
            client_req.write("POST/PUT TEST DATA");
        }
        client_req.end();
        exports.client_req = client_req;
    }
};
exports.oneway_get = oneway('GET');
exports.oneway_post = oneway('POST');
exports.oneway_put = oneway('PUT');
exports.oneway_delete = oneway('DELETE');
exports.oneway = function () {
    async.series([
        exports.oneway_get,
        exports.oneway_post,
        exports.oneway_put,
        exports.oneway_delete], function () {
        console.log('End ONEWAY TEST')
    });
}
var async_get = function (callback) {
    var relayer_header = {},
        end_point_server,
        callback_server;

    //Create end point server
    end_point_server = http.createServer(
        function (req, res) {
            console.log('OK- end point server reached');
            exports.end_point_req = req;
            res.writeHead(200);
            res.write('CALLBACK DATA');
            res.end();
            exports.end_point_res = res;
        }).listen(8765);
    //Create Callback Server
    callback_server = http.createServer(
        function (req, res) {
            console.log('OK- callback arrive');
            //it is going to receive a POST with 'CALLBACK DATA'
            var post_data = '';
            if (req.method == 'POST') {
                req.on('data', function (chunk) {
                    chunk ? post_data += chunk : '';
                });
                req.on('end', function (chunk) {
                    chunk ? post_data += chunk : '';
                    //verify the callback contents
                    exports.callback_data = post_data; //remove
                    //Exitpoint
                    end_point_server.close();
                    callback_server.close();
                    callback && callback();
                });
            }
            else {
                console.log('FAIL- POST HTTP CALLBACK EXPECTED:' + req.method);
            }
            res.writeHead(200);
            res.end();
        }).listen(8764);
    //Do request
    relayer_header[global.HEAD_RELAYER_HOST] = 'http://' + os.hostname() + ':8765';
    relayer_header[global.HEAD_RELAYER_HTTPCALLBACK] = 'http://' + os.hostname() + ':8764';
    relayer_header[global.HEAD_RELAYER_PERSISTENCE] = 'BODY';
    options =
    {
        hostname:LISTENER_HOSTNAME,
        port:LISTENER_PORT,
        method:'GET',
        headers:relayer_header
    };
    client_req = http.request(options, client_request_test_handler);
    client_req.end();
    exports.client_req = client_req;
};
exports.async_callback = async_get;
var client_request_test_handler = function (res) {
    var id = '';
    exports.client_res = res;
    res.on('data', function (chunk) {
        chunk ? id += chunk : '';
    });
    res.on('end', function (chunk) {
        chunk ? id += chunk : '';
        //Verify TEST
        console.log('Verify Client Side');
        //We got a 200 + id?
        if (res.statusCode == 200) {
            console.log('OK- 200 OK received');
        }
        else {
            console.log('FAIL-' + res.statusCode + ' received');
        }
        if (id != '' && id) {
            console.log('OK- ID: ' + id + ' received');
        }
        else {
            console.log('FAIL-  no id received');
        }
    });
}<commit_msg>Test for Async+callback<commit_after>//Testing Module For Rush
var LISTENER_HOSTNAME = 'RelayA',
    LISTENER_PORT = '8030';
var http = require('http');
var global = require('../my_globals').C;
var async = require('async');
var os = require('os');
var assert = require('assert');
var server,
    end_point_req,
    end_point_res,
    client_res,
    client_req,
    relayer_header = {},
    options;
var oneway = function (method) {
    return function (callback) {
        console.log('\nONEWAY TEST FOR METHOD ' + method);
        //Create EndPoint server
        server = http.createServer(
            function (req, res) {
                exports.end_point_req = req;
                //verify Server Side
                console.log('Verify Server Side');
                if (req.url == '/testpath') {
                    console.log('OK- request URL: ' + req.url);
                }
                else {
                    console.log('FAIL- request URL:' + req.url);
                }
                if (req.method == method) {
                    console.log('OK- request METHOD:' + req.method);
                }
                else {
                    console.log('FAIL request METHOD:' + req.method);
                }
                if (req.method == 'POST' || req.method == 'PUT') {
                    var post_data = '';
                    req.on('data', function (chunk) {
                        chunk ? post_data += chunk : '';
                    });
                    req.on('end', function (chunk) {
                        chunk ? post_data += chunk : '';
                        //verify POST DATA
                        if (post_data == "POST/PUT TEST DATA") {
                            console.log('OK- POST/PUT DATA received OK')
                        }
                        else {
                            console.log('FAIL- WRONG POST/PUT DATA: ' + post_data);
                        }
                        //END OF THE SERVER FLOW for PUT y POST
                        callback && callback();
                    })
                }
                if (req.headers['testheader'] == 'FOO_TEST_VAL') {
                    console.log('OK HEADER FOUND: ' + req.headers['testheader']);
                }
                else {
                    console.log('FAIL HEADER: ' + req.headers['testheader']);
                }
                res.writeHead(200);
                res.write('TEST-oneway');
                res.end();
                exports.end_point_res = res;
                //Destroy Server
                server.close();
                if (req.method == 'GET' || req.method == 'DELETE') {
                    callback && callback();
                }
            }).listen(8765);
        //Launch a oneway request
        relayer_header[global.HEAD_RELAYER_HOST] = 'http://' + os.hostname() + ':8765/testpath';
        relayer_header['testheader'] = 'FOO_TEST_VAL';
        options =
        {
            hostname:LISTENER_HOSTNAME,
            port:LISTENER_PORT,
            method:method,
            headers:relayer_header
        };
        client_req = http.request(options, client_request_test_handler);
        if (method == 'POST' || method == 'PUT') {
            //Send post data
            client_req.write("POST/PUT TEST DATA");
        }
        client_req.end();
        exports.client_req = client_req;
    }
};
exports.oneway_get = oneway('GET');
exports.oneway_post = oneway('POST');
exports.oneway_put = oneway('PUT');
exports.oneway_delete = oneway('DELETE');
exports.oneway = function () {
    async.series([
        exports.oneway_get,
        exports.oneway_post,
        exports.oneway_put,
        exports.oneway_delete], function () {
        console.log('End ONEWAY TEST')
    });
}
var async_get = function (callback) {
    var expected_callback,
        relayer_header = {},
        end_point_server,
        callback_server;
    //Create end point server
    end_point_server = http.createServer(
        function (req, res) {
            console.log('OK- end point server reached');
            exports.end_point_req = req;
            res.writeHead(200);
            res.write('CALLBACK DATA');
            res.end();
            exports.end_point_res = res;
        }).listen(8765);
    //Create Callback Server
    callback_server = http.createServer(
        function (req, res) {
            var post_data = '',
                expected_callback,
                expected_callback_JSON;

            console.log('OK- callback arrive');
            //it is going to receive a POST with 'CALLBACK DATA'
            if (req.method == 'POST') {
                req.on('data', function (chunk) {
                    chunk ? post_data += chunk : '';
                });
                req.on('end', function (chunk) {
                    chunk ? post_data += chunk : '';
                    //verify the callback contents
                    exports.callback_data = post_data; //remove
                    expected_callback = {
                        "statusCode":200,
                        "headers":{
                            "connection":"keep-alive",
                            "transfer-encoding":"chunked"
                        },
                        "body":"CALLBACK DATA",
                        "state":"relay_response"
                    };
                    expected_callback_JSON = JSON.stringify(expected_callback);
                    if (post_data==expected_callback_JSON){
                        console.log('OK- expected data at callback');
                    }
                    else
                    {

                        console.log('FAIL- NOT expected data at callback');
                        console.log(post_data);
                        console.log(expected_callback_JSON);

                    }
                    //Exitpoint
                    end_point_server.close();
                    callback_server.close();
                    callback && callback();
                });
            }
            else {
                console.log('FAIL- POST HTTP CALLBACK EXPECTED:' + req.method);
            }
            res.writeHead(200);
            res.end();
        }).listen(8764);
    //Do request
    relayer_header[global.HEAD_RELAYER_HOST] = 'http://' + os.hostname() + ':8765';
    relayer_header[global.HEAD_RELAYER_HTTPCALLBACK] = 'http://' + os.hostname() + ':8764';
    options =
    {
        hostname:LISTENER_HOSTNAME,
        port:LISTENER_PORT,
        method:'GET',
        headers:relayer_header
    };
    client_req = http.request(options, client_request_test_handler);
    client_req.end();
    exports.client_req = client_req;
};
exports.async_callback = async_get;
var client_request_test_handler = function (res) {
    var id = '';
    exports.client_res = res;
    res.on('data', function (chunk) {
        chunk ? id += chunk : '';
    });
    res.on('end', function (chunk) {
        chunk ? id += chunk : '';
        //Verify TEST
        console.log('Verify Client Side');
        //We got a 200 + id?
        if (res.statusCode == 200) {
            console.log('OK- 200 OK received');
        }
        else {
            console.log('FAIL-' + res.statusCode + ' received');
        }
        if (id != '' && id) {
            console.log('OK- ID: ' + id + ' received');
        }
        else {
            console.log('FAIL-  no id received');
        }
    });
}<|endoftext|>