<commit_before>Seed({
		name : "Mold.Lib.VDom.ProtoNode",
		dna : "data",
		include : [
			"Mold.Lib.VDom.VDoc",
			"Mold.Lib.Filter"
		]
	},
	function(){

		var STATE_RENDER = "render";
		var STATE_NEW = "new";
		var STATE_REMOVE = "remove";
		var STATE_NO_CHANGES = "no changes";
		var STATE_UPDATE = "update";

		var VALUE_NODE = 1;
		var BLOCK_NODE = 2;
		var NEGATIVE_BLOCK_NODE = 3;
		var DOM_NODE = 5;
		var TEXT_NODE = 6;
		var ATTRIBUTE_NODE = 6;
		var STRING_NODE = 8;
		var ROOT_NODE = 9;

		var _doc = Mold.Lib.VDom.VDoc;

		if(!config.name){
			throw new Error("Name must be specified!")
		}

		var _vDom = config.vdom || [];
		var _that = this;
		var _oldData = false;

		this.children = this.children || {};
		this.data = config.data || false;
		this.name = config.name;
		this.type = STRING_NODE;
		this.parent = false;
		this.state = STATE_NEW;
		this.isString = config.isString || false;
		this.hasBinding = false;
		this._id = Mold.getId();
		this.services = config.services || false;
		this.template = config.template || false;

		this.vdom = _vDom;
		this.protoChild = {};

		this.renderDom = false;

		this.afterAddChild = function(child){

		}

		this.addNode = function(child, addAttribute){

			if(!addAttribute){
				_vDom.push(child);
			}

			child.parent = _that;
			child.services = this.services;

			if(
				child.type === BLOCK_NODE
				|| child.type === VALUE_NODE
				|| child.type === NEGATIVE_BLOCK_NODE
			){

				//if current node is not a block-node add child to next block-node
				if(this.type !== BLOCK_NODE){

					if(this.children[child.name]){
						//if there are two nodes with the same name create an array
						this.children[child.name] = [this.children[child.name]]
						this.children[child.name].push(child);
					}else{
						this.children[child.name] = child;
					}
					var parentBlock = this.parent;
					if(parentBlock){
						while(parentBlock.type !== BLOCK_NODE && parentBlock.parent){
							parentBlock = parentBlock.parent;
						}

						if(parentBlock){
							if(parentBlock.type === BLOCK_NODE){
								var index = (parentBlock.children.length) ? parentBlock.children.length -1 : 0;
								if(!parentBlock.children[index]){
									parentBlock.children[index] = {};
								}
								parentBlock.children[index][child.name] = this.children[child.name];
							}else{
								parentBlock.children[child.name] = this.children[child.name];
							}
						}
					}
				}else{
					var index = (this.children.length) ? this.children.length  -1 : 0;
					if(!this.children[index]){
						this.children[index] = {};
					}
					this.children[index][child.name] = child;
				}
			}

			this.afterAddChild(child);

		}

		this.addAttribute = function(child){
			this.attributes[child.name] = child;
			this.addNode(child, true);
		}

		this.get = function(){
			return _domPointer;
		}

		this.onSetData = function(data){

				//set children to false if no data is given
			for(var name in this.children){
				if(Mold.isArray(this.children[name])){
					var len = this.children[name].length, i = 0;
					for(; i < len; i++){
						var selected = this.children[name][i];
						if(selected.type === BLOCK_NODE){
							//if type is a blocknode add parent data
							if(data){
								selected.setData(data);
							}else{
								selected.setData(false);
							}
						}else{
							if(selected.hasParentValue && data[selected.parentName] &&  data[selected.parentName][selected.childName]){
								selected.setData(data[selected.parentName][selected.childName]);
							}else if(data[name]){
								selected.setData(data[name]);
							}else{
								selected.setData("");
							}

						}
					}
				}else{

					if(this.children[name].type === BLOCK_NODE){
						//if type is a blocknode add parent data
						if(data){
							this.children[name].setData(data);
						}else{
							this.children[name].setData(false);
						}
					}else{
						var selected = this.children[name];
						if(selected.hasParentValue && data[selected.parentName] &&  data[selected.parentName][selected.childName]){
							selected.setData(data[selected.parentName][selected.childName]);
						}else if(data[name]){
							this.children[name].setData(data[name]);
						}else{
							this.children[name].setData("");
						}
					}

				}
			}
		}

		this.renderParentDom = function(){
			var parent = this.parent;
			var isDom = false;
			while(parent){
				isDom = (parent.type === 5) ? true : false;
				if(isDom) {
					break;
				}
				parent = parent.parent;
			}
			parent.render()
		}

		this.setNodeData = function(nodeName, data, bind){
			if(this.children[nodeName]){
				if(Mold.isArray(this.children[nodeName])){
					var i = 0, len = this.children[nodeName].length;
					for(; i < len; i++){
						this.children[nodeName][i].setData(data, bind)
					}
				}else{
					this.children[nodeName].setData(data, bind)
				}
			}
		}

		this.setData = function(data, bind){

			if(typeof this.data === "string" && this.data === data){
				this.state = STATE_NO_CHANGES;
			}else if(this.state === STATE_NO_CHANGES){
				this.state = STATE_UPDATE;
			}

			this.data = data;
			this.onSetData(data, bind);

		}

		this.renderString = function(){
			throw new Error("renderString ist not implemented!")
		}

	}
)<commit_msg>bind removed<commit_after>Seed({
		name : "Mold.Lib.VDom.ProtoNode",
		dna : "data",
		include : [
			"Mold.Lib.VDom.VDoc",
			"Mold.Lib.Filter"
		]
	},
	function(){

		var STATE_RENDER = "render";
		var STATE_NEW = "new";
		var STATE_REMOVE = "remove";
		var STATE_NO_CHANGES = "no changes";
		var STATE_UPDATE = "update";

		var VALUE_NODE = 1;
		var BLOCK_NODE = 2;
		var NEGATIVE_BLOCK_NODE = 3;
		var DOM_NODE = 5;
		var TEXT_NODE = 6;
		var ATTRIBUTE_NODE = 6;
		var STRING_NODE = 8;
		var ROOT_NODE = 9;

		var _doc = Mold.Lib.VDom.VDoc;

		if(!config.name){
			throw new Error("Name must be specified!")
		}

		var _vDom = config.vdom || [];
		var _that = this;
		var _oldData = false;

		this.children = this.children || {};
		this.data = config.data || false;
		this.name = config.name;
		this.type = STRING_NODE;
		this.parent = false;
		this.state = STATE_NEW;
		this.isString = config.isString || false;
		this.hasBinding = false;
		this._id = Mold.getId();
		this.services = config.services || false;
		this.template = config.template || false;

		this.vdom = _vDom;
		this.protoChild = {};

		this.renderDom = false;

		this.afterAddChild = function(child){

		}

		this.addNode = function(child, addAttribute){

			if(!addAttribute){
				_vDom.push(child);
			}

			child.parent = _that;
			child.services = this.services;

			if(
				child.type === BLOCK_NODE
				|| child.type === VALUE_NODE
				|| child.type === NEGATIVE_BLOCK_NODE
			){

				//if current node is not a block-node add child to next block-node
				if(this.type !== BLOCK_NODE){
					if(this.children[child.name]){
						//if there are two nodes with the same name create an array
						this.children[child.name] = [this.children[child.name]]
						this.children[child.name].push(child);
					}else{
						this.children[child.name] = child;
					}
					var parentBlock = this.parent;
					if(parentBlock){
						while(parentBlock.type !== BLOCK_NODE && parentBlock.parent){
							parentBlock = parentBlock.parent;
						}

						if(parentBlock){
							if(parentBlock.type === BLOCK_NODE){
								var index = (parentBlock.children.length) ? parentBlock.children.length -1 : 0;
								if(!parentBlock.children[index]){
									parentBlock.children[index] = {};
								}
								parentBlock.children[index][child.name] = this.children[child.name];
							}else{
								parentBlock.children[child.name] = this.children[child.name];
							}
						}
					}
				}else{
					var index = (this.children.length) ? this.children.length  -1 : 0;
					if(!this.children[index]){
						this.children[index] = {};
					}
					this.children[index][child.name] = child;
				}
			}

			this.afterAddChild(child);

		}

		this.addAttribute = function(child){
			this.attributes[child.name] = child;
			this.addNode(child, true);
		}

		this.get = function(){
			return _domPointer;
		}

		this.onSetData = function(data){

				//set children to false if no data is given
			for(var name in this.children){
				if(Mold.isArray(this.children[name])){
					var len = this.children[name].length, i = 0;
					for(; i < len; i++){
						var selected = this.children[name][i];

						if(selected.type === BLOCK_NODE){
							//if type is a blocknode add parent data
							if(data){
								selected.setData(data);
							}else{
								selected.setData(false);
							}
						}else{
							if(selected.hasParentValue && data[selected.parentName] &&  data[selected.parentName][selected.childName]){
								selected.setData(data[selected.parentName][selected.childName]);
							}else if(data[name]){
								selected.setData(data[name]);
							}else{
								selected.setData("");
							}

						}
					}
				}else{

					if(this.children[name].type === BLOCK_NODE){
						//if type is a blocknode add parent data
						if(data){
							this.children[name].setData(data);
						}else{
							this.children[name].setData(false);
						}
					}else{
						var selected = this.children[name];
						if(selected.hasParentValue && data[selected.parentName] &&  data[selected.parentName][selected.childName]){
							selected.setData(data[selected.parentName][selected.childName]);
						}else if(data[name]){
							this.children[name].setData(data[name]);
						}else{
							this.children[name].setData("");
						}
					}

				}
			}
		}

		this.renderParentDom = function(){
			var parent = this.parent;
			var isDom = false;
			while(parent){
				isDom = (parent.type === 5) ? true : false;
				if(isDom) {
					break;
				}
				parent = parent.parent;
			}
			parent.render()
		}

		this.setNodeData = function(nodeName, data, bind){
			if(this.children[nodeName]){
				if(Mold.isArray(this.children[nodeName])){
					var i = 0, len = this.children[nodeName].length;
					for(; i < len; i++){
						this.children[nodeName][i].setData(data, bind)
					}
				}else{
					this.children[nodeName].setData(data, bind)
				}
			}
		}

		this.setData = function(data){

			if(typeof this.data === "string" && this.data === data){
				this.state = STATE_NO_CHANGES;
			}else if(this.state === STATE_NO_CHANGES){
				this.state = STATE_UPDATE;
			}

			this.data = data;
			this.onSetData(data);

		}

		this.renderString = function(){
			throw new Error("renderString ist not implemented!")
		}

	}
)<|endoftext|>


<commit_before>/** @license
Copyright 2010 WebDriver committers
Copyright 2010 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @fileoverview Atoms-based implementation of the webelement interface
 */

goog.provide('webdriver.element');

goog.require('bot.dom');
goog.require('goog.dom.TagName');
goog.require('goog.math');
goog.require('goog.style');

/**
 * List of input types that support the "selected" or "checked" property.
 * @type {!Array.<string>}
 * @const
 * @private
 */
webdriver.element.SELECTABLE_TYPES_ = [
  'checkbox',
  'radio'
];

/**
 * @param {!Element} element The element to check.
 * @return {boolean} Whether the element could be checked or selected.
 * @private
 */
webdriver.element.isSelectable_ = function(element) {
  var tagName = element.tagName.toUpperCase();

  if (tagName == goog.dom.TagName.OPTION) {
    return true;
  }

  if (tagName == goog.dom.TagName.INPUT) {
    var type = element.type;

    if (goog.array.contains(webdriver.element.SELECTABLE_TYPES_, type)) {
      return true;
    }
  }

  return false;
};

/**
 * @param {!Element} element The element to use.
 * @return {boolean} Whether the element is checked or selected.
 */
webdriver.element.isSelected = function(element) {
  if (!webdriver.element.isSelectable_(element)) {
    return false;
  }

  var propertyName = 'selected';
  var tagName = element.tagName.toUpperCase();
  var type = element.type && element.type.toLowerCase();

  if ('checkbox' == type || 'radio' == type) {
    propertyName = 'checked';
  }

  return !!element[propertyName];
};

/**
 * Get the value of the given property or attribute. If the "attribute" is for
 * a boolean property, we return null in the case where the value is false. If
 * the attribute name is "style" an attempt to convert that style into a string
 * is done.
 *
 * @param {!Element} element The element to use.
 * @param {!string} attribute The name of the attribute to look up.
 * @return {string} The string value of the attribute or property, or null.
 */
webdriver.element.getAttribute = function(element, attribute) {
  var value = null;
  var name = attribute.toLowerCase();

  if ('style' == attribute.toLowerCase()) {
    value = element.style;

    if (value && !goog.isString(value)) {
      value = value.cssText;
    }

    return value;
  }

  if ('selected' == name || 'checked' == name && webdriver.element.isSelectable_(element)) {
    return webdriver.element.isSelected(element) ? "true" : null;
  }

  // Our tests suggest that returning the attribute is desirable for links,
  // but we normally attempt to get the property value before the attribute.
  var isLink =  element.tagName && goog.dom.TagName.A == element.tagName.toUpperCase()

  if ((name == 'href' && isLink)) {
    value = bot.dom.getAttribute(element, attribute);
  } else {
    try {
      value = bot.dom.getProperty(element, attribute);
    } catch (e) {
      // Call getAttribute if getProperty fails. This happens for event
      // handlers in Firefox. For example, calling getProperty
      // for 'onclick' would fail while getAttribute for 'onclick'
      // will succeed and return the JS code of the handler.
      value = bot.dom.getAttribute(element, attribute);
    }

  }

  if (!goog.isDefAndNotNull(value)) {
    value = bot.dom.getAttribute(element, attribute);
  }

  // The empty string is a valid return value.
  return goog.isDefAndNotNull(value) ? value.toString() : null;
};

/**
 * Get the location of the element, if it's displayed.
 *
 * @param {!Element} element The element to get the location for.
 * @return {goog.math.Rect} The bounding rectangle of the element.
 */
webdriver.element.getLocation = function(element) {
  if (!bot.dom.isShown(element)) {
    return null;
  }
  return goog.style.getBounds(element);
};

/**
 * @param {Element} element The element to use.
 * @return {boolean} Whether the element is in the HEAD tag.
 */
webdriver.element.isInHead_ = function(element) {
  while (element) {
    if (element.tagName && element.tagName.toLowerCase() == 'head') {
      return true;
    }
    try {
      element = element.parentNode;
    } catch (e) {
      // Fine. the DOM has dispeared from underneath us
      return false;
    }
  }

  return false;
};

/**
 * @param {Element} element The element to get the text from.
 * @return {string} The visible text or an empty string.
 */
webdriver.element.getText = function(element) {
  if (webdriver.element.isInHead_(element)) {
    return '';
  }

  return bot.dom.getVisibleText(element);
};<commit_msg>DouniaBerrada: Fix for issue 966<commit_after>/** @license
Copyright 2010 WebDriver committers
Copyright 2010 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/**
 * @fileoverview Atoms-based implementation of the webelement interface
 */

goog.provide('webdriver.element');

goog.require('bot.dom');
goog.require('goog.dom.TagName');
goog.require('goog.math');
goog.require('goog.style');

/**
 * List of input types that support the "selected" or "checked" property.
 * @type {!Array.<string>}
 * @const
 * @private
 */
webdriver.element.SELECTABLE_TYPES_ = [
  'checkbox',
  'radio'
];

/**
 * @param {!Element} element The element to check.
 * @return {boolean} Whether the element could be checked or selected.
 * @private
 */
webdriver.element.isSelectable_ = function(element) {
  var tagName = element.tagName.toUpperCase();

  if (tagName == goog.dom.TagName.OPTION) {
    return true;
  }

  if (tagName == goog.dom.TagName.INPUT) {
    var type = element.type;

    if (goog.array.contains(webdriver.element.SELECTABLE_TYPES_, type)) {
      return true;
    }
  }

  return false;
};

/**
 * @param {!Element} element The element to use.
 * @return {boolean} Whether the element is checked or selected.
 */
webdriver.element.isSelected = function(element) {
  if (!webdriver.element.isSelectable_(element)) {
    return false;
  }

  var propertyName = 'selected';
  var tagName = element.tagName.toUpperCase();
  var type = element.type && element.type.toLowerCase();

  if ('checkbox' == type || 'radio' == type) {
    propertyName = 'checked';
  }

  return !!element[propertyName];
};

/**
 * Get the value of the given property or attribute. If the "attribute" is for
 * a boolean property, we return null in the case where the value is false. If
 * the attribute name is "style" an attempt to convert that style into a string
 * is done.
 *
 * @param {!Element} element The element to use.
 * @param {!string} attribute The name of the attribute to look up.
 * @return {string} The string value of the attribute or property, or null.
 */
webdriver.element.getAttribute = function(element, attribute) {
  var value = null;
  var name = attribute.toLowerCase();

  if ('style' == attribute.toLowerCase()) {
    value = element.style;

    if (value && !goog.isString(value)) {
      value = value.cssText;
    }

    return value;
  }

  if ('selected' == name || 'checked' == name && webdriver.element.isSelectable_(element)) {
    return webdriver.element.isSelected(element) ? "true" : null;
  }

  // Our tests suggest that returning the attribute is desirable for links,
  // but we normally attempt to get the property value before the attribute.
  var isLink =  element.tagName && goog.dom.TagName.A == element.tagName.toUpperCase()

  // When property returns an object we fall back to the actual attribute
  // instead.
  // See issue http://code.google.com/p/selenium/issues/detail?id=966
  if ((name == 'href' && isLink)
      || goog.isObject(bot.dom.getProperty(element, attribute))) {
    value = bot.dom.getAttribute(element, attribute);
  } else {
    try {
      value = bot.dom.getProperty(element, attribute);
    } catch (e) {
      // Call getAttribute if getProperty fails. This happens for event
      // handlers in Firefox. For example, calling getProperty
      // for 'onclick' would fail while getAttribute for 'onclick'
      // will succeed and return the JS code of the handler.
      value = bot.dom.getAttribute(element, attribute);
    }

  }

  if (!goog.isDefAndNotNull(value)) {
    value = bot.dom.getAttribute(element, attribute);
  }

  // The empty string is a valid return value.
  return goog.isDefAndNotNull(value) ? value.toString() : null;
};

/**
 * Get the location of the element, if it's displayed.
 *
 * @param {!Element} element The element to get the location for.
 * @return {goog.math.Rect} The bounding rectangle of the element.
 */
webdriver.element.getLocation = function(element) {
  if (!bot.dom.isShown(element)) {
    return null;
  }
  return goog.style.getBounds(element);
};

/**
 * @param {Element} element The element to use.
 * @return {boolean} Whether the element is in the HEAD tag.
 */
webdriver.element.isInHead_ = function(element) {
  while (element) {
    if (element.tagName && element.tagName.toLowerCase() == 'head') {
      return true;
    }
    try {
      element = element.parentNode;
    } catch (e) {
      // Fine. the DOM has dispeared from underneath us
      return false;
    }
  }

  return false;
};

/**
 * @param {Element} element The element to get the text from.
 * @return {string} The visible text or an empty string.
 */
webdriver.element.getText = function(element) {
  if (webdriver.element.isInHead_(element)) {
    return '';
  }

  return bot.dom.getVisibleText(element);
};
<|endoftext|>


<commit_before>frappe.ui.form.Control = class BaseControl {
	constructor(opts) {
		$.extend(this, opts);
		this.make();

		// if developer_mode=1, show fieldname as tooltip
		if(frappe.boot.user && frappe.boot.developer_mode===1 && this.$wrapper) {
			this.$wrapper.attr("title", __(this.df.fieldname));
		}

		if(this.render_input) {
			this.refresh();
		}
	}
	make() {
		this.make_wrapper();
		this.$wrapper
			.attr("data-fieldtype", this.df.fieldtype)
			.attr("data-fieldname", this.df.fieldname);
		this.wrapper = this.$wrapper.get(0);
		this.wrapper.fieldobj = this; // reference for event handlers
	}

	make_wrapper() {
		this.$wrapper = $("

").appendTo(this.parent);

		// alias
		this.wrapper = this.$wrapper;
	}

	toggle(show) {
		this.df.hidden = show ? 0 : 1;
		this.refresh();
	}

	// returns "Read", "Write" or "None"
	// as strings based on permissions
	get_status(explain) {
		if (this.df.get_status) {
			return this.df.get_status(this);
		}
		if (this.df.is_virtual) {
			return "Read";
		}

		if ((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form' || this.df.is_web_form) {
			let status = "Write";

			// like in case of a dialog box
			if (cint(this.df.hidden)) {
				// eslint-disable-next-line
				if (explain) console.log("By Hidden: None"); // eslint-disable-line no-console
				return "None";

			} else if (cint(this.df.hidden_due_to_dependency)) {
				// eslint-disable-next-line
				if(explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console
				return "None";

			} else if (cint(this.df.read_only || this.df.is_virtual || this.df.fieldtype === "Read Only")) {
				// eslint-disable-next-line
				if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console
				status = "Read";

			} else if ((this.grid &&
						this.grid.display_status == 'Read') ||
						(this.layout &&
						this.layout.grid &&
						this.layout.grid.display_status == 'Read')) {
				// parent grid is read
				if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
				status = "Read";
			}

			let value = this.value || this.get_model_value();
			value = this.get_parsed_value(value);

			if (
				status === "Read" &&
				is_null(value) &&
				!in_list(["HTML", "Image", "Button"], this.df.fieldtype)
			) status = "None";

			return status;
		}

		var status = frappe.perm.get_field_display_status(this.df,
			frappe.model.get_doc(this.doctype, this.docname), this.perm || (this.frm && this.frm.perm), explain);

		// Match parent grid controls read only status
		if (status === 'Write' && (this.grid || (this.layout && this.layout.grid) && !cint(this.df.allow_on_submit))) {
			var grid = this.grid || this.layout.grid;
			if (grid.display_status == 'Read') {
				status = 'Read';
				if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
			}
		}

		let value = frappe.model.get_value(this.doctype, this.docname, this.df.fieldname);
		value = this.get_parsed_value(value);

		// hide if no value
		if (this.doctype && status==="Read" && !this.only_input
			&& is_null(value)
			&& !in_list(["HTML", "Image", "Button"], this.df.fieldtype)) {

			// eslint-disable-next-line
			if (explain) console.log("By Hide Read-only, null fields: None"); // eslint-disable-line no-console
			status = "None";
		}

		return status;
	}
	refresh() {
		this.disp_status = this.get_status();
		this.$wrapper
			&& this.$wrapper.toggleClass("hide-control", this.disp_status=="None")
			&& this.refresh_input
			&& this.refresh_input();

		var value = this.get_value();

		this.show_translatable_button(value);
	}
	show_translatable_button(value) {
		// Disable translation non-string fields or special string fields
		if (!frappe.model
			|| !this.frm
			|| !this.doc
			|| !this.df.translatable
			|| !frappe.model.can_write('Translation')
			|| !value) return;

		// Disable translation in website
		if (!frappe.views || !frappe.views.TranslationManager) return;

		// Already attached button
		if (this.$wrapper.find('.clearfix .btn-translation').length) return;

		const translation_btn =
			`

			`;

		$(translation_btn)
			.appendTo(this.$wrapper.find('.clearfix'))
			.on('click', () => {
				if (!this.doc.__islocal) {
					new frappe.views.TranslationManager({
						'df': this.df,
						'source_text': this.value,
						'target_language': this.doc.language,
						'doc': this.doc
					});
				}
			});

	}
	get_doc() {
		return this.doctype && this.docname
			&& locals[this.doctype] && locals[this.doctype][this.docname] || {};
	}
	get_model_value() {
		if(this.doc) {
			return this.doc[this.df.fieldname];
		}
	}
	get_parsed_value(value) {
		if (this.parse) {
			value = this.parse(value);
		}
		return value;
	}

	set_value(value, force_set_value=false) {
		return this.validate_and_set_in_model(value, null, force_set_value);
	}
	parse_validate_and_set_in_model(value, e) {
		value = this.get_parsed_value(value);
		return this.validate_and_set_in_model(value, e);
	}
	validate_and_set_in_model(value, e, force_set_value=false) {
		const me = this;
		const is_value_same = (this.get_model_value() === value);

		if (this.inside_change_event || (is_value_same && !force_set_value)) {
			return Promise.resolve();
		}

		this.inside_change_event = true;
		function set(value) {
			me.inside_change_event = false;
			return frappe.run_serially([
				() => me._validated = true,
				() => me.set_model_value(value),
				() => delete me._validated,
				() => {
					me.set_mandatory && me.set_mandatory(value);

					if(me.df.change || me.df.onchange) {
						// onchange event specified in df
						let set = (me.df.change || me.df.onchange).apply(me, [e]);
						me.set_invalid && me.set_invalid();
						return set;
					}
					me.set_invalid && me.set_invalid();
				}
			]);
		};
		value = this.validate(value);
		if (value && value.then) {
			// got a promise
			return value.then((value) => set(value));
		} else {
			// all clear
			return set(value);
		}
	}
	get_value() {
		if(this.get_status()==='Write') {
			return this.get_input_value ?
				(this.parse ? this.parse(this.get_input_value()) : this.get_input_value()) :
				undefined;
		} else {
			return this.value || undefined;
		}
	}
	set_model_value(value) {
		if(this.frm) {
			this.last_value = value;
			return frappe.model.set_value(this.doctype, this.docname, this.df.fieldname,
				value, this.df.fieldtype);
		} else {
			if(this.doc) {
				this.doc[this.df.fieldname] = value;
			}
			this.set_input(value);
			return Promise.resolve();
		}
	}
	set_focus() {
		if(this.$input) {
			this.$input.get(0).focus();
			return true;
		}
	}
};
<commit_msg>fix: Retain value in the field instance when field is rendered on a dialog w/o doc<commit_after>frappe.ui.form.Control = class BaseControl {
	constructor(opts) {
		$.extend(this, opts);
		this.make();

		// if developer_mode=1, show fieldname as tooltip
		if(frappe.boot.user && frappe.boot.developer_mode===1 && this.$wrapper) {
			this.$wrapper.attr("title", __(this.df.fieldname));
		}

		if(this.render_input) {
			this.refresh();
		}
	}
	make() {
		this.make_wrapper();
		this.$wrapper
			.attr("data-fieldtype", this.df.fieldtype)
			.attr("data-fieldname", this.df.fieldname);
		this.wrapper = this.$wrapper.get(0);
		this.wrapper.fieldobj = this; // reference for event handlers
	}

	make_wrapper() {
		this.$wrapper = $("

").appendTo(this.parent);

		// alias
		this.wrapper = this.$wrapper;
	}

	toggle(show) {
		this.df.hidden = show ? 0 : 1;
		this.refresh();
	}

	// returns "Read", "Write" or "None"
	// as strings based on permissions
	get_status(explain) {
		if (this.df.get_status) {
			return this.df.get_status(this);
		}
		if (this.df.is_virtual) {
			return "Read";
		}

		if ((!this.doctype && !this.docname) || this.df.parenttype === 'Web Form' || this.df.is_web_form) {
			let status = "Write";

			// like in case of a dialog box
			if (cint(this.df.hidden)) {
				// eslint-disable-next-line
				if (explain) console.log("By Hidden: None"); // eslint-disable-line no-console
				return "None";

			} else if (cint(this.df.hidden_due_to_dependency)) {
				// eslint-disable-next-line
				if(explain) console.log("By Hidden Dependency: None"); // eslint-disable-line no-console
				return "None";

			} else if (cint(this.df.read_only || this.df.is_virtual || this.df.fieldtype === "Read Only")) {
				// eslint-disable-next-line
				if (explain) console.log("By Read Only: Read"); // eslint-disable-line no-console
				status = "Read";

			} else if ((this.grid &&
						this.grid.display_status == 'Read') ||
						(this.layout &&
						this.layout.grid &&
						this.layout.grid.display_status == 'Read')) {
				// parent grid is read
				if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
				status = "Read";
			}

			let value = this.value || this.get_model_value();
			value = this.get_parsed_value(value);

			if (
				status === "Read" &&
				is_null(value) &&
				!in_list(["HTML", "Image", "Button"], this.df.fieldtype)
			) status = "None";

			return status;
		}

		var status = frappe.perm.get_field_display_status(this.df,
			frappe.model.get_doc(this.doctype, this.docname), this.perm || (this.frm && this.frm.perm), explain);

		// Match parent grid controls read only status
		if (status === 'Write' && (this.grid || (this.layout && this.layout.grid) && !cint(this.df.allow_on_submit))) {
			var grid = this.grid || this.layout.grid;
			if (grid.display_status == 'Read') {
				status = 'Read';
				if (explain) console.log("By Parent Grid Read-only: Read"); // eslint-disable-line no-console
			}
		}

		let value = frappe.model.get_value(this.doctype, this.docname, this.df.fieldname);
		value = this.get_parsed_value(value);

		// hide if no value
		if (this.doctype && status==="Read" && !this.only_input
			&& is_null(value)
			&& !in_list(["HTML", "Image", "Button"], this.df.fieldtype)) {

			// eslint-disable-next-line
			if (explain) console.log("By Hide Read-only, null fields: None"); // eslint-disable-line no-console
			status = "None";
		}

		return status;
	}
	refresh() {
		this.disp_status = this.get_status();
		this.$wrapper
			&& this.$wrapper.toggleClass("hide-control", this.disp_status=="None")
			&& this.refresh_input
			&& this.refresh_input();

		var value = this.get_value();

		this.show_translatable_button(value);
	}
	show_translatable_button(value) {
		// Disable translation non-string fields or special string fields
		if (!frappe.model
			|| !this.frm
			|| !this.doc
			|| !this.df.translatable
			|| !frappe.model.can_write('Translation')
			|| !value) return;

		// Disable translation in website
		if (!frappe.views || !frappe.views.TranslationManager) return;

		// Already attached button
		if (this.$wrapper.find('.clearfix .btn-translation').length) return;

		const translation_btn =
			`

			`;

		$(translation_btn)
			.appendTo(this.$wrapper.find('.clearfix'))
			.on('click', () => {
				if (!this.doc.__islocal) {
					new frappe.views.TranslationManager({
						'df': this.df,
						'source_text': this.value,
						'target_language': this.doc.language,
						'doc': this.doc
					});
				}
			});

	}
	get_doc() {
		return this.doctype && this.docname
			&& locals[this.doctype] && locals[this.doctype][this.docname] || {};
	}
	get_model_value() {
		if(this.doc) {
			return this.doc[this.df.fieldname];
		}
	}
	get_parsed_value(value) {
		if (this.parse) {
			value = this.parse(value);
		}
		return value;
	}

	set_value(value, force_set_value=false) {
		return this.validate_and_set_in_model(value, null, force_set_value);
	}
	parse_validate_and_set_in_model(value, e) {
		value = this.get_parsed_value(value);
		return this.validate_and_set_in_model(value, e);
	}
	validate_and_set_in_model(value, e, force_set_value=false) {
		const me = this;
		const is_value_same = (this.get_model_value() === value);

		if (this.inside_change_event || (is_value_same && !force_set_value)) {
			return Promise.resolve();
		}

		this.inside_change_event = true;
		function set(value) {
			me.inside_change_event = false;
			return frappe.run_serially([
				() => me._validated = true,
				() => me.set_model_value(value),
				() => delete me._validated,
				() => {
					me.set_mandatory && me.set_mandatory(value);

					if(me.df.change || me.df.onchange) {
						// onchange event specified in df
						let set = (me.df.change || me.df.onchange).apply(me, [e]);
						me.set_invalid && me.set_invalid();
						return set;
					}
					me.set_invalid && me.set_invalid();
				}
			]);
		};
		value = this.validate(value);
		if (value && value.then) {
			// got a promise
			return value.then((value) => set(value));
		} else {
			// all clear
			return set(value);
		}
	}
	get_value() {
		if(this.get_status()==='Write') {
			return this.get_input_value ?
				(this.parse ? this.parse(this.get_input_value()) : this.get_input_value()) :
				undefined;
		} else {
			return this.value || undefined;
		}
	}
	set_model_value(value) {
		if (this.frm) {
			this.last_value = value;
			return frappe.model.set_value(this.doctype, this.docname, this.df.fieldname,
				value, this.df.fieldtype);
		} else {
			if (this.doc) {
				this.doc[this.df.fieldname] = value;
			} else {
				// case where input is rendered on dialog where doc is not maintained
				this.value = value;
			}
			this.set_input(value);
			return Promise.resolve();
		}
	}
	set_focus() {
		if(this.$input) {
			this.$input.get(0).focus();
			return true;
		}
	}
};
<|endoftext|>


<commit_before>//1 Win: If the player has two in a row, they can place a third to get three in a row.
//2 Block: If the opponent has two in a row, the player must play the third themself to block the opponent.
//3 Fork: Create an opportunity where the player has two threats to win (two non-blocked lines of 2).
//4 Blocking an opponent's fork:
//    Option 1: The player should create two in a row to force the opponent into defending, as long as it doesn't result in them creating a fork. For example, if "X" has a corner, "O" has the center, and "X" has the opposite corner as well, "O" must not play a corner in order to win. (Playing a corner in this scenario creates a fork for "X" to win.)
//    Option 2: If there is a configuration where the opponent can fork, the player should block that fork.
//5 Center: A player marks the center. (If it is the first move of the game, playing on a corner gives "O" more opportunities to make a mistake and may therefore be the better choice; however, it makes no difference between perfect players.)
//6 Opposite corner: If the opponent is in the corner, the player plays the opposite corner.
//7 Empty corner: The player plays in a corner square.
//8 Empty side: The player plays in a middle square on any of the 4 sides.
var II = {
  twoInARow: [
    [0, 1, 2],
    [0, 2, 1],
    [1, 2, 0],
    [3, 4, 5],
    [3, 5, 4],
    [4, 5, 3],
    [6, 7, 8],
    [6, 8, 7],
    [7, 8, 6],
    [0, 3, 6],
    [0, 6, 3],
    [3, 6, 0],
    [1, 4, 7],
    [1, 7, 4],
    [4, 7, 1],
    [2, 5, 8],
    [2, 8, 5],
    [5, 8, 2],
    [0, 4, 8],
    [4, 8, 0],
    [0, 8, 4],
    [2, 4, 6],
    [2, 6, 4],
    [4, 6, 2]
  ],

  fork: [
    [2, 6, 1],
    [0, 8, 5],
    [2, 3, 0],
    [3, 8, 6],
    [0, 7, 6],
    [2, 7, 8],
    [5, 6, 8],
    [0, 5, 2],
    [1, 6, 0],
    [1, 8, 2],
    [1, 3, 6],
    [3, 7, 0],
    [1, 5, 2],
    [5, 7, 8]
  ],

  X: 'X',
  O: 'O',
  positions: [],

  currentPlayerX: function(currMove) {
    return currMove % 2 == 0;
  },

  haveTwoInARowCrosses: function() {
    var X = this.X,
      O = this.O;
    return this.isTwoInARow(X, O);
  },

  haveTwoInARowNoughts: function() {
    var X = this.X,
      O = this.O;
    return this.isTwoInARow(O, X);
  },

  isTwoInARow: function(ch, oppositeCh) {
    var twoInARow = this.twoInARow,
      positions = this.positions;
    for (var i = 0; i < twoInARow.length; i++) {
      if (positions[twoInARow[i][0]] == ch &&
        positions[twoInARow[i][1]] == ch &&
        positions[twoInARow[i][2]] != oppositeCh)
        return true;
    }
    return false;
  },

  winForNoughts: function() {
    var twoInARow = this.twoInARow,
      positions = this.positions,
      X = this.X,
      O = this.O;
    for (var i = 0; i < twoInARow.length; i++) {
      if (positions[twoInARow[i][0]] == O &&
        positions[twoInARow[i][1]] == O &&
        positions[twoInARow[i][2]] != X)
        return twoInARow[i][2];
    }
  },

  winForCrosses: function() {
    var twoInARow = this.twoInARow,
      positions = this.positions,
      X = this.X,
      O = this.O;
    for (var i = 0; i < twoInARow.length; i++) {
      if (positions[twoInARow[i][0]] == X &&
        positions[twoInARow[i][1]] == X &&
        positions[twoInARow[i][2]] != O)
        return twoInARow[i][2];
    }
  },

  blockFromNoughts: function() {
    return this.winForCrosses();
  },

  blockFromCrosses: function() {
    return this.winForNoughts();
  },

  haveForkCrosses: function() {
    var X = this.X,
      O = this.O;
    return this.isFork(X, O);
  },

  haveForkNoughts: function() {
    var X = this.X,
      O = this.O;
    return this.isFork(O, X);
  },

  isFork: function(ch, oppositeCh) {
    var fork = this.fork,
      positions = this.positions;
    for (var i = 0; i < fork.length; i++) {
      if (positions[fork[i][0]] == ch &&
        positions[fork[i][1]] == ch &&
        positions[fork[i][2]] != oppositeCh)
        return true;
    }
    return false;
  },

  setForkNoughts: function() {
    var fork = this.fork,
      positions = this.positions,
      X = this.X,
      O = this.O;
    for (var i = 0; i < fork.length; i++) {
      if (positions[fork[i][0]] == O &&
        positions[fork[i][1]] == O &&
        positions[fork[i][2]] != X)
        return fork[i][2];
    }
  },

  setForkCrosses: function() {
    var fork = this.fork,
      positions = this.positions,
      X = this.X,
      O = this.O;
    for (var i = 0; i < fork.length; i++) {
      if (positions[fork[i][0]] == X &&
        positions[fork[i][1]] == X &&
        positions[fork[i][2]] != O)
        return fork[i][2];
    }
  },

  blockForkFromNoughts: function() {
    return this.setForkCrosses();
  },

  blockForkFromCrosses: function() {
    return this.setForkNoughts();
  },

  getCorner: function(ch) {
    var positions = this.positions;
    if (positions[0] === ch && positions[8] === '') {
      return 8;
    }
    if (positions[2] === ch && positions[6] === '') {
      return 6;
    }
    if (positions[6] === ch && positions[2] === '') {
      return 2;
    }
    if (positions[8] === ch && positions[0] === '') {
      return 0;
    }
    return -1;
  },

  getCornerOrNot: function() {
    var positions = this.positions;
    if (positions[0] === '') return 0;
    if (positions[2] === '') return 2;
    if (positions[6] === '') return 6;
    if (positions[8] === '') return 8;
    return -1;
  },

  getSideOrNot: function() {
    var positions = this.positions;
    if (positions[1] === '') return 1;
    if (positions[3] === '') return 3;
    if (positions[5] === '') return 5;
    if (positions[7] === '') return 7;
    return -1;
  },

  calculateComputerMove: function(positions, currMove) {
    this.positions = positions;
    //1
    if (this.currentPlayerX(currMove)) {
      if (this.haveTwoInARowCrosses()) {
        return this.winForCrosses();
      }
    } else {
      if (this.haveTwoInARowNoughts()) {
        return this.winForNoughts();
      }
    }

    //2
    if (this.currentPlayerX(currMove)) {
      if (this.haveTwoInARowNoughts()) {
        return this.blockFromCrosses();
      }
    } else {
      if (this.haveTwoInARowCrosses()) {
        return this.blockFromNoughts();
      }
    }

    //3
    if (this.currentPlayerX(currMove)) {
      if (this.haveForkCrosses()) {
        return this.setForkCrosses();
      }
    } else {
      if (this.haveForkNoughts()) {
        return this.setForkNoughts();
      }
    }

    //2
    if (this.currentPlayerX(currMove)) {
      if (this.haveForkNoughts()) {
        return this.blockForkFromCrosses();
      }
    } else {
      if (this.haveForkCrosses()) {
        return this.blockForkFromNoughts();
      }
    }

    //5
    if (positions[4] === '') {
      return 4;
    }

    //6
    if (this.currentPlayerX(currMove)) {
      if (this.getCorner(this.O) !== -1)
        return this.getCorner(this.O);
    } else {
      if (this.getCorner(this.X) !== -1)
        return this.getCorner(this.X);
    }

    //7
    if (this.getCornerOrNot() !== -1)
      return this.getCornerOrNot();

    //8
    if (this.getSideOrNot() !== -1)
      return this.getSideOrNot();
  }
}
<commit_msg>Bug #1 fixed<commit_after>//1 Win: If the player has two in a row, they can place a third to get three in a row.
//2 Block: If the opponent has two in a row, the player must play the third themself to block the opponent.
//3 Fork: Create an opportunity where the player has two threats to win (two non-blocked lines of 2).
//4 Blocking an opponent's fork:
//    Option 1: The player should create two in a row to force the opponent into defending, as long as it doesn't result in them creating a fork. For example, if "X" has a corner, "O" has the center, and "X" has the opposite corner as well, "O" must not play a corner in order to win. (Playing a corner in this scenario creates a fork for "X" to win.)
//    Option 2: If there is a configuration where the opponent can fork, the player should block that fork.
//5 Center: A player marks the center. (If it is the first move of the game, playing on a corner gives "O" more opportunities to make a mistake and may therefore be the better choice; however, it makes no difference between perfect players.)
//6 Opposite corner: If the opponent is in the corner, the player plays the opposite corner.
//7 Empty corner: The player plays in a corner square.
//8 Empty side: The player plays in a middle square on any of the 4 sides.
var II = {
  twoInARow: [
    [0, 1, 2],
    [0, 2, 1],
    [1, 2, 0],
    [3, 4, 5],
    [3, 5, 4],
    [4, 5, 3],
    [6, 7, 8],
    [6, 8, 7],
    [7, 8, 6],
    [0, 3, 6],
    [0, 6, 3],
    [3, 6, 0],
    [1, 4, 7],
    [1, 7, 4],
    [4, 7, 1],
    [2, 5, 8],
    [2, 8, 5],
    [5, 8, 2],
    [0, 4, 8],
    [4, 8, 0],
    [0, 8, 4],
    [2, 4, 6],
    [2, 6, 4],
    [4, 6, 2]
  ],

  fork: [
    [2, 6, 1],
    [0, 8, 5],
    [2, 3, 0],
    [3, 8, 6],
    [0, 7, 6],
    [2, 7, 8],
    [5, 6, 8],
    [0, 5, 2],
    [1, 6, 0],
    [1, 8, 2],
    [1, 3, 6],
    [3, 7, 0],
    [1, 5, 2],
    [5, 7, 8]
  ],

  X: 'X',
  O: 'O',
  positions: [],

  currentPlayerX: function(currMove) {
    return currMove % 2 == 0;
  },

  haveTwoInARowCrosses: function() {
    var X = this.X,
      O = this.O;
    return this.isTwoInARow(X, O);
  },

  haveTwoInARowNoughts: function() {
    var X = this.X,
      O = this.O;
    return this.isTwoInARow(O, X);
  },

  isTwoInARow: function(ch, oppositeCh) {
    var twoInARow = this.twoInARow,
      positions = this.positions;
    for (var i = 0; i < twoInARow.length; i++) {
      if (positions[twoInARow[i][0]] == ch &&
        positions[twoInARow[i][1]] == ch &&
        positions[twoInARow[i][2]] === '')
        return true;
    }
    return false;
  },

  winForNoughts: function() {
    var twoInARow = this.twoInARow,
      positions = this.positions,
      X = this.X,
      O = this.O;
    for (var i = 0; i < twoInARow.length; i++) {
      if (positions[twoInARow[i][0]] == O &&
        positions[twoInARow[i][1]] == O &&
        positions[twoInARow[i][2]] === '')
        return twoInARow[i][2];
    }
  },

  winForCrosses: function() {
    var twoInARow = this.twoInARow,
      positions = this.positions,
      X = this.X,
      O = this.O;
    for (var i = 0; i < twoInARow.length; i++) {
      if (positions[twoInARow[i][0]] == X &&
        positions[twoInARow[i][1]] == X &&
        positions[twoInARow[i][2]] === '')
        return twoInARow[i][2];
    }
  },

  blockFromNoughts: function() {
    return this.winForCrosses();
  },

  blockFromCrosses: function() {
    return this.winForNoughts();
  },

  haveForkCrosses: function() {
    var X = this.X,
      O = this.O;
    return this.isFork(X, O);
  },

  haveForkNoughts: function() {
    var X = this.X,
      O = this.O;
    return this.isFork(O, X);
  },

  isFork: function(ch, oppositeCh) {
    var fork = this.fork,
      positions = this.positions;
    for (var i = 0; i < fork.length; i++) {
      if (positions[fork[i][0]] == ch &&
        positions[fork[i][1]] == ch &&
        positions[fork[i][2]] === '')
        return true;
    }
    return false;
  },

  setForkNoughts: function() {
    var fork = this.fork,
      positions = this.positions,
      X = this.X,
      O = this.O;
    for (var i = 0; i < fork.length; i++) {
      if (positions[fork[i][0]] == O &&
        positions[fork[i][1]] == O &&
        positions[fork[i][2]] === '')
        return fork[i][2];
    }
  },

  setForkCrosses: function() {
    var fork = this.fork,
      positions = this.positions,
      X = this.X,
      O = this.O;
    for (var i = 0; i < fork.length; i++) {
      if (positions[fork[i][0]] == X &&
        positions[fork[i][1]] == X &&
        positions[fork[i][2]] === '')
        return fork[i][2];
    }
  },

  blockForkFromNoughts: function() {
    return this.setForkCrosses();
  },

  blockForkFromCrosses: function() {
    return this.setForkNoughts();
  },

  getCorner: function(ch) {
    var positions = this.positions;
    if (positions[0] === ch && positions[8] === '') {
      return 8;
    }
    if (positions[2] === ch && positions[6] === '') {
      return 6;
    }
    if (positions[6] === ch && positions[2] === '') {
      return 2;
    }
    if (positions[8] === ch && positions[0] === '') {
      return 0;
    }
    return -1;
  },

  getCornerOrNot: function() {
    var positions = this.positions;
    if (positions[0] === '') return 0;
    if (positions[2] === '') return 2;
    if (positions[6] === '') return 6;
    if (positions[8] === '') return 8;
    return -1;
  },

  getSideOrNot: function() {
    var positions = this.positions;
    if (positions[1] === '') return 1;
    if (positions[3] === '') return 3;
    if (positions[5] === '') return 5;
    if (positions[7] === '') return 7;
    return -1;
  },

  calculateComputerMove: function(positions, currMove) {
    this.positions = positions;
    //1
    if (this.currentPlayerX(currMove)) {
      if (this.haveTwoInARowCrosses()) {
        return this.winForCrosses();
      }
    } else {
      if (this.haveTwoInARowNoughts()) {
        return this.winForNoughts();
      }
    }

    //2
    if (this.currentPlayerX(currMove)) {
      if (this.haveTwoInARowNoughts()) {
        return this.blockFromCrosses();
      }
    } else {
      if (this.haveTwoInARowCrosses()) {
        return this.blockFromNoughts();
      }
    }

    //3
    if (this.currentPlayerX(currMove)) {
      if (this.haveForkCrosses()) {
        return this.setForkCrosses();
      }
    } else {
      if (this.haveForkNoughts()) {
        return this.setForkNoughts();
      }
    }

    //4
    if (this.currentPlayerX(currMove)) {
      if (this.haveForkNoughts()) {
        return this.blockForkFromCrosses();
      }
    } else {
      if (this.haveForkCrosses()) {
        return this.blockForkFromNoughts();
      }
    }

    //5
    if (positions[4] === '') {
      return 4;
    }

    //6
    if (this.currentPlayerX(currMove)) {
      if (this.getCorner(this.O) !== -1)
        return this.getCorner(this.O);
    } else {
      if (this.getCorner(this.X) !== -1)
        return this.getCorner(this.X);
    }

    //7
    if (this.getCornerOrNot() !== -1)
      return this.getCornerOrNot();

    //8
    if (this.getSideOrNot() !== -1)
      return this.getSideOrNot();
  }
}
<|endoftext|>


<commit_before>// thecount.js -- node command-line utility to analyze Firefox Marketplace catalog
// online version at http://wfwalker.github.io/thecount/
// see https://github.com/wfwalker/thecount

var request = require('request');
var fs = require('fs');
var Q = require('q');
var parseArgs = require('minimist');
var url = require('url');
var parseAppcacheManifest = require("parse-appcache-manifest");

// global scope

var theScope = {};
theScope.apps = {};
theScope.pendingRequests = 0;

// creates a Q promise that
//      resolves upon getting the bytes at the supplied URL and parsing them as JSON
//      rejects otherwise

function getPromiseForRequestAndParseJSON(inURL) {
    var deferred = Q.defer();

    theScope.pendingRequests += 1;

    request({ uri: inURL, strictSSL: false }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            theScope.pendingRequests -= 1;

            try {
                deferred.resolve(JSON.parse(body.trim()));
            }
            catch (e) {
                console.log('cannot parse ' + inURL + ', ' + e + '\n\n\n\n' + body + '\n\n\n\n\n\n');
                deferred.reject(new Error(e));
            }
        } else {
            theScope.pendingRequests -= 1;
            // console.log('cannot retrieve ' + inURL + ', ' + error);
            deferred.reject(new Error(error));
        }
    });

    return deferred.promise;
}

// creates a Q promise that
//      resolves upon getting the bytes at the supplied URL
//      rejects otherwise

function getPromiseForRequest(inURL) {
    var deferred = Q.defer();

    theScope.pendingRequests += 1;

    request(inURL, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            theScope.pendingRequests -= 1;
            deferred.resolve(body);
        } else {
            theScope.pendingRequests -= 1;
            // console.log('cannot retrieve ' + inURL + ', ' + error);
            deferred.reject(new Error(error));
        }
    });

    return deferred.promise;
}

// creates a Q promise that
//      resolves upon getting a response to a HEAD request and returns the content-length header
//      rejects otherwise

function getPromiseForResponseContentSize(inURL) {
    var deferred = Q.defer();

    theScope.pendingRequests += 1;

    request(inURL, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            theScope.pendingRequests -= 1;
            deferred.resolve(response.headers['content-length']);
        } else {
            theScope.pendingRequests -= 1;
            // console.log('cannot retrieve ' + inURL + ', ' + error);
            deferred.reject(new Error(error));
        }
    });

    return deferred.promise;
}

// returns a Q Promise for retrieving and parsing an app's manifest
// catches errors and returns them as JSON

function getManifest(inApp) {
    return getPromiseForRequestAndParseJSON(inApp.manifest_url).catch(function (error) {
        console.log('getManifest ' + inApp.manifest_url + ' CATCH ' + error);
        // TODO: this doesn't seem to work
        return {'error' : error};
    });
}

// returns a Q promise for retrieving an app's appcache manifest
// catches errors and returns them as JSON

function getAppcacheManifest(inApp) {
    var manifestURL = url.parse(inApp.manifest_url);
    var appcacheManifestURL = url.resolve(manifestURL, inApp.manifest.appcache_path);

    return getPromiseForRequest(appcacheManifestURL).catch(function (error) {
        console.log('getAppcacheManifest ' + appcacheManifestURL + ' CATCH ' + error);
        // TODO: this doesn't seem to work
        return {'error' : error};
    });
}

// returns a Q promise for retrieving the size of a resource on the web
// catches errors and returns 0 instead

function getAppcacheManifestEntrySize(inEntryURL) {
    return getPromiseForResponseContentSize(inEntryURL).catch(function (error) {
        // console.log('getAppcacheManifestEntrySize ' + inEntryURL + ' CATCH ' + error);
        // TODO: this doesn't seem to work
        return 0;
    })

}

// adds to the existing array of promises a promise
// to retrieve the size of an entry in the appcache manifest

function addPromiseForAppcacheEntry(subpromises, app, entryURL) {
    subpromises.push(getAppcacheManifestEntrySize(entryURL).then(function (responseContentLength) {
        theScope.apps[app.id].appcache_entry_sizes[entryURL] = responseContentLength;
    }));
}

// adds to the existing array of promises one or two promises
// to retrieve the app manifest and appcache manifest

function addPromiseForManifest(subpromises, app) {
    if (app.manifest_url) {
        var manifestURL = url.parse(app.manifest_url);

        // add a subpromise for the app manifest
        subpromises.push(getManifest(app).then(function (data) {
            theScope.apps[app.id].manifest = data;
            theScope.apps[app.id].appcache_entry_sizes = {};

            if (data.appcache_path) {
                // add a subpromise for the appcache manifest
                subpromises.push(getAppcacheManifest(theScope.apps[app.id]).then(function (appcacheData) {
                    theScope.apps[app.id].appcache_manifest = appcacheData;

                    var entries = parseAppcacheManifest(appcacheData);
                    theScope.apps[app.id].appcache_entry_count = entries.cache.length;

                    for (var entryIndex = 0; entryIndex < entries.cache.length; entryIndex++) {
                        var entry = entries.cache[entryIndex];
                        var entryURL = url.resolve(manifestURL, entry);

                        // add a subpromise for the size of each appcache entry
                        addPromiseForAppcacheEntry(subpromises, app, entryURL);
                    }
                }));
            }
        }));
    }
}

// returns a Q Promise for searching the Apps catalog using the Marketplace API

function searchAppData(inSearchURL) {
    return getPromiseForRequestAndParseJSON(inSearchURL).then(function (data) {
        // resolved
        var subpromises = [];

        for (index in data.objects) {
            var app = data.objects[index];
            theScope.apps[app.id] = app;
            addPromiseForManifest(subpromises, app);
        }

        if (data.meta.next) {
            console.log(data.meta.offset + '/' + data.meta.total_count + ' pending ' + theScope.pendingRequests + ' size ' + Object.keys(theScope.apps).length);
            subpromises.push(searchAppData('https://marketplace.firefox.com' + data.meta.next));
        }

        return Q.all(subpromises);
    });
}

// returns a Q promise to retrieve the entire firefox marketplace catalog

function findAppData() {
    return searchAppData('https://marketplace.firefox.com/api/v1/apps/search/?format=JSON&limit=200');
}

// CSV REPORT GENERATION --------------------------------------------------------------------------------------

// Emitting CSV into the given file for the given data rows

function emitCSV(inOutputFile, inData) {
    var stream = fs.createWriteStream(inOutputFile);

    stream.once('open', function(fd) {
        for (var index in inData) {
            var row = inData[index];
            stream.write(row.join(',') + '\n');
        }
        stream.end();

        console.log('wrote ' + inData.length + ' rows to ' + inOutputFile);
    });
}

// emit a table with one row per app showing various attributes

function emitPackageSizeTable(inOutputFile) {
    var rows = [];

    rows.push([
        'name',
        'type',
        'payments',
        'ratings',
        'weekly_downloads',
        'created',
        'cache size',
    ]);

    for (index in theScope.apps) {
        var app = theScope.apps[index];
        var appNameKeys = Object.keys(app.name);

        var manifestURL = url.parse(app.manifest_url);

        var cacheSize = '';

        if (app.manifest && app.manifest.size) {
            cacheSize = app.manifest.size;
        }

        if (app.appcache_entry_count) {
            cacheSize = sumAppcacheEntrySizes(app.appcache_entry_sizes);
        }

        rows.push([
            app.name[appNameKeys[0]].replace(/,/g, ''),
            app.app_type,
            app.premium_type,
            app.ratings ? app.ratings.count : '',
            (app.weekly_downloads != 'null') ? app.weekly_downloads: '',
            app.created.substring(0, 10),
            cacheSize
        ]);
    }

    emitCSV(inOutputFile, rows);
}

// Compute the distribution of packaged app sizes

function sumAppcacheEntrySizes(inAppcacheEntrySizes) {
    var total = 0;

    for (var entryIndex in inAppcacheEntrySizes) {
        var entry = inAppcacheEntrySizes[entryIndex];
        if (parseInt(entry) != NaN) {
            total += parseInt(entry);
        }
    }
}

function emitPackageSizeSummary(inOutputFile, inFilterCB) {
    var appTotal = 0.0;
    var appCount = 0;
    var min = 100000000.0;
    var max = 0.0;
    var rows = [];

    var countsByMB = [];

    for (var index = 0; index < 50; index++) {
        countsByMB[index] = 0;
    }

    for (index in theScope.apps) {
        var app = theScope.apps[index];

        if (inFilterCB && (! inFilterCB(app))) {
            continue;
        }

        if (app.manifest && app.manifest.size) {
            var mb = Math.round(app.manifest.size / 1000000);
            countsByMB[mb] = countsByMB[mb] + 1;

            appTotal = appTotal + Math.round(app.manifest.size);
            min = Math.min(min, app.manifest.size);
            max = Math.max(max, app.manifest.size);
            appCount = appCount + 1;
        }
    }

    rows.push(['total', appTotal]);
    rows.push(['count', appCount]);
    rows.push(['average', Math.round(appTotal / appCount)]);
    rows.push(['min', min]);
    rows.push(['max', max]);

    rows.push(['size', 'count']);

    for (var index = 0; index < 50; index++) {
        rows.push(['"' + index + '"', countsByMB[index]]);
    }

    emitCSV(inOutputFile, rows);
}

function emitPermissionUsageSummary(inOutputFile) {
    var rows = [];

    var permissionCounts = {};
    var appsFound = 0;

    for (index in theScope.apps) {
        var app = theScope.apps[index];

        if (app.manifest.permissions) {
            appsFound++;

            var permissionKeys = Object.keys(app.manifest.permissions);
            for (var permissionsIndex in permissionKeys) {
                var permission = permissionKeys[permissionsIndex];

                if (permissionCounts[permission]) {
                    permissionCounts[permission]++;
                } else {
                    permissionCounts[permission] = 1;
                }
            }
        }
    }

    rows.push(['total', appsFound]);

    for (countKey in permissionCounts) {
        var count = permissionCounts[countKey];
        rows.push([countKey, count]);
    }

    emitCSV(inOutputFile, rows);
}

// Build a summary of all the common attributes of apps

function emitAppKindSummary(inOutputFile) {
    var rows = [];

    var packaged = 0, privileged = 0, hosted = 0, appcache = 0;

    var desktop = 0, firefoxos = 0, androidtablet = 0, androidmobile = 0, android = 0;

    var freeapp = 0, premiumapp = 0, freeinapp = 0, premiuminapp = 0;

    for (index in theScope.apps) {
        var app = theScope.apps[index];

        if (app.app_type == 'hosted') { hosted++; }
        if (app.app_type == 'privileged') { privileged++; }
        if (app.app_type == 'packaged') { packaged++; }
        if (app.manifest.appcache_path) { appcache++; }

        if (app.premium_type == 'free') { freeapp++; }
        if (app.premium_type == 'premium') { premiumapp++; }
        if (app.premium_type == 'free-inapp') { freeinapp++; }
        if (app.premium_type == 'premium-inapp') { premiuminapp++; }

        if (app.device_types.indexOf('desktop') > -1) { desktop++; }
        if (app.device_types.indexOf('firefoxos') > -1) { firefoxos++; }
        if (app.device_types.indexOf('android-tablet') > -1) { androidtablet++; android++; }
        if (app.device_types.indexOf('android-mobile') > -1) { androidmobile++; android++; }
    }

    rows.push(['total', Object.keys(theScope.apps).length]);
    rows.push(['hosted', hosted]);
    rows.push(['privileged', privileged]);
    rows.push(['packaged', packaged]);
    rows.push(['appcache', appcache]);

    rows.push(['free', freeapp]);
    rows.push(['premium', premiumapp]);
    rows.push(['free-inapp', freeinapp]);
    rows.push(['premium-inapp', premiuminapp]);

    rows.push(['desktop', desktop]);
    rows.push(['firefoxos', firefoxos]);
    rows.push(['android-tablet', androidtablet]);
    rows.push(['android-mobile', androidmobile]);
    rows.push(['android-total', android]);

    emitCSV(inOutputFile, rows);
}

// Creating and Loading the local database

function loadDB(inJSONFilename) {
    var raw = fs.readFileSync(inJSONFilename);

    try {
        theScope.apps = JSON.parse(raw);
        console.log('loaded ' + Object.keys(theScope.apps).length + ' objects');
    }
    catch (e) {
        console.log('cannot parse ' + inJSONFilename + ', ' + e);
        theScope.apps = [];
    }
}

function createMarketplaceCatalogDB(inOutputFile) {
    return findAppData().then(function() {
        console.log('DONE ALL ' + theScope.apps.length);

        fs.writeFile(inOutputFile, JSON.stringify(theScope.apps, null, 4), function(err) {
            if (err) {
              console.log(err);
            } else {
              console.log("JSON saved to " + inOutputFile);
            }
        });
    }).catch(function (error) {
        console.log('create err ' + error);
    });
}

// MAIN - parse command-line arguments and either build the database or analyze it

var argv = parseArgs(process.argv.slice(2));

if (argv['build']) {
    createMarketplaceCatalogDB('apps.json');
}

if (argv['emit']) {
    loadDB('apps.json');
    emitPackageSizeSummary('package-size-summary.csv');

    emitPackageSizeSummary('android-package-size-summary.csv', function (app) {
        return (app.device_types.indexOf('android-tablet') > -1) || (app.device_types.indexOf('android-mobile') > -1);
    });

    emitPackageSizeTable('marketplace-app-table.csv');
    emitAppKindSummary('app-kind-summary.csv');
    emitPermissionUsageSummary('app-permission-summary.csv');
}

<commit_msg>start pulling down packages and store in /tmp<commit_after>// thecount.js -- node command-line utility to analyze Firefox Marketplace catalog
// online version at http://wfwalker.github.io/thecount/
// see https://github.com/wfwalker/thecount

var request = require('request');
var fs = require('fs');
var Q = require('q');
var parseArgs = require('minimist');
var url = require('url');
var parseAppcacheManifest = require("parse-appcache-manifest");

// global scope

var theScope = {};
theScope.apps = {};
theScope.pendingRequests = 0;

// creates a Q promise that
//      resolves upon getting the bytes at the supplied URL and parsing them as JSON
//      rejects otherwise

function getPromiseForRequestAndParseJSON(inURL) {
    var deferred = Q.defer();

    theScope.pendingRequests += 1;

    request({ uri: inURL, strictSSL: false }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            theScope.pendingRequests -= 1;

            try {
                deferred.resolve(JSON.parse(body.trim()));
            }
            catch (e) {
                console.log('cannot parse ' + inURL + ', ' + e);
                deferred.reject(new Error(e));
            }
        } else {
            theScope.pendingRequests -= 1;
            // console.log('cannot retrieve ' + inURL + ', ' + error);
            deferred.reject(new Error(error));
        }
    });

    return deferred.promise;
}

// creates a Q promise that
//      resolves upon getting the bytes at the supplied URL and saving them in tmp
//      rejects otherwise

function getPromiseForDownloadPackage(inURL, inFilename) {
    var deferred = Q.defer();

    theScope.pendingRequests += 1;

    request({ uri: inURL, strictSSL: false, encoding: null }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            theScope.pendingRequests -= 1;

            try {
                fs.writeFileSync(inFilename, body);
                deferred.resolve(inFilename);
            }
            catch (e) {
                console.log('cannot parse ' + inURL + ', ' + e);
                deferred.reject(new Error(e));
            }
        } else {
            theScope.pendingRequests -= 1;
            // console.log('cannot retrieve ' + inURL + ', ' + error);
            deferred.reject(new Error(error));
        }
    });

    return deferred.promise;
}

// creates a Q promise that
//      resolves upon getting the bytes at the supplied URL
//      rejects otherwise

function getPromiseForRequest(inURL) {
    var deferred = Q.defer();

    theScope.pendingRequests += 1;

    request(inURL, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            theScope.pendingRequests -= 1;
            deferred.resolve(body);
        } else {
            theScope.pendingRequests -= 1;
            // console.log('cannot retrieve ' + inURL + ', ' + error);
            deferred.reject(new Error(error));
        }
    });

    return deferred.promise;
}

// creates a Q promise that
//      resolves upon getting a response to a HEAD request and returns the content-length header
//      rejects otherwise

function getPromiseForResponseContentSize(inURL) {
    var deferred = Q.defer();

    theScope.pendingRequests += 1;

    request(inURL, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            theScope.pendingRequests -= 1;
            deferred.resolve(response.headers['content-length']);
        } else {
            theScope.pendingRequests -= 1;
            // console.log('cannot retrieve ' + inURL + ', ' + error);
            deferred.reject(new Error(error));
        }
    });

    return deferred.promise;
}

// returns a Q Promise for retrieving and parsing an app's manifest
// catches errors and returns them as JSON

function getManifest(inApp) {
    return getPromiseForRequestAndParseJSON(inApp.manifest_url).catch(function (error) {
        console.log('getManifest ' + inApp.manifest_url + ' CATCH ' + error);
        // TODO: this doesn't seem to work
        return {'error' : error};
    });
}

// returns a Q promise for retrieving an app's appcache manifest
// catches errors and returns them as JSON

function getAppcacheManifest(inApp) {
    var manifestURL = url.parse(inApp.manifest_url);
    var appcacheManifestURL = url.resolve(manifestURL, inApp.manifest.appcache_path);

    return getPromiseForRequest(appcacheManifestURL).catch(function (error) {
        console.log('getAppcacheManifest ' + appcacheManifestURL + ' CATCH ' + error);
        // TODO: this doesn't seem to work
        return {'error' : error};
    });
}

// returns a Q promise for retrieving an app's appcache manifest
// catches errors and returns them as JSON

function getAppPackage(inApp) {
    var filename = '/tmp/' + inApp.id + '.zip';
    var manifestURL = url.parse(inApp.manifest_url);
    var packageURL = url.resolve(manifestURL, inApp.manifest.package_path);
    console.log('packageURL ' + packageURL);

    return getPromiseForDownloadPackage(packageURL, filename).catch(function (error) {
        console.log('getAppPackage ' + packageURL + ' CATCH ' + error);
        // TODO: this doesn't seem to work
        return {'error' : error};
    });
}

// function writePackageToTmp(inApp, inData) {
//     console.log('writePackageToTmp');
//     console.log(inData);

//     var deferred = Q.defer();
//     var tmpFilename = '/tmp/' + inApp.id + '.zip';

//     fs.writeFile(tmpFilename, inData, function (err) {
//         if (err) {
//             deferred.reject(new Error(err))
//         } else {
//             deferred.resolve(tmpFilename);
//         }
//     });

//     return deferred.promise;
// }

// returns a Q promise for retrieving the size of a resource on the web
// catches errors and returns 0 instead

function getAppcacheManifestEntrySize(inEntryURL) {
    return getPromiseForResponseContentSize(inEntryURL).catch(function (error) {
        // console.log('getAppcacheManifestEntrySize ' + inEntryURL + ' CATCH ' + error);
        // TODO: this doesn't seem to work
        return 0;
    })

}

// adds to the existing array of promises a promise
// to retrieve the size of an entry in the appcache manifest

function addPromiseForAppcacheEntry(subpromises, app, entryURL) {
    subpromises.push(getAppcacheManifestEntrySize(entryURL).then(function (responseContentLength) {
        theScope.apps[app.id].appcache_entry_sizes[entryURL] = responseContentLength;
    }));
}

// adds to the existing array of promises one or two promises
// to retrieve the app manifest and appcache manifest

function addPromiseForManifest(subpromises, app) {
    if (app.manifest_url) {
        var manifestURL = url.parse(app.manifest_url);

        // add a subpromise for the app manifest
        subpromises.push(getManifest(app).then(function (data) {
            theScope.apps[app.id].manifest = data;
            theScope.apps[app.id].appcache_entry_sizes = {};

            // for apps with a package, add a promise to retrieve the package
            if (data.package_path) {
                subpromises.push(getAppPackage(theScope.apps[app.id]));
            }

            // for apps that use appcache, add promises to retrieve the size of each
            // asset listed in the appcache manifest
            if (data.appcache_path) {
                // add a subpromise for the appcache manifest
                subpromises.push(getAppcacheManifest(theScope.apps[app.id]).then(function (appcacheData) {
                    theScope.apps[app.id].appcache_manifest = appcacheData;

                    var entries = parseAppcacheManifest(appcacheData);
                    theScope.apps[app.id].appcache_entry_count = entries.cache.length;

                    for (var entryIndex = 0; entryIndex < entries.cache.length; entryIndex++) {
                        var entry = entries.cache[entryIndex];
                        var entryURL = url.resolve(manifestURL, entry);

                        // add a subpromise for the size of each appcache entry
                        addPromiseForAppcacheEntry(subpromises, app, entryURL);
                    }
                }));
            }
        }));
    }
}

// returns a Q Promise for searching the Apps catalog using the Marketplace API

function searchAppData(inSearchURL) {
    return getPromiseForRequestAndParseJSON(inSearchURL).then(function (data) {
        // resolved
        var subpromises = [];

        for (index in data.objects) {
            var app = data.objects[index];
            theScope.apps[app.id] = app;
            addPromiseForManifest(subpromises, app);
        }

        if (data.meta.next) {
            console.log(data.meta.offset + '/' + data.meta.total_count + ' pending ' + theScope.pendingRequests + ' size ' + Object.keys(theScope.apps).length);
            subpromises.push(searchAppData('https://marketplace.firefox.com' + data.meta.next));
        }

        return Q.all(subpromises);
    });
}

// returns a Q promise to retrieve the entire firefox marketplace catalog

function findAppData() {
    return searchAppData('https://marketplace.firefox.com/api/v1/apps/search/?format=JSON&limit=200');
}

// CSV REPORT GENERATION --------------------------------------------------------------------------------------

// Emitting CSV into the given file for the given data rows

function emitCSV(inOutputFile, inData) {
    var stream = fs.createWriteStream(inOutputFile);

    stream.once('open', function(fd) {
        for (var index in inData) {
            var row = inData[index];
            stream.write(row.join(',') + '\n');
        }
        stream.end();

        console.log('wrote ' + inData.length + ' rows to ' + inOutputFile);
    });
}

// emit a table with one row per app showing various attributes

function emitPackageSizeTable(inOutputFile) {
    var rows = [];

    rows.push([
        'name',
        'type',
        'payments',
        'ratings',
        'weekly_downloads',
        'created',
        'cache size',
    ]);

    for (index in theScope.apps) {
        var app = theScope.apps[index];
        var appNameKeys = Object.keys(app.name);

        var manifestURL = url.parse(app.manifest_url);

        var cacheSize = '';

        if (app.manifest && app.manifest.size) {
            cacheSize = app.manifest.size;
        }

        if (app.appcache_entry_count) {
            cacheSize = sumAppcacheEntrySizes(app.appcache_entry_sizes);
        }

        rows.push([
            app.name[appNameKeys[0]].replace(/,/g, ''),
            app.app_type,
            app.premium_type,
            app.ratings ? app.ratings.count : '',
            (app.weekly_downloads != 'null') ? app.weekly_downloads: '',
            app.created.substring(0, 10),
            cacheSize
        ]);
    }

    emitCSV(inOutputFile, rows);
}

// Compute the distribution of packaged app sizes

function sumAppcacheEntrySizes(inAppcacheEntrySizes) {
    var total = 0;

    for (var entryIndex in inAppcacheEntrySizes) {
        var entry = inAppcacheEntrySizes[entryIndex];
        if (parseInt(entry) != NaN) {
            total += parseInt(entry);
        }
    }
}

function emitPackageSizeSummary(inOutputFile, inFilterCB) {
    var appTotal = 0.0;
    var appCount = 0;
    var min = 100000000.0;
    var max = 0.0;
    var rows = [];

    var countsByMB = [];

    for (var index = 0; index < 50; index++) {
        countsByMB[index] = 0;
    }

    for (index in theScope.apps) {
        var app = theScope.apps[index];

        if (inFilterCB && (! inFilterCB(app))) {
            continue;
        }

        if (app.manifest && app.manifest.size) {
            var mb = Math.round(app.manifest.size / 1000000);
            countsByMB[mb] = countsByMB[mb] + 1;

            appTotal = appTotal + Math.round(app.manifest.size);
            min = Math.min(min, app.manifest.size);
            max = Math.max(max, app.manifest.size);
            appCount = appCount + 1;
        }
    }

    rows.push(['total', appTotal]);
    rows.push(['count', appCount]);
    rows.push(['average', Math.round(appTotal / appCount)]);
    rows.push(['min', min]);
    rows.push(['max', max]);

    rows.push(['size', 'count']);

    for (var index = 0; index < 50; index++) {
        rows.push(['"' + index + '"', countsByMB[index]]);
    }

    emitCSV(inOutputFile, rows);
}

function emitPermissionUsageSummary(inOutputFile) {
    var rows = [];

    var permissionCounts = {};
    var appsFound = 0;

    for (index in theScope.apps) {
        var app = theScope.apps[index];

        if (app.manifest.permissions) {
            appsFound++;

            var permissionKeys = Object.keys(app.manifest.permissions);
            for (var permissionsIndex in permissionKeys) {
                var permission = permissionKeys[permissionsIndex];

                if (permissionCounts[permission]) {
                    permissionCounts[permission]++;
                } else {
                    permissionCounts[permission] = 1;
                }
            }
        }
    }

    rows.push(['total', appsFound]);

    for (countKey in permissionCounts) {
        var count = permissionCounts[countKey];
        rows.push([countKey, count]);
    }

    emitCSV(inOutputFile, rows);
}

// Build a summary of all the common attributes of apps

function emitAppKindSummary(inOutputFile) {
    var rows = [];

    var packaged = 0, privileged = 0, hosted = 0, appcache = 0;

    var desktop = 0, firefoxos = 0, androidtablet = 0, androidmobile = 0, android = 0;

    var freeapp = 0, premiumapp = 0, freeinapp = 0, premiuminapp = 0;

    for (index in theScope.apps) {
        var app = theScope.apps[index];

        if (app.app_type == 'hosted') { hosted++; }
        if (app.app_type == 'privileged') { privileged++; }
        if (app.app_type == 'packaged') { packaged++; }
        if (app.manifest.appcache_path) { appcache++; }

        if (app.premium_type == 'free') { freeapp++; }
        if (app.premium_type == 'premium') { premiumapp++; }
        if (app.premium_type == 'free-inapp') { freeinapp++; }
        if (app.premium_type == 'premium-inapp') { premiuminapp++; }

        if (app.device_types.indexOf('desktop') > -1) { desktop++; }
        if (app.device_types.indexOf('firefoxos') > -1) { firefoxos++; }
        if (app.device_types.indexOf('android-tablet') > -1) { androidtablet++; android++; }
        if (app.device_types.indexOf('android-mobile') > -1) { androidmobile++; android++; }
    }

    rows.push(['total', Object.keys(theScope.apps).length]);
    rows.push(['hosted', hosted]);
    rows.push(['privileged', privileged]);
    rows.push(['packaged', packaged]);
    rows.push(['appcache', appcache]);

    rows.push(['free', freeapp]);
    rows.push(['premium', premiumapp]);
    rows.push(['free-inapp', freeinapp]);
    rows.push(['premium-inapp', premiuminapp]);

    rows.push(['desktop', desktop]);
    rows.push(['firefoxos', firefoxos]);
    rows.push(['android-tablet', androidtablet]);
    rows.push(['android-mobile', androidmobile]);
    rows.push(['android-total', android]);

    emitCSV(inOutputFile, rows);
}

// Creating and Loading the local database

function loadDB(inJSONFilename) {
    var raw = fs.readFileSync(inJSONFilename);

    try {
        theScope.apps = JSON.parse(raw);
        console.log('loaded ' + Object.keys(theScope.apps).length + ' objects');
    }
    catch (e) {
        console.log('cannot parse ' + inJSONFilename + ', ' + e);
        theScope.apps = [];
    }
}

function createMarketplaceCatalogDB(inOutputFile) {
    return findAppData().then(function() {
        console.log('DONE ALL ' + theScope.apps.length);

        fs.writeFile(inOutputFile, JSON.stringify(theScope.apps, null, 4), function(err) {
            if (err) {
              console.log(err);
            } else {
              console.log("JSON saved to " + inOutputFile);
            }
        });
    }).catch(function (error) {
        console.log('create err ' + error);
    });
}

// MAIN - parse command-line arguments and either build the database or analyze it

var argv = parseArgs(process.argv.slice(2));

if (argv['build']) {
    createMarketplaceCatalogDB('apps.json');
}

if (argv['emit']) {
    loadDB('apps.json');
    emitPackageSizeSummary('package-size-summary.csv');

    emitPackageSizeSummary('android-package-size-summary.csv', function (app) {
        return (app.device_types.indexOf('android-tablet') > -1) || (app.device_types.indexOf('android-mobile') > -1);
    });

    emitPackageSizeTable('marketplace-app-table.csv');
    emitAppKindSummary('app-kind-summary.csv');
    emitPermissionUsageSummary('app-permission-summary.csv');
}

<|endoftext|>


<commit_before>;

import angular from 'angular';
import 'ng-redux';
import chatTextFieldModule from './chat-textfield.js';
import postHeaderModule from './post-header.js';
import feedbackGridModule from './feedback-grid.js';
import postSeeksInfoModule from './post-seeks-info.js';
import postIsInfoModule from './post-is-info.js';
import labelledHrModule from './labelled-hr.js';

import {
    selectOpenPostUri,
    selectNeedByConnectionUri,
    selectLastUpdateTime,
} from '../selectors.js';
import {
    connect2Redux,
} from '../won-utils.js';
import {
    relativeTime,
} from '../won-label-utils.js';
import {
    attach,
    getIn,
} from '../utils.js';
import { actionCreators }  from '../actions/actions.js';

const serviceDependencies = ['$ngRedux', '$scope'];

function genComponentConf() {
    let template = `

                <svg style="--local-primary:var(--won-primary-color);"
                     class="post-info__header__icon clickable">
                    <use href="#ico36_close"></use>
                </svg>

            <won-post-header
                need-uri="self.postUriToConnectTo"
                timestamp="self.createdTimestamp"
                hide-image="::false">
            </won-post-header>
            <svg class="post-info__header__icon__small clickable"
                style="--local-primary:#var(--won-secondary-color);"
                ng-show="!self.contextMenuOpen"
                ng-click="self.contextMenuOpen = true">
                    <use href="#ico16_arrow_down"></use>
            </svg>

                      <svg class="post-info__header__icon__small__contextmenu clickable"
                        style="--local-primary:black;"
                        ng-click="self.contextMenuOpen = false">
                            <use href="#ico16_arrow_up"></use>
                      </svg>

                    <button ng-if="self.connection && self.connection.get('isRated')"
                        class="post-info__footer__button won-button--filled black"
                        ng-click="self.closeConnection()">
                            Remove This
                    </button>

                        <svg class="rdflink__small">
                            <use href="#rdf_logo_1"></use>
                        </svg>
                        Show RDF

            <won-gallery ng-show="self.suggestedPost.get('hasImages')">
            </won-gallery>

                Created

                {{ self.friendlyTimestamp }}

                <won-post-is-info is-part="::self.isPart"></won-post-is-info>

                <won-post-seeks-info seeks-part="::self.seeksPart"></won-post-seeks-info>

                    Know someone who might also be interested in this posting? Consider sharing the link below in social media.

                <input class="post-info__footer__link__input" value="{{self.linkToPost}}" disabled type="text">

            <won-labelled-hr label="::'Or'" class="post-info__footer__labelledhr"></won-labelled-hr>

            <won-feedback-grid ng-if="self.connection && !self.connection.get('isRated')" connection-uri="self.connectionUri"></won-feedback-grid>
            <chat-textfield
                placeholder="::'Request Message (optional)'"
                on-input="::self.input(value)"
                on-paste="::self.input(value)"
                on-submit="::self.sendRequest()"
                allow-empty-submit="true"
                submit-button-label="::'Ask to Chat'"
                ng-if="!self.connection || self.connection.get('isRated')"
                >
            </chat-textfield>

                    <svg class="rdflink__small">
                        <use href="#rdf_logo_1"></use>
                    </svg>
                    Show RDF

    `;

    class Controller {
        constructor() {
            attach(this, serviceDependencies, arguments);
            this.maxThumbnails = 9;
            this.message = '';
            this.WON = won.WON;
            window.openMatch4dbg = this;

            const selectFromState = (state) => {
                //const sendAdHocRequest = getIn(state, ['router', 'currentParams', 'sendAdHocRequest']); //if this parameter is set we will not have a connection to send this request to

                const connectionUri = decodeURIComponent(getIn(state, ['router', 'currentParams', 'connectionUri']));
                const ownNeed = connectionUri && selectNeedByConnectionUri(state, connectionUri);
                const connection = ownNeed && ownNeed.getIn(["connections", connectionUri]);
                const postUriToConnectTo = !connection ? selectOpenPostUri(state) : connection && connection.get("remoteNeedUri");

                const suggestedPost = state.getIn(["needs", postUriToConnectTo]);

                const is = suggestedPost? suggestedPost.get('is') : undefined;
                //TODO it will be possible to have more than one seeks
                const seeks = suggestedPost? suggestedPost.get('seeks') : undefined;

                return {
                    connection,
                    connectionUri,
                    ownNeed,
                    isPart: is? {
                        postUri: postUriToConnectTo,
                        is: is,
                        isString: 'is',
                        location: is && is.get('location'),
                        address: is.get('location') && is.get('location').get('address'),
                    }: undefined,
                    seeksPart: seeks? {
                        postUri: postUriToConnectTo,
                        seeks: seeks,
                        seeksString: 'seeks',
                        location: seeks && seeks.get('location'),
                        address: seeks.get('location') && seeks.get('location').get('address'),
                    }: undefined,
                    suggestedPost,
                    lastUpdateTimestamp: connection && connection.get('lastUpdateDate'),
                    connectionUri,
                    postUriToConnectTo,
                    linkToPost: suggestedPost && new URL("/owner/#!post/?postUri="+encodeURI(suggestedPost.get('uri')), window.location.href).href,
                    friendlyTimestamp: suggestedPost && relativeTime(
                        selectLastUpdateTime(state),
                        suggestedPost.get('creationDate')
                    ),
                    createdTimestamp: suggestedPost && suggestedPost.get('creationDate'),
                }
            };
            connect2Redux(selectFromState, actionCreators, [], this);
        }

        input(userInput) {
            this.chatMessage = userInput;
        }

        sendRequest() {
            message = this.chatMessage;

            if(!this.connection || (this.ownNeed && this.ownNeed.get("isWhatsAround"))){
                if(this.ownNeed && this.ownNeed.get("isWhatsAround")){
                    //Close the connection if there was a present connection for a whatsaround need
                    this.connections__close(this.connectionUri);
                }

                if(this.postUriToConnectTo){
                    this.connections__connectAdHoc(this.postUriToConnectTo, message);
                }

                //this.router__stateGoCurrent({connectionUri: null, sendAdHocRequest: null});
                this.router__stateGoResetParams('connections');
            }else{
                this.needs__connect(
                		this.ownNeed.get("uri"),
                		this.connectionUri,
                		this.ownNeed.getIn(['connections',this.connectionUri]).get("remoteNeedUri"),
                		message);
                this.router__stateGoCurrent({connectionUri: this.connectionUri})
            }
        }

        closeConnection(){
            this.connections__close(this.connectionUri);
            this.router__stateGoCurrent({connectionUri: null});
        }
    }
    Controller.$inject = serviceDependencies;

    return {
        restrict: 'E',
        controller: Controller,
        controllerAs: 'self',
        bindToController: true, //scope-bindings -> ctrl
        scope: {
            includeHeader: '='
        },
        template: template
    }
}

export default angular.module('won.owner.components.sendRequest', [
    postIsInfoModule,
    postSeeksInfoModule,
    postHeaderModule,
    feedbackGridModule,
    labelledHrModule,
    chatTextFieldModule,
])
    .directive('wonSendRequest', genComponentConf)
    .name;

<commit_msg>Removed other usage of mediumjs-based textfield.<commit_after>;

import angular from 'angular';
import 'ng-redux';
import postHeaderModule from './post-header.js';
import feedbackGridModule from './feedback-grid.js';
import postSeeksInfoModule from './post-seeks-info.js';
import postIsInfoModule from './post-is-info.js';
import labelledHrModule from './labelled-hr.js';
import chatTextFieldSimpleModule from './chat-textfield-simple.js';

import {
    selectOpenPostUri,
    selectNeedByConnectionUri,
    selectLastUpdateTime,
} from '../selectors.js';
import {
    connect2Redux,
} from '../won-utils.js';
import {
    relativeTime,
} from '../won-label-utils.js';
import {
    attach,
    getIn,
} from '../utils.js';
import { actionCreators }  from '../actions/actions.js';

const serviceDependencies = ['$ngRedux', '$scope'];

function genComponentConf() {
    let template = `

                <svg style="--local-primary:var(--won-primary-color);"
                     class="post-info__header__icon clickable">
                    <use href="#ico36_close"></use>
                </svg>

            <won-post-header
                need-uri="self.postUriToConnectTo"
                timestamp="self.createdTimestamp"
                hide-image="::false">
            </won-post-header>
            <svg class="post-info__header__icon__small clickable"
                style="--local-primary:#var(--won-secondary-color);"
                ng-show="!self.contextMenuOpen"
                ng-click="self.contextMenuOpen = true">
                    <use href="#ico16_arrow_down"></use>
            </svg>

                      <svg class="post-info__header__icon__small__contextmenu clickable"
                        style="--local-primary:black;"
                        ng-click="self.contextMenuOpen = false">
                            <use href="#ico16_arrow_up"></use>
                      </svg>

                    <button ng-if="self.connection && self.connection.get('isRated')"
                        class="post-info__footer__button won-button--filled black"
                        ng-click="self.closeConnection()">
                            Remove This
                    </button>

                        <svg class="rdflink__small">
                            <use href="#rdf_logo_1"></use>
                        </svg>
                        Show RDF

            <won-gallery ng-show="self.suggestedPost.get('hasImages')">
            </won-gallery>

                Created

                {{ self.friendlyTimestamp }}

                <won-post-is-info is-part="::self.isPart"></won-post-is-info>

                <won-post-seeks-info seeks-part="::self.seeksPart"></won-post-seeks-info>

                    Know someone who might also be interested in this posting? Consider sharing the link below in social media.

                <input class="post-info__footer__link__input" value="{{self.linkToPost}}" disabled type="text">

            <won-labelled-hr label="::'Or'" class="post-info__footer__labelledhr"></won-labelled-hr>

            <won-feedback-grid ng-if="self.connection && !self.connection.get('isRated')" connection-uri="self.connectionUri"></won-feedback-grid>

            <chat-textfield-simple
                placeholder="::'Request Message (optional)'"
                on-submit="::self.sendRequest(value)"
                allow-empty-submit="::true"
                submit-button-label="::'Ask to Chat'"
                ng-if="!self.connection || self.connection.get('isRated')"
            >
            </chat-textfield-simple>

                    <svg class="rdflink__small">
                        <use href="#rdf_logo_1"></use>
                    </svg>
                    Show RDF

    `;

    class Controller {
        constructor() {
            attach(this, serviceDependencies, arguments);
            this.maxThumbnails = 9;
            this.message = '';
            this.WON = won.WON;
            window.openMatch4dbg = this;

            const selectFromState = (state) => {
                //const sendAdHocRequest = getIn(state, ['router', 'currentParams', 'sendAdHocRequest']); //if this parameter is set we will not have a connection to send this request to

                const connectionUri = decodeURIComponent(getIn(state, ['router', 'currentParams', 'connectionUri']));
                const ownNeed = connectionUri && selectNeedByConnectionUri(state, connectionUri);
                const connection = ownNeed && ownNeed.getIn(["connections", connectionUri]);
                const postUriToConnectTo = !connection ? selectOpenPostUri(state) : connection && connection.get("remoteNeedUri");

                const suggestedPost = state.getIn(["needs", postUriToConnectTo]);

                const is = suggestedPost? suggestedPost.get('is') : undefined;
                //TODO it will be possible to have more than one seeks
                const seeks = suggestedPost? suggestedPost.get('seeks') : undefined;

                return {
                    connection,
                    connectionUri,
                    ownNeed,
                    isPart: is? {
                        postUri: postUriToConnectTo,
                        is: is,
                        isString: 'is',
                        location: is && is.get('location'),
                        address: is.get('location') && is.get('location').get('address'),
                    }: undefined,
                    seeksPart: seeks? {
                        postUri: postUriToConnectTo,
                        seeks: seeks,
                        seeksString: 'seeks',
                        location: seeks && seeks.get('location'),
                        address: seeks.get('location') && seeks.get('location').get('address'),
                    }: undefined,
                    suggestedPost,
                    lastUpdateTimestamp: connection && connection.get('lastUpdateDate'),
                    connectionUri,
                    postUriToConnectTo,
                    linkToPost: suggestedPost && new URL("/owner/#!post/?postUri="+encodeURI(suggestedPost.get('uri')), window.location.href).href,
                    friendlyTimestamp: suggestedPost && relativeTime(
                        selectLastUpdateTime(state),
                        suggestedPost.get('creationDate')
                    ),
                    createdTimestamp: suggestedPost && suggestedPost.get('creationDate'),
                }
            };
            connect2Redux(selectFromState, actionCreators, [], this);
        }

        sendRequest(message) {
            if(!this.connection || (this.ownNeed && this.ownNeed.get("isWhatsAround"))){
                if(this.ownNeed && this.ownNeed.get("isWhatsAround")){
                    //Close the connection if there was a present connection for a whatsaround need
                    this.connections__close(this.connectionUri);
                }

                if(this.postUriToConnectTo){
                    this.connections__connectAdHoc(this.postUriToConnectTo, message);
                }

                //this.router__stateGoCurrent({connectionUri: null, sendAdHocRequest: null});
                this.router__stateGoResetParams('connections');
            }else{
                this.needs__connect(
                		this.ownNeed.get("uri"),
                		this.connectionUri,
                		this.ownNeed.getIn(['connections',this.connectionUri]).get("remoteNeedUri"),
                		message);
                this.router__stateGoCurrent({connectionUri: this.connectionUri})
            }
        }

        closeConnection(){
            this.connections__close(this.connectionUri);
            this.router__stateGoCurrent({connectionUri: null});
        }
    }
    Controller.$inject = serviceDependencies;

    return {
        restrict: 'E',
        controller: Controller,
        controllerAs: 'self',
        bindToController: true, //scope-bindings -> ctrl
        scope: {
            includeHeader: '='
        },
        template: template
    }
}

export default angular.module('won.owner.components.sendRequest', [
    postIsInfoModule,
    postSeeksInfoModule,
    postHeaderModule,
    feedbackGridModule,
    labelledHrModule,
    chatTextFieldSimpleModule,
])
    .directive('wonSendRequest', genComponentConf)
    .name;

<|endoftext|>