<commit_before>let addonsDir = []
try {
	fs.accessSync('addons')
	let addonsDird = fs.readdirSync('addons')

	for(let i = 0; i < addonsDird.length; i++)
		addonsDir.push('addons/' + addonsDird[i])
} catch(e) {}
try {
	fs.accessSync('resources')
	let resourcesDir = fs.readdirSync('resources')

	for(let i = 0; i < resourcesDir.length; i++)
		resourcesDir.push('resources/' + resourcesDir[i])
} catch(e) {}

console.log('Checking addons: ' + addonsDir.join(', '))
for(let i = 0; i < addonsDir.length; i++) {
	try {
		fs.accessSync(`${addonsDir[i]}/package.json`)
		fs.accessSync(`${addonsDir[i]}/addon.js`)

		let pack = fs.readFileSync(`${addonsDir[i]}/package.json`, { encoding: 'utf8' })
		let infos = JSON.parse(pack)
		if(infos.hasOwnProperty('systems')) {
			if(infos.systems.indexOf(process.platform) >= 0) {
				let scriptElement = document.createElement('script')
				scriptElement.setAttribute('type', 'application/javascript')
				scriptElement.src = `${addonsDir[i]}/addon.js`

				document.querySelector('body').appendChild(scriptElement)
				console.log('Addon ' + addonsDir[i] + ' is being loaded')
			} else {
				console.log('Addon ' + addonsDir[i] + ' is not compatible with the current platform')
			}
		} else {
			console.log('Addon ' + addonsDir[i] + ' has no platform informations')
		}

	} catch(e) {
		console.error(e)
	}
}<commit_msg>Fixed major issue with endless loop<commit_after>let addonsDir = []
try {
	fs.accessSync('addons')
	let addonsDird = fs.readdirSync('addons')

	for(let i = 0; i < addonsDird.length; i++)
		addonsDir.push('addons/' + addonsDird[i])
} catch(e) {}
try {
	fs.accessSync('resources')
	let resourcesDir = fs.readdirSync('resources')

	for(let i = 0; i < resourcesDir.length; i++)
		addonsDir.push('resources/' + resourcesDir[i])
} catch(e) {}

console.log('Checking addons: ' + addonsDir.join(', '))
for(let i = 0; i < addonsDir.length; i++) {
	try {
		fs.accessSync(`${addonsDir[i]}/package.json`)
		fs.accessSync(`${addonsDir[i]}/addon.js`)

		let pack = fs.readFileSync(`${addonsDir[i]}/package.json`, { encoding: 'utf8' })
		let infos = JSON.parse(pack)
		if(infos.hasOwnProperty('systems')) {
			if(infos.systems.indexOf(process.platform) >= 0) {
				let scriptElement = document.createElement('script')
				scriptElement.setAttribute('type', 'application/javascript')
				scriptElement.src = `${addonsDir[i]}/addon.js`

				document.querySelector('body').appendChild(scriptElement)
				console.log('Addon ' + addonsDir[i] + ' is being loaded')
			} else {
				console.log('Addon ' + addonsDir[i] + ' is not compatible with the current platform')
			}
		} else {
			console.log('Addon ' + addonsDir[i] + ' has no platform informations')
		}

	} catch(e) {
		console.error(e)
	}
}<|endoftext|>


<commit_before>'use strict';

var Promise = require("bluebird");
var request = require("request");

var getAdvice = function () {
  return new Promise(function(resolve, reject) {
    // GET a random post from reddit's /r/WorstAdviceEver
    request('http://reddit.com/r/WorstAdviceEver.json', function (error, response, body) {
      if (!error && response.statusCode == 200) {
        var posts = JSON.parse(body).data.children
        var randomPostURI = posts[Math.floor(Math.random() * posts.length)].data.permalink;

        // GET a random answer
        request('http://reddit.com' + randomPostURI + '.json', function (error, response, body) {
          if (!error && response.statusCode == 200) {
            if (!JSON.parse(body)[1]) {
              resolve("Treat yo' self");
            } else {
              var responses = JSON.parse(body)[1].data.children;
              var randomResponse = responses[Math.floor(Math.random() * responses.length)].data.body;
              resolve(randomResponse);
            }
          }
        });
      }
    });
  });
};

module.exports = function () {
  process.stdin.resume();
  process.stdin.setEncoding('utf8');

  console.log("Hello! I'm a special piece of software programmed to help people make hard");
  console.log("decisions. Ask me anything and I'll dig deep into the internet to help you");
  console.log("find the answer to your problem.");

  process.stdin.on('data', function (text) {
    getAdvice()
      .then(function (advice) {
        console.log(advice);
        process.exit();
      });
  });
};
<commit_msg>Clean up advice wording<commit_after>'use strict';

var Promise = require("bluebird");
var request = require("request");

var getAdvice = function () {
  return new Promise(function(resolve, reject) {
    // GET a random post from reddit's /r/WorstAdviceEver
    request('http://reddit.com/r/WorstAdviceEver.json', function (error, response, body) {
      if (!error && response.statusCode == 200) {
        var posts = JSON.parse(body).data.children
        var randomPostURI = posts[Math.floor(Math.random() * posts.length)].data.permalink;

        // GET a random answer
        request('http://reddit.com' + randomPostURI + '.json', function (error, response, body) {
          if (!error && response.statusCode == 200) {
            if (!JSON.parse(body)[1]) {
              resolve("Treat yo' self");
            } else {
              var responses = JSON.parse(body)[1].data.children;
              var randomResponse = responses[Math.floor(Math.random() * responses.length)].data.body;
              resolve(randomResponse);
            }
          }
        });
      }
    });
  });
};

module.exports = function () {
  process.stdin.resume();
  process.stdin.setEncoding('utf8');

  console.log("Hello! I'm a programmed to help people make hard decisions. Ask me anything!");

  process.stdin.on('data', function (text) {
    getAdvice()
      .then(function (advice) {
        console.log(advice);
        process.exit();
      });
  });
};
<|endoftext|>


<commit_before>/**
 * Licensed under the MIT License
 *
 * @author   Kanstantsin A Kamkou (2ka.by), Alexander Günther
 * @license  http://www.opensource.org/licenses/mit-license.php The MIT License
 * @link     https://github.com/kkamkou/node-akamai-http-api
 *
 * Modified by MattW for MoL to support .create() method, and
 * extended for multiple instances to allow different cpcodes/configs
 * to be used at the same time. This requires a change in signature. Rather
 * than:
 * 		akamai = require('akamai-http-api') ;
 * use:
 * 		akamai = require('akamai-http-api').setConfig({config}) ;
 */

// required stuff
var request = require('request'),
  Stream = require('stream'),
  path = require('path'),
  crypto = require('crypto'),
  xml2js = require('xml2js'),
  qs = require('querystring'),
  _ = require('lodash');

// the class itself
var akamai = {} ;

/**
 * Generates a random number
 *
 * @returns {String}
 */
akamai.getUniqueId = function () {
  var str = '';
  for (var i = 0, r; i < 6; i++) {
    if ((i & 0x03) === 0) {
      r = Math.random() * 0x100000000;
    }
    str += r >>> ((i & 0x03) << 3) & 0xff;
  }
  return str + process.pid;
};

/**
 * Returns the config object
 *
 * @returns {Object}
 */
akamai.getConfig = function () {
  return this.config;
};

/**
 * Returns a set of headers for the authentication process
 *
 * @param {String} path
 * @param {Object} queryObj
 * @returns {Object}
 */
akamai.getHeaders = function (path, queryObj) {
  var authData, authSign, query;

  query = qs.stringify(_.merge({version: 1, action: 'du', format: 'xml'}, queryObj || {}));

  authData = [
    5, '0.0.0.0', '0.0.0.0', parseInt(Date.now() / 1000, 10), this.getUniqueId(),
    this.getConfig().keyName
  ].join(', ');

  authSign = crypto.createHmac('sha256', this.getConfig().key)
    .update([authData + path.replace(/\/$/, ''), 'x-akamai-acs-action:' + query, null].join("\n"))
    .digest('base64');

  return {
    'X-Akamai-ACS-Action': query,
    'X-Akamai-ACS-Auth-Data': authData,
    'X-Akamai-ACS-Auth-Sign': authSign
  };
};

/**
 * Adds http or https to the host
 *
 * @param {String} path
 * @returns {String}
 */
akamai.getUri = function (path) {
  var host = ['http', this.getConfig().ssl ? 's' : '', '://', this.getConfig().host].join('');
  return [host, path.replace(/(^\/|\/$)/g, '')].join('/');
};

/**
 * Converts a xml string to an object
 *
 * @param {String} data
 * @param {Function} cb
 */
akamai.getObjectFromXml = function (data, cb) {
  (new xml2js.Parser())
    .parseString(data, function (err, result) {
      if (err) {
        return cb(err);
      }

      cb(null, _.merge({}, result, function (a, b) {
        var obj = {};
        Object.keys(b).forEach(function (key) {
          if (key === '$') {
            obj.attribs = b[key];
          } else if (_.isArray(b[key])) {
            obj[key] = _.pluck(b[key], '$');
          }
        });
        return obj;
      }));
    });
};

akamai.setLogger = function(l) {
	this.logger = l ;
}

/**
 * Returns a request object for streaming
 *
 * @param {String} path
 * @param {Object} params
 * @param {Function} cb
 * @returns {request}
 */
akamai.getRequestObject = function (path, params, cb) {
  var self = this,
    callback = function () {},
    options = _.merge(
      {url: this.getUri(path), headers: this.getHeaders(path, params.headers)},
      params.request
    );

  var reqOptions = this.getConfig().request ;
  if (reqOptions) {
	_.assign(options,reqOptions) ;
  }

  if (typeof(cb) === 'function') {
    callback = function (err, resp, body) {
	  self.logger && self.logger("akamai-http-api:",path,params.headers,err) ;

      // we have an error
      if (err) {
    	err.message += "\t"+path+", "+JSON.stringify(params) ;
        return cb(err);
      }

      // wrong response code
      if (resp.statusCode >= 300) {
        var errMsg = 'The server sent us the ' + resp.statusCode + ' code';
        errMsg += "\t"+path+", "+JSON.stringify(params) ;
        if (self.getConfig().verbose && body) {
          errMsg += '. Body: ' + body;
        }
        err = new Error(errMsg) ;
        err.status = resp.statusCode ;
        return cb(err);
      }

      if (!body.match(/^<\?xml\s+/)) {
        return cb(null, {status: resp.statusCode});
      }

      self.getObjectFromXml(body, cb);
    };
  }

  return request(options, callback);
};

/** Custom helpers **/

akamai.fileExists = function (path, cb) {
  return this.stat(path, function (err, data) {
    if (err && err.message.indexOf('404 code') !== -1) {
      return cb(null, false);
    }
    if (data && data.stat && data.stat.file) {
      return cb(null, true);
    }
    return cb(err);
  });
};

/** Api functions **/

akamai.upload = function (stream, path, cb) {
  var options = {
    request: {method: 'put'},
    headers: {action: 'upload', 'upload-type': 'binary'}
  };
  stream.pipe(this.getRequestObject(path, options, cb));
  return this;
};

akamai.download = function (path, stream, cb) {
  this.getRequestObject(path, {headers: {action: 'download'}}, cb).pipe(stream);
  return this;
};

akamai.stat = function (path, cb) {
  this.getRequestObject(path, {headers: {action: 'stat'}}, cb);
  return this;
};

akamai.du = function (path, cb) {
  this.getRequestObject(path, {headers: {action: 'du'}}, cb);
  return this;
};

akamai.dir = function (path, cb) {
  this.getRequestObject(path, {headers: {action: 'dir'}}, cb);
  return this;
};

akamai.delete = function (path, cb) {
  this.getRequestObject(path, {request: {method: 'put'}, headers: {action: 'delete'}}, cb);
  return this;
};

akamai.mkdir = function (path, cb) {
  this.getRequestObject(path, {request: {method: 'put'}, headers: {action: 'mkdir'}}, cb);
  return this;
};

akamai.rmdir = function (path, cb) {
  this.getRequestObject(path, {request: {method: 'put'}, headers: {action: 'rmdir'}}, cb);
  return this;
};

akamai.rename = function (pathFrom, pathTo, cb) {
  var options = {
    request: {method: 'put'},
    headers: {action: 'rename', destination: pathTo}
  };
  this.getRequestObject(pathFrom, options, cb);
  return this;
};

akamai.symlink = function (pathFileTo, pathFileFrom, cb) {
  var options = {
    request: {method: 'put'},
    headers: {action: 'symlink', target: pathFileTo}
  };
  this.getRequestObject(pathFileFrom, options, cb);
  return this;
};

akamai.mtime = function (path, date, cb) {
  if (!(date instanceof Date)) {
    return cb(new TypeError('The date has to be an instance of Date'));
  }

  var options = {
    request: {method: 'put'},
    headers: {action: 'mtime', mtime: parseInt(date.getTime() / 1000, 10)}
  };
  this.getRequestObject(path, options, cb);
  return this;
};

/* Added by MattW for MOL */
/* Like upload, but creates the path beforehand */
akamai.create = function(source /*stream*/, cpcode, target, callback) {
	var self = this ;

	function mkdir(path,idx,done) {
    	if (idx>path.length)
    		return done(null) ;
		idx += 1 ;
    	var l = path.slice(0,idx).join("/") ;
    	self.mkdir(l,function(err){
    		if (err && err.status!=409 && err.status!=403) {
    			err.message = "mkdir("+l+") "+err.message ;
    			return done(err) ;
    		}
    		mkdir(path,idx,done) ;
    	});
	}

    // Create path
	target = path.join("/", "" + cpcode, "/", target) ;
    var dirPath = target.split("/") ;
    dirPath = dirPath.slice(0,dirPath.length-1).join("/") ;

    mkdir(dirPath.split("/"),2,function(err){
        if (err) {
            err.message = err.message+"\n"+JSON.stringify(self.getConfig()) ;
            return callback && callback(err);
        }
        if (!("pipe" in source)) {
        	// Probabaly not a stream
        	var original = source ;
        	source = new Stream();
        	source.pipe = function(dest) {
        	  dest.write(original);
        	  return dest;
        	};
        }
    	self.upload(source, target, function(err){
            if (err) {
                err.message = err.message+"\t"+JSON.stringify(self.getConfig()) ;
                return callback && callback(err);
            }
            callback && callback();
    	}) ;
    }) ;
}

// exporting outside
module.exports = {
	setConfig:function(config){
		return Object.create(akamai,{config:{value:_.assign({},config)}}) ;
	}
};
<commit_msg>Catch case where the source stream is null/undefined, and report via the callback<commit_after>/**
 * Licensed under the MIT License
 *
 * @author   Kanstantsin A Kamkou (2ka.by), Alexander Günther
 * @license  http://www.opensource.org/licenses/mit-license.php The MIT License
 * @link     https://github.com/kkamkou/node-akamai-http-api
 *
 * Modified by MattW for MoL to support .create() method, and
 * extended for multiple instances to allow different cpcodes/configs
 * to be used at the same time. This requires a change in signature. Rather
 * than:
 * 		akamai = require('akamai-http-api') ;
 * use:
 * 		akamai = require('akamai-http-api').setConfig({config}) ;
 */

// required stuff
var request = require('request'),
  Stream = require('stream'),
  path = require('path'),
  crypto = require('crypto'),
  xml2js = require('xml2js'),
  qs = require('querystring'),
  _ = require('lodash');

// the class itself
var akamai = {} ;

/**
 * Generates a random number
 *
 * @returns {String}
 */
akamai.getUniqueId = function () {
  var str = '';
  for (var i = 0, r; i < 6; i++) {
    if ((i & 0x03) === 0) {
      r = Math.random() * 0x100000000;
    }
    str += r >>> ((i & 0x03) << 3) & 0xff;
  }
  return str + process.pid;
};

/**
 * Returns the config object
 *
 * @returns {Object}
 */
akamai.getConfig = function () {
  return this.config;
};

/**
 * Returns a set of headers for the authentication process
 *
 * @param {String} path
 * @param {Object} queryObj
 * @returns {Object}
 */
akamai.getHeaders = function (path, queryObj) {
  var authData, authSign, query;

  query = qs.stringify(_.merge({version: 1, action: 'du', format: 'xml'}, queryObj || {}));

  authData = [
    5, '0.0.0.0', '0.0.0.0', parseInt(Date.now() / 1000, 10), this.getUniqueId(),
    this.getConfig().keyName
  ].join(', ');

  authSign = crypto.createHmac('sha256', this.getConfig().key)
    .update([authData + path.replace(/\/$/, ''), 'x-akamai-acs-action:' + query, null].join("\n"))
    .digest('base64');

  return {
    'X-Akamai-ACS-Action': query,
    'X-Akamai-ACS-Auth-Data': authData,
    'X-Akamai-ACS-Auth-Sign': authSign
  };
};

/**
 * Adds http or https to the host
 *
 * @param {String} path
 * @returns {String}
 */
akamai.getUri = function (path) {
  var host = ['http', this.getConfig().ssl ? 's' : '', '://', this.getConfig().host].join('');
  return [host, path.replace(/(^\/|\/$)/g, '')].join('/');
};

/**
 * Converts a xml string to an object
 *
 * @param {String} data
 * @param {Function} cb
 */
akamai.getObjectFromXml = function (data, cb) {
  (new xml2js.Parser())
    .parseString(data, function (err, result) {
      if (err) {
        return cb(err);
      }

      cb(null, _.merge({}, result, function (a, b) {
        var obj = {};
        Object.keys(b).forEach(function (key) {
          if (key === '$') {
            obj.attribs = b[key];
          } else if (_.isArray(b[key])) {
            obj[key] = _.pluck(b[key], '$');
          }
        });
        return obj;
      }));
    });
};

akamai.setLogger = function(l) {
	this.logger = l ;
}

/**
 * Returns a request object for streaming
 *
 * @param {String} path
 * @param {Object} params
 * @param {Function} cb
 * @returns {request}
 */
akamai.getRequestObject = function (path, params, cb) {
  var self = this,
    callback = function () {},
    options = _.merge(
      {url: this.getUri(path), headers: this.getHeaders(path, params.headers)},
      params.request
    );

  var reqOptions = this.getConfig().request ;
  if (reqOptions) {
	_.assign(options,reqOptions) ;
  }

  if (typeof(cb) === 'function') {
    callback = function (err, resp, body) {
	  self.logger && self.logger("akamai-http-api:",path,params.headers,err) ;

      // we have an error
      if (err) {
    	err.message += "\t"+path+", "+JSON.stringify(params) ;
        return cb(err);
      }

      // wrong response code
      if (resp.statusCode >= 300) {
        var errMsg = 'The server sent us the ' + resp.statusCode + ' code';
        errMsg += "\t"+path+", "+JSON.stringify(params) ;
        if (self.getConfig().verbose && body) {
          errMsg += '. Body: ' + body;
        }
        err = new Error(errMsg) ;
        err.status = resp.statusCode ;
        return cb(err);
      }

      if (!body.match(/^<\?xml\s+/)) {
        return cb(null, {status: resp.statusCode});
      }

      self.getObjectFromXml(body, cb);
    };
  }

  return request(options, callback);
};

/** Custom helpers **/

akamai.fileExists = function (path, cb) {
  return this.stat(path, function (err, data) {
    if (err && err.message.indexOf('404 code') !== -1) {
      return cb(null, false);
    }
    if (data && data.stat && data.stat.file) {
      return cb(null, true);
    }
    return cb(err);
  });
};

/** Api functions **/

akamai.upload = function (stream, path, cb) {
  var options = {
    request: {method: 'put'},
    headers: {action: 'upload', 'upload-type': 'binary'}
  };
  stream.pipe(this.getRequestObject(path, options, cb));
  return this;
};

akamai.download = function (path, stream, cb) {
  this.getRequestObject(path, {headers: {action: 'download'}}, cb).pipe(stream);
  return this;
};

akamai.stat = function (path, cb) {
  this.getRequestObject(path, {headers: {action: 'stat'}}, cb);
  return this;
};

akamai.du = function (path, cb) {
  this.getRequestObject(path, {headers: {action: 'du'}}, cb);
  return this;
};

akamai.dir = function (path, cb) {
  this.getRequestObject(path, {headers: {action: 'dir'}}, cb);
  return this;
};

akamai.delete = function (path, cb) {
  this.getRequestObject(path, {request: {method: 'put'}, headers: {action: 'delete'}}, cb);
  return this;
};

akamai.mkdir = function (path, cb) {
  this.getRequestObject(path, {request: {method: 'put'}, headers: {action: 'mkdir'}}, cb);
  return this;
};

akamai.rmdir = function (path, cb) {
  this.getRequestObject(path, {request: {method: 'put'}, headers: {action: 'rmdir'}}, cb);
  return this;
};

akamai.rename = function (pathFrom, pathTo, cb) {
  var options = {
    request: {method: 'put'},
    headers: {action: 'rename', destination: pathTo}
  };
  this.getRequestObject(pathFrom, options, cb);
  return this;
};

akamai.symlink = function (pathFileTo, pathFileFrom, cb) {
  var options = {
    request: {method: 'put'},
    headers: {action: 'symlink', target: pathFileTo}
  };
  this.getRequestObject(pathFileFrom, options, cb);
  return this;
};

akamai.mtime = function (path, date, cb) {
  if (!(date instanceof Date)) {
    return cb(new TypeError('The date has to be an instance of Date'));
  }

  var options = {
    request: {method: 'put'},
    headers: {action: 'mtime', mtime: parseInt(date.getTime() / 1000, 10)}
  };
  this.getRequestObject(path, options, cb);
  return this;
};

/* Added by MattW for MOL */
/* Like upload, but creates the path beforehand */
akamai.create = function(source /*stream*/, cpcode, target, callback) {
	var self = this ;

	function mkdir(path,idx,done) {
    	if (idx>path.length)
    		return done(null) ;
		idx += 1 ;
    	var l = path.slice(0,idx).join("/") ;
    	self.mkdir(l,function(err){
    		if (err && err.status!=409 && err.status!=403) {
    			err.message = "mkdir("+l+") "+err.message ;
    			return done(err) ;
    		}
    		mkdir(path,idx,done) ;
    	});
	}

    // Create path
	target = path.join("/", "" + cpcode, "/", target) ;
    var dirPath = target.split("/") ;
    dirPath = dirPath.slice(0,dirPath.length-1).join("/") ;

    mkdir(dirPath.split("/"),2,function(err){
        if (err) {
            err.message = err.message+"\n"+JSON.stringify(self.getConfig()) ;
            return callback && callback(err);
        }
        try {
            if (!("pipe" in source)) {
            	// Probabaly not a stream
            	var original = source ;
            	source = new Stream();
            	source.pipe = function(dest) {
            	  dest.write(original);
            	  return dest;
            	};
            }
        } catch(ex) {
        	return callback && callback(ex) ;
        }
    	self.upload(source, target, function(err){
            if (err) {
                err.message = err.message+"\t"+JSON.stringify(self.getConfig()) ;
                return callback && callback(err);
            }
            callback && callback();
    	}) ;
    }) ;
}

// exporting outside
module.exports = {
	setConfig:function(config){
		return Object.create(akamai,{config:{value:_.assign({},config)}}) ;
	}
};
<|endoftext|>


<commit_before>import _ from 'lodash';
import log from './logger';
import { getBuildInfo, updateBuildInfo, APPIUM_VER } from './config';
import { BaseDriver, errors, isSessionCommand } from 'appium-base-driver';
import B from 'bluebird';
import AsyncLock from 'async-lock';
import { inspectObject, parseCapsForInnerDriver, getPackageVersion } from './utils';
import semver from 'semver';

const PLATFORMS = {
  FAKE: 'fake',
  ANDROID: 'android',
  IOS: 'ios',
  WINDOWS: 'windows',
  MAC: 'mac',
  TIZEN: 'tizen',
};

const AUTOMATION_NAMES = {
  APPIUM: 'Appium',
  SELENDROID: 'Selendroid',
  UIAUTOMATOR2: 'UiAutomator2',
  UIAUTOMATOR1: 'UiAutomator1',
  XCUITEST: 'XCUITest',
  YOUIENGINE: 'YouiEngine',
  ESPRESSO: 'Espresso',
  TIZEN: 'Tizen',
  FAKE: 'Fake',
  INSTRUMENTS: 'Instruments',
  WINDOWS: 'Windows',
  MAC: 'Mac',
};
const DRIVER_MAP = {
  [AUTOMATION_NAMES.SELENDROID.toLowerCase()]: {
    driverClassName: 'SelendroidDriver',
    driverPackage: 'appium-selendroid-driver',
  },
  [AUTOMATION_NAMES.UIAUTOMATOR2.toLowerCase()]: {
    driverClassName: 'AndroidUiautomator2Driver',
    driverPackage: 'appium-uiautomator2-driver',
  },
  [AUTOMATION_NAMES.XCUITEST.toLowerCase()]: {
    driverClassName: 'XCUITestDriver',
    driverPackage: 'appium-xcuitest-driver',
  },
  [AUTOMATION_NAMES.YOUIENGINE.toLowerCase()]: {
    driverClassName: 'YouiEngineDriver',
    driverPackage: 'appium-youiengine-driver',
  },
  [AUTOMATION_NAMES.FAKE.toLowerCase()]: {
    driverClassName: 'FakeDriver',
    driverPackage: 'appium-fake-driver',
  },
  [AUTOMATION_NAMES.UIAUTOMATOR1.toLowerCase()]: {
    driverClassName: 'AndroidDriver',
    driverPackage: 'appium-android-driver',
  },
  [AUTOMATION_NAMES.INSTRUMENTS.toLowerCase()]: {
    driverClassName: 'IosDriver',
    driverPackage: 'appium-ios-driver',
  },
  [AUTOMATION_NAMES.WINDOWS.toLowerCase()]: {
    driverClassName: 'WindowsDriver',
    driverPackage: 'appium-windows-driver',
  },
  [AUTOMATION_NAMES.MAC.toLowerCase()]: {
    driverClassName: 'MacDriver',
    driverPackage: 'appium-mac-driver',
  },
  [AUTOMATION_NAMES.ESPRESSO.toLowerCase()]: {
    driverClassName: 'EspressoDriver',
    driverPackage: 'appium-espresso-driver',
  },
  [AUTOMATION_NAMES.TIZEN.toLowerCase()]: {
    driverClassName: 'TizenDriver',
    driverPackage: 'appium-package-driver',
  },
};

const PLATFORMS_MAP = {
  [PLATFORMS.FAKE]: () => AUTOMATION_NAMES.FAKE,
  [PLATFORMS.ANDROID]: (caps) => {
    const platformVersion = semver.valid(semver.coerce(caps.platformVersion));
    log.warn(`DeprecationWarning: 'automationName' capability was not provided. ` +
      `Future versions of Appium will require 'automationName' capability to be set for Android sessions.`);
    log.info(`Setting automation to '${AUTOMATION_NAMES.UIAUTOMATOR1}'. `);
    if (platformVersion && semver.satisfies(platformVersion, '>=6.0.0')) {
      log.warn(`Consider setting 'automationName' capability to '${AUTOMATION_NAMES.UIAUTOMATOR2}' ` +
        'on Android >= 6, since UIAutomator1 framework ' +
        'is not maintained anymore by the OS vendor.');
    }

    return AUTOMATION_NAMES.UIAUTOMATOR1;
  },
  [PLATFORMS.IOS]: (caps) => {
    const platformVersion = semver.valid(semver.coerce(caps.platformVersion));
    log.warn(`DeprecationWarning: 'automationName' capability was not provided. ` +
      `Future versions of Appium will require 'automationName' capability to be set for iOS sessions.`);
    if (platformVersion && semver.satisfies(platformVersion, '>=10.0.0')) {
      log.info('Requested iOS support with version >= 10, ' +
        `using '${AUTOMATION_NAMES.XCUITEST}' ` +
        'driver instead of UIAutomation-based driver, since the ' +
        'latter is unsupported on iOS 10 and up.');
      return AUTOMATION_NAMES.XCUITEST;
    }

    return AUTOMATION_NAMES.INSTRUMENTS;
  },
  [PLATFORMS.WINDOWS]: () => AUTOMATION_NAMES.WINDOWS,
  [PLATFORMS.MAC]: () => AUTOMATION_NAMES.MAC,
  [PLATFORMS.TIZEN]: () => AUTOMATION_NAMES.TIZEN,
};

const desiredCapabilityConstraints = {
  automationName: {
    presence: false,
    isString: true,
    inclusionCaseInsensitive: _.values(AUTOMATION_NAMES),
  },
  platformName: {
    presence: true,
    isString: true,
    inclusionCaseInsensitive: _.keys(PLATFORMS_MAP),
  },
};

const sessionsListGuard = new AsyncLock();
const pendingDriversGuard = new AsyncLock();

class AppiumDriver extends BaseDriver {
  constructor (args) {
    super();

    this.desiredCapConstraints = desiredCapabilityConstraints;

    // the main Appium Driver has no new command timeout
    this.newCommandTimeoutMs = 0;

    this.args = Object.assign({}, args);

    // Access to sessions list must be guarded with a Semaphore, because
    // it might be changed by other async calls at any time
    // It is not recommended to access this property directly from the outside
    this.sessions = {};

    // Access to pending drivers list must be guarded with a Semaphore, because
    // it might be changed by other async calls at any time
    // It is not recommended to access this property directly from the outside
    this.pendingDrivers = {};

    // allow this to happen in the background, so no `await`
    updateBuildInfo();
  }

  /**
   * Cancel commands queueing for the umbrella Appium driver
   */
  get isCommandsQueueEnabled () {
    return false;
  }

  sessionExists (sessionId) {
    const dstSession = this.sessions[sessionId];
    return dstSession && dstSession.sessionId !== null;
  }

  driverForSession (sessionId) {
    return this.sessions[sessionId];
  }

  getDriverAndVersionForCaps (caps) {
    if (!_.isString(caps.platformName)) {
      throw new Error('You must include a platformName capability');
    }

    const platformName = caps.platformName.toLowerCase();

    // we don't necessarily have an `automationName` capability
    let automationNameCap = caps.automationName;
    if (!_.isString(automationNameCap)) {
      const driverSelector = PLATFORMS_MAP[platformName];
      if (driverSelector) {
        automationNameCap = driverSelector(caps);
      }
    }
    automationNameCap = automationNameCap.toLowerCase();

    try {
      const {driverPackage, driverClassName} = DRIVER_MAP[automationNameCap];
      const driver = require(driverPackage)[driverClassName];
      return {
        driver,
        version: this.getDriverVersion(driver.name, driverPackage),
      };
    } catch (ign) {
      // error will be reported below, and here would come out as an unclear
      // problem with destructuring undefined
    }

    const msg = _.isString(caps.automationName)
      ? `Could not find a driver for automationName '${caps.automationName}' and platformName ` +
            `'${caps.platformName}'.`
      : `Could not find a driver for platformName '${caps.platformName}'.`;
    throw new Error(`${msg} Please check your desired capabilities.`);
  }

  getDriverVersion (driverName, driverPackage) {
    const version = getPackageVersion(driverPackage);
    if (version) {
      return version;
    }
    log.warn(`Unable to get version of driver '${driverName}'`);
  }

  async getStatus () { // eslint-disable-line require-await
    return {
      build: _.clone(getBuildInfo()),
    };
  }

  async getSessions () {
    const sessions = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions);
    return _.toPairs(sessions)
      .map(([id, driver]) => {
        return {id, capabilities: driver.caps};
      });
  }

  printNewSessionAnnouncement (caps, driverName, driverVersion) {
    const introString = driverVersion
      ? `Appium v${APPIUM_VER} creating new ${driverName} (v${driverVersion}) session`
      : `Appium v${APPIUM_VER} creating new ${driverName} session`;
    log.info(introString);
    log.info('Capabilities:');
    inspectObject(caps);
  }

  /**
   * Create a new session
   * @param {Object} jsonwpCaps JSONWP formatted desired capabilities
   * @param {Object} reqCaps Required capabilities (JSONWP standard)
   * @param {Object} w3cCapabilities W3C capabilities
   * @return {Array} Unique session ID and capabilities
   */
  async createSession (jsonwpCaps, reqCaps, w3cCapabilities) {
    const {defaultCapabilities} = this.args;
    let protocol;
    let innerSessionId, dCaps;

    try {
      // Parse the caps into a format that the InnerDriver will accept
      const parsedCaps = parseCapsForInnerDriver(
        jsonwpCaps,
        w3cCapabilities,
        this.desiredCapConstraints,
        defaultCapabilities
      );

      const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, error} = parsedCaps;
      protocol = parsedCaps.protocol;

      // If the parsing of the caps produced an error, throw it in here
      if (error) {
        throw error;
      }

      const {driver: InnerDriver, version: driverVersion} = this.getDriverAndVersionForCaps(desiredCaps);
      this.printNewSessionAnnouncement(desiredCaps, InnerDriver.name, driverVersion);

      if (this.args.sessionOverride) {
        const sessionIdsToDelete = await sessionsListGuard.acquire(AppiumDriver.name, () => _.keys(this.sessions));
        if (sessionIdsToDelete.length) {
          log.info(`Session override is on. Deleting other ${sessionIdsToDelete.length} active session${sessionIdsToDelete.length ? '' : 's'}.`);
          try {
            await B.map(sessionIdsToDelete, (id) => this.deleteSession(id));
          } catch (ign) {}
        }
      }

      let runningDriversData, otherPendingDriversData;
      const d = new InnerDriver(this.args);
      if (this.args.relaxedSecurityEnabled) {
        log.info(`Applying relaxed security to '${InnerDriver.name}' as per server command line argument`);
        d.relaxedSecurityEnabled = true;
      }
      // This assignment is required for correct web sockets functionality inside the driver
      d.server = this.server;
      try {
        runningDriversData = await this.curSessionDataForDriver(InnerDriver);
      } catch (e) {
        throw new errors.SessionNotCreatedError(e.message);
      }
      await pendingDriversGuard.acquire(AppiumDriver.name, () => {
        this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
        otherPendingDriversData = this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData);
        this.pendingDrivers[InnerDriver.name].push(d);
      });

      try {
        [innerSessionId, dCaps] = await d.createSession(
          processedJsonwpCapabilities,
          reqCaps,
          processedW3CCapabilities,
          [...runningDriversData, ...otherPendingDriversData]
        );
        protocol = d.protocol;
        await sessionsListGuard.acquire(AppiumDriver.name, () => {
          this.sessions[innerSessionId] = d;
        });
      } finally {
        await pendingDriversGuard.acquire(AppiumDriver.name, () => {
          _.pull(this.pendingDrivers[InnerDriver.name], d);
        });
      }

      // this is an async function but we don't await it because it handles
      // an out-of-band promise which is fulfilled if the inner driver
      // unexpectedly shuts down
      this.attachUnexpectedShutdownHandler(d, innerSessionId);

      log.info(`New ${InnerDriver.name} session created successfully, session ` +
              `${innerSessionId} added to master session list`);

      // set the New Command Timeout for the inner driver
      d.startNewCommandTimeout();
    } catch (error) {
      return {
        protocol,
        error,
      };
    }

    return {
      protocol,
      value: [innerSessionId, dCaps, protocol]
    };
  }

  async attachUnexpectedShutdownHandler (driver, innerSessionId) {
    // Remove the session on unexpected shutdown, so that we are in a position
    // to open another session later on.
    // TODO: this should be removed and replaced by a onShutdown callback.
    try {
      await driver.onUnexpectedShutdown; // this is a cancellable promise
      // if we get here, we've had an unexpected shutdown, so error
      throw new Error('Unexpected shutdown');
    } catch (e) {
      if (e instanceof B.CancellationError) {
        // if we cancelled the unexpected shutdown promise, that means we
        // no longer care about it, and can safely ignore it
        return;
      }
      log.warn(`Closing session, cause was '${e.message}'`);
      log.info(`Removing session ${innerSessionId} from our master session list`);
      await sessionsListGuard.acquire(AppiumDriver.name, () => {
        delete this.sessions[innerSessionId];
      });
    }
  }

  async curSessionDataForDriver (InnerDriver) {
    const sessions = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions);
    const data = _.values(sessions)
                   .filter((s) => s.constructor.name === InnerDriver.name)
                   .map((s) => s.driverData);
    for (let datum of data) {
      if (!datum) {
        throw new Error(`Problem getting session data for driver type ` +
                        `${InnerDriver.name}; does it implement 'get ` +
                        `driverData'?`);
      }
    }
    return data;
  }

  async deleteSession (sessionId) {
    let protocol;
    try {
      let otherSessionsData = null;
      let dstSession = null;
      await sessionsListGuard.acquire(AppiumDriver.name, () => {
        if (!this.sessions[sessionId]) {
          return;
        }
        const curConstructorName = this.sessions[sessionId].constructor.name;
        otherSessionsData = _.toPairs(this.sessions)
              .filter(([key, value]) => value.constructor.name === curConstructorName && key !== sessionId)
              .map(([, value]) => value.driverData);
        dstSession = this.sessions[sessionId];
        protocol = dstSession.protocol;
        log.info(`Removing session ${sessionId} from our master session list`);
        // regardless of whether the deleteSession completes successfully or not
        // make the session unavailable, because who knows what state it might
        // be in otherwise
        delete this.sessions[sessionId];
      });
      return {
        protocol,
        value: await dstSession.deleteSession(sessionId, otherSessionsData),
      };
    } catch (e) {
      log.error(`Had trouble ending session ${sessionId}: ${e.message}`);
      return {
        protocol,
        error: e,
      };
    }
  }

  async executeCommand (cmd, ...args) {
    // getStatus command should not be put into queue. If we do it as part of super.executeCommand, it will be added to queue.
    // There will be lot of status commands in queue during createSession command, as createSession can take up to or more than a minute.
    if (cmd === 'getStatus') {
      return await this.getStatus();
    }

    if (isAppiumDriverCommand(cmd)) {
      return await super.executeCommand(cmd, ...args);
    }

    const sessionId = _.last(args);
    const dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions[sessionId]);
    if (!dstSession) {
      throw new Error(`The session with id '${sessionId}' does not exist`);
    }

    let res = {
      protocol: dstSession.protocol
    };

    try {
      res.value = await dstSession.executeCommand(cmd, ...args);
    } catch (e) {
      res.error = e;
    }
    return res;
  }

  proxyActive (sessionId) {
    const dstSession = this.sessions[sessionId];
    return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
  }

  getProxyAvoidList (sessionId) {
    const dstSession = this.sessions[sessionId];
    return dstSession ? dstSession.getProxyAvoidList() : [];
  }

  canProxy (sessionId) {
    const dstSession = this.sessions[sessionId];
    return dstSession && dstSession.canProxy(sessionId);
  }
}

// help decide which commands should be proxied to sub-drivers and which
// should be handled by this, our umbrella driver
function isAppiumDriverCommand (cmd) {
  return !isSessionCommand(cmd) || cmd === 'deleteSession';
}

export { AppiumDriver };
<commit_msg>Fixed to set tizen driver in appium (#12270)<commit_after>import _ from 'lodash';
import log from './logger';
import { getBuildInfo, updateBuildInfo, APPIUM_VER } from './config';
import { BaseDriver, errors, isSessionCommand } from 'appium-base-driver';
import B from 'bluebird';
import AsyncLock from 'async-lock';
import { inspectObject, parseCapsForInnerDriver, getPackageVersion } from './utils';
import semver from 'semver';

const PLATFORMS = {
  FAKE: 'fake',
  ANDROID: 'android',
  IOS: 'ios',
  WINDOWS: 'windows',
  MAC: 'mac',
  TIZEN: 'tizen',
};

const AUTOMATION_NAMES = {
  APPIUM: 'Appium',
  SELENDROID: 'Selendroid',
  UIAUTOMATOR2: 'UiAutomator2',
  UIAUTOMATOR1: 'UiAutomator1',
  XCUITEST: 'XCUITest',
  YOUIENGINE: 'YouiEngine',
  ESPRESSO: 'Espresso',
  TIZEN: 'Tizen',
  FAKE: 'Fake',
  INSTRUMENTS: 'Instruments',
  WINDOWS: 'Windows',
  MAC: 'Mac',
};
const DRIVER_MAP = {
  [AUTOMATION_NAMES.SELENDROID.toLowerCase()]: {
    driverClassName: 'SelendroidDriver',
    driverPackage: 'appium-selendroid-driver',
  },
  [AUTOMATION_NAMES.UIAUTOMATOR2.toLowerCase()]: {
    driverClassName: 'AndroidUiautomator2Driver',
    driverPackage: 'appium-uiautomator2-driver',
  },
  [AUTOMATION_NAMES.XCUITEST.toLowerCase()]: {
    driverClassName: 'XCUITestDriver',
    driverPackage: 'appium-xcuitest-driver',
  },
  [AUTOMATION_NAMES.YOUIENGINE.toLowerCase()]: {
    driverClassName: 'YouiEngineDriver',
    driverPackage: 'appium-youiengine-driver',
  },
  [AUTOMATION_NAMES.FAKE.toLowerCase()]: {
    driverClassName: 'FakeDriver',
    driverPackage: 'appium-fake-driver',
  },
  [AUTOMATION_NAMES.UIAUTOMATOR1.toLowerCase()]: {
    driverClassName: 'AndroidDriver',
    driverPackage: 'appium-android-driver',
  },
  [AUTOMATION_NAMES.INSTRUMENTS.toLowerCase()]: {
    driverClassName: 'IosDriver',
    driverPackage: 'appium-ios-driver',
  },
  [AUTOMATION_NAMES.WINDOWS.toLowerCase()]: {
    driverClassName: 'WindowsDriver',
    driverPackage: 'appium-windows-driver',
  },
  [AUTOMATION_NAMES.MAC.toLowerCase()]: {
    driverClassName: 'MacDriver',
    driverPackage: 'appium-mac-driver',
  },
  [AUTOMATION_NAMES.ESPRESSO.toLowerCase()]: {
    driverClassName: 'EspressoDriver',
    driverPackage: 'appium-espresso-driver',
  },
  [AUTOMATION_NAMES.TIZEN.toLowerCase()]: {
    driverClassName: 'TizenDriver',
    driverPackage: 'appium-tizen-driver',
  },
};

const PLATFORMS_MAP = {
  [PLATFORMS.FAKE]: () => AUTOMATION_NAMES.FAKE,
  [PLATFORMS.ANDROID]: (caps) => {
    const platformVersion = semver.valid(semver.coerce(caps.platformVersion));
    log.warn(`DeprecationWarning: 'automationName' capability was not provided. ` +
      `Future versions of Appium will require 'automationName' capability to be set for Android sessions.`);
    log.info(`Setting automation to '${AUTOMATION_NAMES.UIAUTOMATOR1}'. `);
    if (platformVersion && semver.satisfies(platformVersion, '>=6.0.0')) {
      log.warn(`Consider setting 'automationName' capability to '${AUTOMATION_NAMES.UIAUTOMATOR2}' ` +
        'on Android >= 6, since UIAutomator1 framework ' +
        'is not maintained anymore by the OS vendor.');
    }

    return AUTOMATION_NAMES.UIAUTOMATOR1;
  },
  [PLATFORMS.IOS]: (caps) => {
    const platformVersion = semver.valid(semver.coerce(caps.platformVersion));
    log.warn(`DeprecationWarning: 'automationName' capability was not provided. ` +
      `Future versions of Appium will require 'automationName' capability to be set for iOS sessions.`);
    if (platformVersion && semver.satisfies(platformVersion, '>=10.0.0')) {
      log.info('Requested iOS support with version >= 10, ' +
        `using '${AUTOMATION_NAMES.XCUITEST}' ` +
        'driver instead of UIAutomation-based driver, since the ' +
        'latter is unsupported on iOS 10 and up.');
      return AUTOMATION_NAMES.XCUITEST;
    }

    return AUTOMATION_NAMES.INSTRUMENTS;
  },
  [PLATFORMS.WINDOWS]: () => AUTOMATION_NAMES.WINDOWS,
  [PLATFORMS.MAC]: () => AUTOMATION_NAMES.MAC,
  [PLATFORMS.TIZEN]: () => AUTOMATION_NAMES.TIZEN,
};

const desiredCapabilityConstraints = {
  automationName: {
    presence: false,
    isString: true,
    inclusionCaseInsensitive: _.values(AUTOMATION_NAMES),
  },
  platformName: {
    presence: true,
    isString: true,
    inclusionCaseInsensitive: _.keys(PLATFORMS_MAP),
  },
};

const sessionsListGuard = new AsyncLock();
const pendingDriversGuard = new AsyncLock();

class AppiumDriver extends BaseDriver {
  constructor (args) {
    super();

    this.desiredCapConstraints = desiredCapabilityConstraints;

    // the main Appium Driver has no new command timeout
    this.newCommandTimeoutMs = 0;

    this.args = Object.assign({}, args);

    // Access to sessions list must be guarded with a Semaphore, because
    // it might be changed by other async calls at any time
    // It is not recommended to access this property directly from the outside
    this.sessions = {};

    // Access to pending drivers list must be guarded with a Semaphore, because
    // it might be changed by other async calls at any time
    // It is not recommended to access this property directly from the outside
    this.pendingDrivers = {};

    // allow this to happen in the background, so no `await`
    updateBuildInfo();
  }

  /**
   * Cancel commands queueing for the umbrella Appium driver
   */
  get isCommandsQueueEnabled () {
    return false;
  }

  sessionExists (sessionId) {
    const dstSession = this.sessions[sessionId];
    return dstSession && dstSession.sessionId !== null;
  }

  driverForSession (sessionId) {
    return this.sessions[sessionId];
  }

  getDriverAndVersionForCaps (caps) {
    if (!_.isString(caps.platformName)) {
      throw new Error('You must include a platformName capability');
    }

    const platformName = caps.platformName.toLowerCase();

    // we don't necessarily have an `automationName` capability
    let automationNameCap = caps.automationName;
    if (!_.isString(automationNameCap)) {
      const driverSelector = PLATFORMS_MAP[platformName];
      if (driverSelector) {
        automationNameCap = driverSelector(caps);
      }
    }
    automationNameCap = automationNameCap.toLowerCase();

    try {
      const {driverPackage, driverClassName} = DRIVER_MAP[automationNameCap];
      const driver = require(driverPackage)[driverClassName];
      return {
        driver,
        version: this.getDriverVersion(driver.name, driverPackage),
      };
    } catch (ign) {
      // error will be reported below, and here would come out as an unclear
      // problem with destructuring undefined
    }

    const msg = _.isString(caps.automationName)
      ? `Could not find a driver for automationName '${caps.automationName}' and platformName ` +
            `'${caps.platformName}'.`
      : `Could not find a driver for platformName '${caps.platformName}'.`;
    throw new Error(`${msg} Please check your desired capabilities.`);
  }

  getDriverVersion (driverName, driverPackage) {
    const version = getPackageVersion(driverPackage);
    if (version) {
      return version;
    }
    log.warn(`Unable to get version of driver '${driverName}'`);
  }

  async getStatus () { // eslint-disable-line require-await
    return {
      build: _.clone(getBuildInfo()),
    };
  }

  async getSessions () {
    const sessions = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions);
    return _.toPairs(sessions)
      .map(([id, driver]) => {
        return {id, capabilities: driver.caps};
      });
  }

  printNewSessionAnnouncement (caps, driverName, driverVersion) {
    const introString = driverVersion
      ? `Appium v${APPIUM_VER} creating new ${driverName} (v${driverVersion}) session`
      : `Appium v${APPIUM_VER} creating new ${driverName} session`;
    log.info(introString);
    log.info('Capabilities:');
    inspectObject(caps);
  }

  /**
   * Create a new session
   * @param {Object} jsonwpCaps JSONWP formatted desired capabilities
   * @param {Object} reqCaps Required capabilities (JSONWP standard)
   * @param {Object} w3cCapabilities W3C capabilities
   * @return {Array} Unique session ID and capabilities
   */
  async createSession (jsonwpCaps, reqCaps, w3cCapabilities) {
    const {defaultCapabilities} = this.args;
    let protocol;
    let innerSessionId, dCaps;

    try {
      // Parse the caps into a format that the InnerDriver will accept
      const parsedCaps = parseCapsForInnerDriver(
        jsonwpCaps,
        w3cCapabilities,
        this.desiredCapConstraints,
        defaultCapabilities
      );

      const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities, error} = parsedCaps;
      protocol = parsedCaps.protocol;

      // If the parsing of the caps produced an error, throw it in here
      if (error) {
        throw error;
      }

      const {driver: InnerDriver, version: driverVersion} = this.getDriverAndVersionForCaps(desiredCaps);
      this.printNewSessionAnnouncement(desiredCaps, InnerDriver.name, driverVersion);

      if (this.args.sessionOverride) {
        const sessionIdsToDelete = await sessionsListGuard.acquire(AppiumDriver.name, () => _.keys(this.sessions));
        if (sessionIdsToDelete.length) {
          log.info(`Session override is on. Deleting other ${sessionIdsToDelete.length} active session${sessionIdsToDelete.length ? '' : 's'}.`);
          try {
            await B.map(sessionIdsToDelete, (id) => this.deleteSession(id));
          } catch (ign) {}
        }
      }

      let runningDriversData, otherPendingDriversData;
      const d = new InnerDriver(this.args);
      if (this.args.relaxedSecurityEnabled) {
        log.info(`Applying relaxed security to '${InnerDriver.name}' as per server command line argument`);
        d.relaxedSecurityEnabled = true;
      }
      // This assignment is required for correct web sockets functionality inside the driver
      d.server = this.server;
      try {
        runningDriversData = await this.curSessionDataForDriver(InnerDriver);
      } catch (e) {
        throw new errors.SessionNotCreatedError(e.message);
      }
      await pendingDriversGuard.acquire(AppiumDriver.name, () => {
        this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
        otherPendingDriversData = this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData);
        this.pendingDrivers[InnerDriver.name].push(d);
      });

      try {
        [innerSessionId, dCaps] = await d.createSession(
          processedJsonwpCapabilities,
          reqCaps,
          processedW3CCapabilities,
          [...runningDriversData, ...otherPendingDriversData]
        );
        protocol = d.protocol;
        await sessionsListGuard.acquire(AppiumDriver.name, () => {
          this.sessions[innerSessionId] = d;
        });
      } finally {
        await pendingDriversGuard.acquire(AppiumDriver.name, () => {
          _.pull(this.pendingDrivers[InnerDriver.name], d);
        });
      }

      // this is an async function but we don't await it because it handles
      // an out-of-band promise which is fulfilled if the inner driver
      // unexpectedly shuts down
      this.attachUnexpectedShutdownHandler(d, innerSessionId);

      log.info(`New ${InnerDriver.name} session created successfully, session ` +
              `${innerSessionId} added to master session list`);

      // set the New Command Timeout for the inner driver
      d.startNewCommandTimeout();
    } catch (error) {
      return {
        protocol,
        error,
      };
    }

    return {
      protocol,
      value: [innerSessionId, dCaps, protocol]
    };
  }

  async attachUnexpectedShutdownHandler (driver, innerSessionId) {
    // Remove the session on unexpected shutdown, so that we are in a position
    // to open another session later on.
    // TODO: this should be removed and replaced by a onShutdown callback.
    try {
      await driver.onUnexpectedShutdown; // this is a cancellable promise
      // if we get here, we've had an unexpected shutdown, so error
      throw new Error('Unexpected shutdown');
    } catch (e) {
      if (e instanceof B.CancellationError) {
        // if we cancelled the unexpected shutdown promise, that means we
        // no longer care about it, and can safely ignore it
        return;
      }
      log.warn(`Closing session, cause was '${e.message}'`);
      log.info(`Removing session ${innerSessionId} from our master session list`);
      await sessionsListGuard.acquire(AppiumDriver.name, () => {
        delete this.sessions[innerSessionId];
      });
    }
  }

  async curSessionDataForDriver (InnerDriver) {
    const sessions = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions);
    const data = _.values(sessions)
                   .filter((s) => s.constructor.name === InnerDriver.name)
                   .map((s) => s.driverData);
    for (let datum of data) {
      if (!datum) {
        throw new Error(`Problem getting session data for driver type ` +
                        `${InnerDriver.name}; does it implement 'get ` +
                        `driverData'?`);
      }
    }
    return data;
  }

  async deleteSession (sessionId) {
    let protocol;
    try {
      let otherSessionsData = null;
      let dstSession = null;
      await sessionsListGuard.acquire(AppiumDriver.name, () => {
        if (!this.sessions[sessionId]) {
          return;
        }
        const curConstructorName = this.sessions[sessionId].constructor.name;
        otherSessionsData = _.toPairs(this.sessions)
              .filter(([key, value]) => value.constructor.name === curConstructorName && key !== sessionId)
              .map(([, value]) => value.driverData);
        dstSession = this.sessions[sessionId];
        protocol = dstSession.protocol;
        log.info(`Removing session ${sessionId} from our master session list`);
        // regardless of whether the deleteSession completes successfully or not
        // make the session unavailable, because who knows what state it might
        // be in otherwise
        delete this.sessions[sessionId];
      });
      return {
        protocol,
        value: await dstSession.deleteSession(sessionId, otherSessionsData),
      };
    } catch (e) {
      log.error(`Had trouble ending session ${sessionId}: ${e.message}`);
      return {
        protocol,
        error: e,
      };
    }
  }

  async executeCommand (cmd, ...args) {
    // getStatus command should not be put into queue. If we do it as part of super.executeCommand, it will be added to queue.
    // There will be lot of status commands in queue during createSession command, as createSession can take up to or more than a minute.
    if (cmd === 'getStatus') {
      return await this.getStatus();
    }

    if (isAppiumDriverCommand(cmd)) {
      return await super.executeCommand(cmd, ...args);
    }

    const sessionId = _.last(args);
    const dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions[sessionId]);
    if (!dstSession) {
      throw new Error(`The session with id '${sessionId}' does not exist`);
    }

    let res = {
      protocol: dstSession.protocol
    };

    try {
      res.value = await dstSession.executeCommand(cmd, ...args);
    } catch (e) {
      res.error = e;
    }
    return res;
  }

  proxyActive (sessionId) {
    const dstSession = this.sessions[sessionId];
    return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
  }

  getProxyAvoidList (sessionId) {
    const dstSession = this.sessions[sessionId];
    return dstSession ? dstSession.getProxyAvoidList() : [];
  }

  canProxy (sessionId) {
    const dstSession = this.sessions[sessionId];
    return dstSession && dstSession.canProxy(sessionId);
  }
}

// help decide which commands should be proxied to sub-drivers and which
// should be handled by this, our umbrella driver
function isAppiumDriverCommand (cmd) {
  return !isSessionCommand(cmd) || cmd === 'deleteSession';
}

export { AppiumDriver };
<|endoftext|>


<commit_before>/**
   * `before()` is run before executing any of the `targets`
   * defined below.
   *
   * This is where we can validate user input, configure default
   * scope variables, get extra dependencies, and so on.
   *
   * @param  {Object} scope
   * @param  {Function} cb    [callback]
   */
function before(scope, cb) {

		this.INVALID_SCOPE_VARIABLE = function  (varname, details, message) {
		  var DEFAULT_MESSAGE =
		  'Issue encountered in generator "angular":\n'+
		  'Missing required scope variable: `%s`"\n' +
		  'If you are the author of `sails-generate-angular`, please resolve this '+
		  'issue and publish a new patch release.';

		  message = (message || DEFAULT_MESSAGE) + (details ? '\n'+details : '');
		  message = util.inspect(message, varname);

  		return new Error(message);
		}
    // scope.args are the raw command line arguments.
    //
    // e.g. if someone runs:
    // $ sails generate angular user find create update
    // then `scope.args` would be `['user', 'find', 'create', 'update']`
    // if (!scope.args[0]) {
    //   return cb( new Error('Please provide a name for this angular.') );
    // }

    // scope.rootPath is the base path for this generator
    //
    // e.g. if this generator specified the target:
    // './Foobar.md': { copy: 'Foobar.md' }
    //
    // And someone ran this generator from `/Users/dbowie/sailsStuff`,
    // then `/Users/dbowie/sailsStuff/Foobar.md` would be created.
    if (!scope.rootPath) {
      return cb( this.INVALID_SCOPE_VARIABLE('rootPath') );
    }

    // Attach defaults
    _.defaults(scope, {
      createdAt: new Date()
    });

    // Decide the output filename for use in targets below:
    // scope.filename = scope.args[0];

    // Add other stuff to the scope for use in our templates:
    // scope.whatIsThis = 'an example file created at '+scope.createdAt;

    // When finished, we trigger a callback with no error
    // to begin generating files/folders as specified by
    // the `targets` below.
    cb();
}

module.export = before;
<commit_msg>Fix before function<commit_after>/**
   * `before()` is run before executing any of the `targets`
   * defined below.
   *
   * This is where we can validate user input, configure default
   * scope variables, get extra dependencies, and so on.
   *
   * @param  {Object} scope
   * @param  {Function} cb    [callback]
   */
function before(scope, cb) {

		this.INVALID_SCOPE_VARIABLE = function  (varname, details, message) {
		  var DEFAULT_MESSAGE =
		  'Issue encountered in generator "angular":\n'+
		  'Missing required scope variable: `%s`"\n' +
		  'If you are the author of `sails-generate-angular`, please resolve this '+
		  'issue and publish a new patch release.';

		  message = (message || DEFAULT_MESSAGE) + (details ? '\n'+details : '');
		  message = util.inspect(message, varname);

  		return new Error(message);
		}
    // scope.args are the raw command line arguments.
    //
    // e.g. if someone runs:
    // $ sails generate angular user find create update
    // then `scope.args` would be `['user', 'find', 'create', 'update']`
    // if (!scope.args[0]) {
    //   return cb( new Error('Please provide a name for this angular.') );
    // }

    // scope.rootPath is the base path for this generator
    //
    // e.g. if this generator specified the target:
    // './Foobar.md': { copy: 'Foobar.md' }
    //
    // And someone ran this generator from `/Users/dbowie/sailsStuff`,
    // then `/Users/dbowie/sailsStuff/Foobar.md` would be created.
    if (!scope.rootPath) {
      return cb( this.INVALID_SCOPE_VARIABLE('rootPath') );
    }

    // Attach defaults
    _.defaults(scope, {
      createdAt: new Date()
    });

    // Decide the output filename for use in targets below:
    // scope.filename = scope.args[0];

    // Add other stuff to the scope for use in our templates:
    // scope.whatIsThis = 'an example file created at '+scope.createdAt;

    // When finished, we trigger a callback with no error
    // to begin generating files/folders as specified by
    // the `targets` below.
    cb();
}

module.exports = before;
<|endoftext|>


<commit_before>var B = exports.Binary;
var BS = exports.ByteString;
var BA = exports.ByteArray;

var Binary_get = function(index) {
	return this[index];
}

var Binary_copy = function(target, start, stop, targetStart) {
	var from = start || 0;
	var to = stop || this.length;
	var targetOffset = targetStart || 0;
	for (var i=0; i<to-from; i++) {
		target[i+targetOffset] = this[i+from];
	}
}

var Binary_join = function(array, separator) {
	var arr = [];
	for (var i=0;i<array.length;i++) {
		if (i && arguments.length > 1) { arr.push(separator); }
		arr.push(array[i]);
	}
	return new this(arr);
}

var Binary_split = function(delimiter, options) {
	var o = {
		count: -1,
		includeDelimiter: true
	}
	for (var p in options) { o[p] = options[p]; }

	var result = [];
	var start = 0;
	var index = -1;
	do {
		if (result.length == o.count) { return result; }

		index = this.indexOf(delimiter, start);
		system.stdout("index: "+index+"\n");
		if (index == -1) { break; }

		var end = index;
		if (o.includeDelimiter) { end += 1; } /* delimiter.length */
		system.stdout("slice from "+start+" to "+end+"\n");
		result.push(this.slice(start, end));
		start = index + 1; /* delimiter.length */
	} while (index != -1);

	if (start < this.length) {
		result.push(this.slice(start));
	}

	return result;
}

/* Binary stuff */

B.prototype.toArray = function(charset) {
	var arr = [];
	var source = this;
	if (charset) { source = this.decodeToString(charset); }

	for (var i=0;i<source.length;i++) {
		arr.push(source.codeAt(i));
	}
	return arr;
}

B.prototype.join = Binary_join;

B.prototype.split = Binary_split;

B.prototype.copy = Binary_copy;

/* BS stuff */

BS.prototype.get = BS.prototype.valueAt = BS.prototype.byteAt = Binary_get;

BS.prototype.toString = function() {
	return "[ByteString "+this.length + "]";
}

BS.prototype.toSource = function() {
	return "ByteString([" + this.toArray().join(", ") + "])";
}

BS.prototype.substring = function(index1, index2) {
	/* swap if necessary */
	var start = Math.min(index1, index2);
	var end = Math.max(index1, index2);

	/* both non-negative */
	start = Math.max(start, 0);
	end = Math.max(end, 0);

	return this.slice(start, end);
}

BS.prototype.substr = function(index, length) {
	/* non-negative start, default end */
	var start = Math.max(index, 0);
	var end = this.length;

	/* length specified */
	if (arguments.length > 1) {
		end = start + length;
		/* length nonnegative */
		end = Math.max(start, end);
	}

	return this.slice(start, end);
}

/* BA stuff */

BA.prototype.get = BA.prototype.valueAt = BA.prototype.byteAt = Binary_get;

BA.prototype.toString = function() {
	return "[ByteArray "+this.length + "]";
}

BA.prototype.toSource = function() {
	return "ByteArray([" + this.toArray().join(", ") + "])";
}

BA.prototype.extendLeft = BA.prototype.unshift;

BA.prototype.extendRight = BA.prototype.push;

/* BA JS 1.6+ Array methods */

BA.prototype.forEach = function(cb, _this) {
	var len = this.length;
	for (var i=0;i<len;i++) {
		if (i in this) { cb.call(_this, this[i], i, this); }
	}
}

BA.prototype.every = function(cb, _this) {
	var len = this.length;
	for (var i=0;i<len;i++) {
		if (i in this && !cb.call(_this, this[i], i, this)) { return false; }
	}
	return true;
}

BA.prototype.some = function(cb, _this) {
	var len = this.length;
	for (var i=0;i<len;i++) {
		if (i in this && cb.call(_this, this[i], i, this)) { return true; }
	}
	return false;
}

BA.prototype.map = function(cb, _this) {
	var len = this.length;
	var res = new Array(len);
	for (var i=0;i<len;i++) {
		if (i in this) { res[i] = cb.call(_this, this[i], i, this); }
	}
	return res;
}

BA.prototype.filter = function(cb, _this) {
	var len = this.length;
	var res = [];
		for (var i=0;i<len;i++) {
			if (i in this) {
				var val = this[i];
				if (cb.call(_this, val, i, this)) { res.push(val); }
			}
		}
	return res;
}

BA.prototype.reduce = function(fun, initial) {
	var len = this.length >>> 0;
	if (typeof fun != "function") { throw new TypeError(); }

	/* no value to return if no initial value and an empty array */
	if (len == 0 && arguments.length == 1) { throw new TypeError(); }

	var i = 0;
	if (arguments.length >= 2) {
		var rv = initial;
	} else {
		do {
			if (i in this) {
				rv = this[i++];
				break;
			}

			/* if array contains no values, no initial value to return */
			if (++i >= len) { throw new TypeError(); }
		} while (true);
	}

	for (; i < len; i++) {
		if (i in this) { rv = fun.call(null, rv, this[i], i, this); }
	}

	return rv;
}

BA.prototype.reduceRight = function(fun, initial) {
	var len = this.length >>> 0;
	if (typeof fun != "function") { throw new TypeError(); }

	/* no value to return if no initial value, empty array */
	if (len == 0 && arguments.length == 1) { throw new TypeError(); }

	var i = len - 1;
	if (arguments.length >= 2) {
		var rv = initial;
	} else {
		do {
			if (i in this) {
				rv = this[i--];
				break;
			}

			/* if array contains no values, no initial value to return */
			if (--i < 0) { throw new TypeError(); }
		} while (true);
	}

	for (; i >= 0; i--) {
		if (i in this) { rv = fun.call(null, rv, this[i], i, this); }
	}

	return rv;
};

BA.prototype.fill = function(value, start, stop) {
	var num = (value instanceof B ? value[0] : value);
	var from = start || 0;
	var to = stop || this.length;
	for (var i=from; i<to; i++) {
		this[i] = num;
	}
	return this;
}

/* String stuff */

String.prototype.toByteArray = function(charset) {
	return new BA(this, charset);
}

String.prototype.toByteString = function(charset) {
	return new BS(this, charset);
}

String.prototype.charCode = function() {
	var arr = [];
	for (var i=0;i<this.length;i++) { arr.push(this.charCodeAt(i)); }
	return arr;
}

String.prototype.codeAt = function(index) {
	return this.charCodeAt(index);
}

String.prototype.get = function(index) {
	return this[index];
}

/* Array stuff */

Array.prototype.toByteArray = function(charset) {
	var str = String.fromCharCode.apply(null, this);
	return new BA(str, charset);
}

Array.prototype.toByteString = function(charset) {
	var str = String.fromCharCode.apply(null, this);
	return new BS(str, charset);
}

Array.prototype.get = function(index) {
	return this[index];
}
<commit_msg>more binary<commit_after>var B = exports.Binary;
var BS = exports.ByteString;
var BA = exports.ByteArray;

var Binary_get = function(index) {
	return this[index];
}

var Binary_copy = function(target, start, stop, targetStart) {
	var from = start || 0;
	var to = stop || this.length;
	var targetOffset = targetStart || 0;
	for (var i=0; i<to-from; i++) {
		target[i+targetOffset] = this[i+from];
	}
}

var Binary_join = function(array, separator) {
	var arr = [];
	for (var i=0;i<array.length;i++) {
		if (i && arguments.length > 1) { arr.push(separator); }
		arr.push(array[i]);
	}
	return new this(arr);
}

var Binary_split = function(delimiter, options) {
	var o = {
		count: -1,
		includeDelimiter: true
	}
	for (var p in options) { o[p] = options[p]; }

	var result = [];
	var start = 0;
	var index = -1;
	do {
		if (result.length == o.count) { return result; }

		index = this.indexOf(delimiter, start);
		system.stdout("index: "+index+"\n");
		if (index == -1) { break; }

		var end = index;
		if (o.includeDelimiter) { end += 1; } /* delimiter.length */
		system.stdout("slice from "+start+" to "+end+"\n");
		result.push(this.slice(start, end));
		start = index + 1; /* delimiter.length */
	} while (index != -1);

	if (start < this.length) {
		result.push(this.slice(start));
	}

	return result;
}

/* Binary stuff */

B.prototype.toArray = function(charset) {
	var arr = [];
	var source = this;
	if (charset) { source = this.decodeToString(charset); }

	for (var i=0;i<source.length;i++) {
		arr.push(source.codeAt(i));
	}
	return arr;
}

B.prototype.join = Binary_join;

B.prototype.split = Binary_split;

B.prototype.copy = Binary_copy;

/* BS stuff */

BS.prototype.get = BS.prototype.valueAt = BS.prototype.byteAt = Binary_get;

BS.prototype.toString = function() {
	return "[ByteString "+this.length + "]";
}

BS.prototype.toSource = function() {
	return "ByteString([" + this.toArray().join(", ") + "])";
}

BS.prototype.substring = function(index1, index2) {
	/* swap if necessary */
	var start = Math.min(index1, index2);
	var end = Math.max(index1, index2);

	/* both non-negative */
	start = Math.max(start, 0);
	end = Math.max(end, 0);

	return this.slice(start, end);
}

BS.prototype.substr = function(index, length) {
	/* non-negative start, default end */
	var start = Math.max(index, 0);
	var end = this.length;

	/* length specified */
	if (arguments.length > 1) {
		end = start + length;
		/* length nonnegative */
		end = Math.max(start, end);
	}

	return this.slice(start, end);
}

/* BA stuff */

BA.prototype.get = BA.prototype.valueAt = BA.prototype.byteAt = Binary_get;

BA.prototype.toString = function() {
	return "[ByteArray "+this.length + "]";
}

BA.prototype.toSource = function() {
	return "ByteArray([" + this.toArray().join(", ") + "])";
}

BA.prototype.extendLeft = BA.prototype.unshift;

BA.prototype.extendRight = BA.prototype.push;

BA.prototype.fill = function(value, start, stop) {
	var num = (value instanceof B ? value[0] : value);
	var from = start || 0;
	var to = stop || this.length;
	for (var i=from; i<to; i++) {
		this[i] = num;
	}
	return this;
}

/* BA JS 1.6+ Array methods */

var list = ["forEach", "every", "some", "map", "filter", "reduce", "reduceRight"];
for (var i=0;i<list.length;i++) {
	var name = list[i];
	BA.prototype[name] = Array.prototype[name];
}

/* String stuff */

String.prototype.toByteArray = function(charset) {
	return new BA(this, charset);
}

String.prototype.toByteString = function(charset) {
	return new BS(this, charset);
}

String.prototype.charCode = function() {
	var arr = [];
	for (var i=0;i<this.length;i++) { arr.push(this.charCodeAt(i)); }
	return arr;
}

String.prototype.codeAt = function(index) {
	return this.charCodeAt(index);
}

String.prototype.get = function(index) {
	return this[index];
}

/* Array stuff */

Array.prototype.toByteArray = function(charset) {
	var str = String.fromCharCode.apply(null, this);
	return new BA(str, charset);
}

Array.prototype.toByteString = function(charset) {
	var str = String.fromCharCode.apply(null, this);
	return new BS(str, charset);
}

Array.prototype.get = function(index) {
	return this[index];
}
<|endoftext|>


<commit_before>'use strict';

const _             = require('lodash');
const EventEmitter  = require('events');
const childProcess  = require('child_process');

const moment        = require('moment');
const linuxBattery  = require('linux-battery');
const mpdCmd        = require('mpd').cmd;

const util          = require('./util');
const config        = require('./config');
const clients       = require('./clients');

// Abstract block
// Emits 'update' events
// Output should be stored in this._output
class Block extends EventEmitter {
  constructor() {
    super();
    this._output = ` ${config.get('strLoading')} `;
  }

  // Should update block output state and emit an 'update' event
  // abstract update();

  query() {
    return this._output;
  }
}

// Static block
class StaticBlock extends Block {
  constructor(text) {
    super();
    this._output = text;
  }

  update() {
    this.emit('update');
  }
}

// Abstract block which updates at intervals
class IntervalBlock extends Block {
  constructor(interval, delay) {
    super();

    // Default for delay
    delay = delay || 0;

    // Wait until the delay
    setTimeout(() => {
      // Update once more
      this.update();

      // Set an interval to update on the interval
      setInterval(this.update.bind(this), interval);
    }, delay);

    // Update once initially
    this.update();
  }
}

// Abstract block which updates via an event emitter
class EventBlock extends Block {
  constructor(emitter) {
    super();
    this.emitter = emitter;
    this._do_registration();
  }

  // Here is where all callback registration should be done
  // abstract _do_registration();

  // Registers a this.update to the emitter, then updates once
  _register(events) {
    for (let e of events)
      this.emitter.on(e, this.update.bind(this));

    // Update once initially
    this.update();
  }
}

// Abstract block which updates via an i3 ipc connection
class I3Block extends EventBlock {
  constructor() {
    super(clients.i3);
  }
}

// Abstract block which updates via an acpi connection
class AcpiBlock extends EventBlock {
  constructor() {
    super(clients.acpi);
  }
}

// Abstract block which updates via an mpd connection
class MpdBlock extends EventBlock {
  constructor() {
    super(clients.mpd);
  }
}

// Updates time once a minute, on the minute
class DatetimeBlock extends IntervalBlock {
  constructor() {
    let m = moment();
    let delay =(60 * 1000) -
      (m.millisecond() + m.second() * 1000) +
      config.get('fudgeTimeout');
    super(60 * 1000, delay);
  }

  update() {
    this._output = ` ${
      moment().locale(config.get('dtLocale')).format(config.get('dtFmt'))
    } `;
    this.emit('update');
  }
}

// Updates once an hour
class YumUpdateBlock extends IntervalBlock {
  constructor() {
    super(config.get('yumUpdateFreq'));
  }

  update() {
    childProcess.exec('yum check-update | sed \'1,/^$/d\' | wc -l', (err, out) => {
      if (err) {
        this.emit('error', err);
        return;
      }

      let updates = Number.parseInt(out.trim());
      let output = ` ${updates} `;

      // Accent if updates
      if (updates)
        output = util.addBG(output, 'cAccent');

      output = util.addAction(output,
          `${config.get('termExec')} sudo yum update`);

      this._output = output;
      this.emit('update');
    });
  }
}

class WorkspaceBlock extends I3Block {
  constructor(separator) {
    super();
    this.separator = separator || ' ';
  }

  _do_registration() {
    this._register(['workspace']);
  }

  update() {
    this.emitter.workspaces((err, data) => {
      if (err) {
        this.emit('error', err);
        return;
      }

      this._output = data.map(function(space) {
        let current = ` ${
          space.name.split(':').slice(1).join('') || space.name
        } `;

        current = util.addAction(current, `i3-msg workspace "${space.name}"`);

        if (space.focused)
          return util.addBG(current, 'cAccent');
        else if (space.urgent)
          return util.addBG(current, 'cUrgent');
        else
          return current;
      }).join(this.separator);

      this.emit('update');
    });
  }
}

class TitleBlock extends I3Block {
  _do_registration() {
    this._register(['window', 'workspace::focus']);
  }

  update() {
    this.emitter.tree((err, data) => {
      if (err) {
        this.emit('error', err);
        return;
      }

      this._output = ` ${_.trunc(util.findFocused(data).name, {
        length:     config.get('titleMaxlen'),
        omission:   config.get('titleTrunc')
      })} `;

      this.emit('update');
    });
  }
}

class BatteryBlock extends AcpiBlock {
  constructor(separator) {
    super();

    this.separator = separator || ' ';

    // Update status periodically
    setInterval(this.update.bind(this), config.get('batInterval'));
  }

  _do_registration() {
    this._register(['ac_adapter']);
  }

  update() {
    // Set a fudge timeout
    if (this.fudge)
      clearTimeout(this.fudge);
    this.fudge = setTimeout(() => {
      linuxBattery().then((batteries) => {
        let bat = batteries[0]; // Grab the first battery
        let stats = [];

        // Time until...
        let time = bat.timeToEmpty || bat.timeToFull;
        if (time)
          stats.push(time.replace(/\s*([a-z])[a-z]*/, '$1'));

        // Charging status
        let state;
        switch (bat.state) {
          case 'fully-charged':
            state = '✓';
            break;
          case 'charging':
            state = '↑';
            break;
          case 'discharging':
            state = '↓';
            break;
          default:
            // Do nothing
        }
        if (state)
          stats.push(state);

        // Percent
        let percent = Number.parseInt(bat.percentage.replace(/[^\d]/, ''));
        stats.push(percent);

        let output = ` ${stats.join(this.separator)} `;

        // Critical
        if (percent <= config.get('batCritical'))
          output = util.addBG(output, 'cUrgent');

        this._output = output;
        this.emit('update');
      });
    }, config.get('fudgeTimeout'));
  }
}

class VolumeBlock extends AcpiBlock {
  _do_registration() {
    this._register([
        'button/volumeup',
        'button/volumedown',
        'button/mute',
        'jack/headphone',
    ]);
  }

  update() {
    // Set a fudge timeout
    if (this.fudge)
      clearTimeout(this.fudge);
    this.fudge = setTimeout(() => {
      childProcess.exec('amixer get Master', (err, out) => {
        if (err) {
          this.emit('error', err);
          return;
        }

        let matches = out.match(/\[(\d{1,3})%\] \[(on|off)\]\n/);
        let output = ` ${matches[1]} `;
        if (matches[2] === 'off')
          output = util.addBG(output, 'cUrgent');

        // Commands
        // Toggle mute
        output = util.addAction(output, 'amixer set Master toggle', 1, true);
        // Volume up
        output = util.addAction(output,
            `amixer set Master ${config.get('volPercent')}%+`, 4, true);
        // Volume down
        output = util.addAction(output,
            `amixer set Master ${config.get('volPercent')}%-`, 5, true);

        this._output = output;
        this.emit('update');
      });
    }, config.get('fudgeTimeout'));
  }
}

class SsidBlock extends EventBlock {
  constructor() {
    super(clients.iwevent);
  }

  _do_registration() {
    this._register(['New Access Point']);
  }

  // TODO: detect if airplane mode is on or not
  update() {
    // Set a fudge timout
    if (this.fudge)
      clearTimeout(this.fudge);
    this.fudge = setTimeout(() => {
      childProcess.exec('iwgetid', (err, out) => {
        // err means no ssid
        let output = ` ${
          err ? 'disconnected' : out.match(/ESSID:"(.*)"\n/)[1]
        } `;

        // Disconnected -> critical
        if (err)
          output = util.addBG(output, 'cUrgent');

        output = util.addAction(output, `${config.get('termExec')} nmtui`);

        this._output = output;
        this.emit('update');
      });
    }, config.get('netFudgeTimeout'));
  }
}

class CurrentSongBlock extends MpdBlock {
  _do_registration() {
    this.emitter.on('ready', () => {
      this._register(['system-player', 'system-playlist']);
    });
  }

  update() {
    this.emitter.sendCommand(mpdCmd('currentsong', []), (err, data) => {
      if (err) {
        this.emit('error', err);
        return;
      }

      if (data) {
        let stats = [];

        let matches  = [
          data.match(/\nTitle: (.*)\n/),
          data.match(/\nArtist: (.*)\n/)
        ];

        matches.forEach((match) => {
          if (match)
            stats.push(match[1]);
        });

        this._output = ` ${stats.join(' - ')} `;
      } else {
        // Nothing playing
        this._output = ' no tunes ';
      }

      this.emit('update');
    });
  }
}

class PlayStatusBlock extends MpdBlock {
  _do_registration() {
    this.emitter.on('ready', () => {
      this._register(['system-player', 'system-playlist', 'system-options']);
    });
  }

  update() {
    if (this.fudge)
      clearTimeout(this.fudge);
    this.fudge = setTimeout(() => {
      this.emitter.sendCommand(mpdCmd('status', []), (err, data) => {
        if (err) {
          this.emit('error', err);
          return;
        }

        if (data) {
          let stats = '';

          let binaries  = [
            {
              label: 'r',
              action: 'mpc repeat',
              match: data.match(/\nrepeat: ([01])\n/)
            },
            {
              label: 'z',
              action: 'mpc random',
              match: data.match(/\nrandom: ([01])\n/)
            }
          ];

          binaries.forEach((status) => {
            stats += util.addAction(
                (status.match && status.match[1] !== '0') ? status.label : '-',
                status.action);
          });

          let xfadeMatch = data.match(/\nxfade: (\d+)\n/);
          this.lastXfade = xfadeMatch ? Number.parseInt(xfadeMatch[1]) : 0;
          stats += util.addAction(xfadeMatch ? 'x' : '-',
              `mpc crossfade ${this.lastXfade ? 0 : config.get('mpdXfade')}`);

          let playing = '♪';
          let playingMatch = data.match(/\nstate: (.*)\n/);
          if (playingMatch) {
            switch (playingMatch[1]) {
              case 'play':
                playing = '▶';
                break;
              case 'pause':
                playing = '‖';
                break;
              case 'stop':
                playing = '■';
                break;
              default:
                // Do nothing
            }
          }

          playing = util.addAction(playing, 'mpc toggle');

          this._output = ` ${stats} ${playing} `;
        } else {
          // Nothing playing
          this._output = ' ♪ ';
        }

        this.emit('update');
      });
    }, config.get('mpdFudgeTimeout'));
  }
}

module.exports = {
  Block:              Block,
  StaticBlock:        StaticBlock,
  IntervalBlock:      IntervalBlock,
  EventBlock:         EventBlock,
  I3Block:            I3Block,
  AcpiBlock:          AcpiBlock,
  DatetimeBlock:      DatetimeBlock,
  YumUpdateBlock:     YumUpdateBlock,
  WorkspaceBlock:     WorkspaceBlock,
  TitleBlock:         TitleBlock,
  BatteryBlock:       BatteryBlock,
  VolumeBlock:        VolumeBlock,
  SsidBlock:          SsidBlock,
  CurrentSongBlock:   CurrentSongBlock,
  PlayStatusBlock:    PlayStatusBlock,
};

<commit_msg>remove workspace separator<commit_after>'use strict';

const _             = require('lodash');
const EventEmitter  = require('events');
const childProcess  = require('child_process');

const moment        = require('moment');
const linuxBattery  = require('linux-battery');
const mpdCmd        = require('mpd').cmd;

const util          = require('./util');
const config        = require('./config');
const clients       = require('./clients');

// Abstract block
// Emits 'update' events
// Output should be stored in this._output
class Block extends EventEmitter {
  constructor() {
    super();
    this._output = ` ${config.get('strLoading')} `;
  }

  // Should update block output state and emit an 'update' event
  // abstract update();

  query() {
    return this._output;
  }
}

// Static block
class StaticBlock extends Block {
  constructor(text) {
    super();
    this._output = text;
  }

  update() {
    this.emit('update');
  }
}

// Abstract block which updates at intervals
class IntervalBlock extends Block {
  constructor(interval, delay) {
    super();

    // Default for delay
    delay = delay || 0;

    // Wait until the delay
    setTimeout(() => {
      // Update once more
      this.update();

      // Set an interval to update on the interval
      setInterval(this.update.bind(this), interval);
    }, delay);

    // Update once initially
    this.update();
  }
}

// Abstract block which updates via an event emitter
class EventBlock extends Block {
  constructor(emitter) {
    super();
    this.emitter = emitter;
    this._do_registration();
  }

  // Here is where all callback registration should be done
  // abstract _do_registration();

  // Registers a this.update to the emitter, then updates once
  _register(events) {
    for (let e of events)
      this.emitter.on(e, this.update.bind(this));

    // Update once initially
    this.update();
  }
}

// Abstract block which updates via an i3 ipc connection
class I3Block extends EventBlock {
  constructor() {
    super(clients.i3);
  }
}

// Abstract block which updates via an acpi connection
class AcpiBlock extends EventBlock {
  constructor() {
    super(clients.acpi);
  }
}

// Abstract block which updates via an mpd connection
class MpdBlock extends EventBlock {
  constructor() {
    super(clients.mpd);
  }
}

// Updates time once a minute, on the minute
class DatetimeBlock extends IntervalBlock {
  constructor() {
    let m = moment();
    let delay =(60 * 1000) -
      (m.millisecond() + m.second() * 1000) +
      config.get('fudgeTimeout');
    super(60 * 1000, delay);
  }

  update() {
    this._output = ` ${
      moment().locale(config.get('dtLocale')).format(config.get('dtFmt'))
    } `;
    this.emit('update');
  }
}

// Updates once an hour
class YumUpdateBlock extends IntervalBlock {
  constructor() {
    super(config.get('yumUpdateFreq'));
  }

  update() {
    childProcess.exec('yum check-update | sed \'1,/^$/d\' | wc -l', (err, out) => {
      if (err) {
        this.emit('error', err);
        return;
      }

      let updates = Number.parseInt(out.trim());
      let output = ` ${updates} `;

      // Accent if updates
      if (updates)
        output = util.addBG(output, 'cAccent');

      output = util.addAction(output,
          `${config.get('termExec')} sudo yum update`);

      this._output = output;
      this.emit('update');
    });
  }
}

class WorkspaceBlock extends I3Block {
  _do_registration() {
    this._register(['workspace']);
  }

  update() {
    this.emitter.workspaces((err, data) => {
      if (err) {
        this.emit('error', err);
        return;
      }

      this._output = data.map(function(space) {
        let current = ` ${
          space.name.split(':').slice(1).join('') || space.name
        } `;

        current = util.addAction(current, `i3-msg workspace "${space.name}"`);

        if (space.focused)
          return util.addBG(current, 'cAccent');
        else if (space.urgent)
          return util.addBG(current, 'cUrgent');
        else
          return current;
      }).join('');

      this.emit('update');
    });
  }
}

class TitleBlock extends I3Block {
  _do_registration() {
    this._register(['window', 'workspace::focus']);
  }

  update() {
    this.emitter.tree((err, data) => {
      if (err) {
        this.emit('error', err);
        return;
      }

      this._output = ` ${_.trunc(util.findFocused(data).name, {
        length:     config.get('titleMaxlen'),
        omission:   config.get('titleTrunc')
      })} `;

      this.emit('update');
    });
  }
}

class BatteryBlock extends AcpiBlock {
  constructor(separator) {
    super();

    this.separator = separator || ' ';

    // Update status periodically
    setInterval(this.update.bind(this), config.get('batInterval'));
  }

  _do_registration() {
    this._register(['ac_adapter']);
  }

  update() {
    // Set a fudge timeout
    if (this.fudge)
      clearTimeout(this.fudge);
    this.fudge = setTimeout(() => {
      linuxBattery().then((batteries) => {
        let bat = batteries[0]; // Grab the first battery
        let stats = [];

        // Time until...
        let time = bat.timeToEmpty || bat.timeToFull;
        if (time)
          stats.push(time.replace(/\s*([a-z])[a-z]*/, '$1'));

        // Charging status
        let state;
        switch (bat.state) {
          case 'fully-charged':
            state = '✓';
            break;
          case 'charging':
            state = '↑';
            break;
          case 'discharging':
            state = '↓';
            break;
          default:
            // Do nothing
        }
        if (state)
          stats.push(state);

        // Percent
        let percent = Number.parseInt(bat.percentage.replace(/[^\d]/, ''));
        stats.push(percent);

        let output = ` ${stats.join(this.separator)} `;

        // Critical
        if (percent <= config.get('batCritical'))
          output = util.addBG(output, 'cUrgent');

        this._output = output;
        this.emit('update');
      });
    }, config.get('fudgeTimeout'));
  }
}

class VolumeBlock extends AcpiBlock {
  _do_registration() {
    this._register([
        'button/volumeup',
        'button/volumedown',
        'button/mute',
        'jack/headphone',
    ]);
  }

  update() {
    // Set a fudge timeout
    if (this.fudge)
      clearTimeout(this.fudge);
    this.fudge = setTimeout(() => {
      childProcess.exec('amixer get Master', (err, out) => {
        if (err) {
          this.emit('error', err);
          return;
        }

        let matches = out.match(/\[(\d{1,3})%\] \[(on|off)\]\n/);
        let output = ` ${matches[1]} `;
        if (matches[2] === 'off')
          output = util.addBG(output, 'cUrgent');

        // Commands
        // Toggle mute
        output = util.addAction(output, 'amixer set Master toggle', 1, true);
        // Volume up
        output = util.addAction(output,
            `amixer set Master ${config.get('volPercent')}%+`, 4, true);
        // Volume down
        output = util.addAction(output,
            `amixer set Master ${config.get('volPercent')}%-`, 5, true);

        this._output = output;
        this.emit('update');
      });
    }, config.get('fudgeTimeout'));
  }
}

class SsidBlock extends EventBlock {
  constructor() {
    super(clients.iwevent);
  }

  _do_registration() {
    this._register(['New Access Point']);
  }

  // TODO: detect if airplane mode is on or not
  update() {
    // Set a fudge timout
    if (this.fudge)
      clearTimeout(this.fudge);
    this.fudge = setTimeout(() => {
      childProcess.exec('iwgetid', (err, out) => {
        // err means no ssid
        let output = ` ${
          err ? 'disconnected' : out.match(/ESSID:"(.*)"\n/)[1]
        } `;

        // Disconnected -> critical
        if (err)
          output = util.addBG(output, 'cUrgent');

        output = util.addAction(output, `${config.get('termExec')} nmtui`);

        this._output = output;
        this.emit('update');
      });
    }, config.get('netFudgeTimeout'));
  }
}

class CurrentSongBlock extends MpdBlock {
  _do_registration() {
    this.emitter.on('ready', () => {
      this._register(['system-player', 'system-playlist']);
    });
  }

  update() {
    this.emitter.sendCommand(mpdCmd('currentsong', []), (err, data) => {
      if (err) {
        this.emit('error', err);
        return;
      }

      if (data) {
        let stats = [];

        let matches  = [
          data.match(/\nTitle: (.*)\n/),
          data.match(/\nArtist: (.*)\n/)
        ];

        matches.forEach((match) => {
          if (match)
            stats.push(match[1]);
        });

        this._output = ` ${stats.join(' - ')} `;
      } else {
        // Nothing playing
        this._output = ' no tunes ';
      }

      this.emit('update');
    });
  }
}

class PlayStatusBlock extends MpdBlock {
  _do_registration() {
    this.emitter.on('ready', () => {
      this._register(['system-player', 'system-playlist', 'system-options']);
    });
  }

  update() {
    if (this.fudge)
      clearTimeout(this.fudge);
    this.fudge = setTimeout(() => {
      this.emitter.sendCommand(mpdCmd('status', []), (err, data) => {
        if (err) {
          this.emit('error', err);
          return;
        }

        if (data) {
          let stats = '';

          let binaries  = [
            {
              label: 'r',
              action: 'mpc repeat',
              match: data.match(/\nrepeat: ([01])\n/)
            },
            {
              label: 'z',
              action: 'mpc random',
              match: data.match(/\nrandom: ([01])\n/)
            }
          ];

          binaries.forEach((status) => {
            stats += util.addAction(
                (status.match && status.match[1] !== '0') ? status.label : '-',
                status.action);
          });

          let xfadeMatch = data.match(/\nxfade: (\d+)\n/);
          this.lastXfade = xfadeMatch ? Number.parseInt(xfadeMatch[1]) : 0;
          stats += util.addAction(xfadeMatch ? 'x' : '-',
              `mpc crossfade ${this.lastXfade ? 0 : config.get('mpdXfade')}`);

          let playing = '♪';
          let playingMatch = data.match(/\nstate: (.*)\n/);
          if (playingMatch) {
            switch (playingMatch[1]) {
              case 'play':
                playing = '▶';
                break;
              case 'pause':
                playing = '‖';
                break;
              case 'stop':
                playing = '■';
                break;
              default:
                // Do nothing
            }
          }

          playing = util.addAction(playing, 'mpc toggle');

          this._output = ` ${stats} ${playing} `;
        } else {
          // Nothing playing
          this._output = ' ♪ ';
        }

        this.emit('update');
      });
    }, config.get('mpdFudgeTimeout'));
  }
}

module.exports = {
  Block:              Block,
  StaticBlock:        StaticBlock,
  IntervalBlock:      IntervalBlock,
  EventBlock:         EventBlock,
  I3Block:            I3Block,
  AcpiBlock:          AcpiBlock,
  DatetimeBlock:      DatetimeBlock,
  YumUpdateBlock:     YumUpdateBlock,
  WorkspaceBlock:     WorkspaceBlock,
  TitleBlock:         TitleBlock,
  BatteryBlock:       BatteryBlock,
  VolumeBlock:        VolumeBlock,
  SsidBlock:          SsidBlock,
  CurrentSongBlock:   CurrentSongBlock,
  PlayStatusBlock:    PlayStatusBlock,
};

<|endoftext|>