<commit_before>// ERPNext - web based ERP (http://erpnext.com)
// Copyright (C) 2012 Web Notes Technologies Pvt Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// tree of chart of accounts / cost centers
// multiple companies
// add node
// edit node
// see ledger

pscript['onload_Accounts Browser'] = function(wrapper){
	wrapper.appframe = new wn.ui.AppFrame($(wrapper).find('.appframe-area'));
	wrapper.appframe.add_home_breadcrumb()
	wrapper.appframe.add_module_breadcrumb("Accounts")

	if (wn.boot.profile.can_create.indexOf("Company") !== -1) {
		wrapper.appframe.add_button('New Company', function() { newdoc('Company'); },
			'icon-plus');
	}

	wrapper.appframe.add_button('Refresh', function() {
			wrapper.$company_select.change();
		}, 'icon-refresh');

	// company-select
	wrapper.$company_select = $('<select class="accbrowser-company-select"></select>')
		.change(function() {
			var ctype = wn.get_route()[1] || 'Account';
			erpnext.account_chart = new erpnext.AccountsChart(ctype, $(this).val(), wrapper);
			pscript.set_title(wrapper, ctype, $(this).val());
		})
		.appendTo(wrapper.appframe.$w.find('.appframe-toolbar'));

	// load up companies
	wn.call({
		method:'accounts.page.accounts_browser.accounts_browser.get_companies',
		callback: function(r) {
			wrapper.$company_select.empty();
			$.each(r.message, function(i, v) {
				$('<option>').html(v).attr('value', v).appendTo(wrapper.$company_select);
			});
			wrapper.$company_select.val(sys_defaults.company || r[0]).change();
		}
	});
}

pscript.set_title = function(wrapper, ctype, val) {
	if(val) {
		wrapper.appframe.set_title('Chart of '+ctype+'s' + " - " + cstr(val));
	} else {
		wrapper.appframe.set_title('Chart of '+ctype+'s');
	}
}

pscript['onshow_Accounts Browser'] = function(wrapper){
	// set route
	var ctype = wn.get_route()[1] || 'Account';

	if(erpnext.account_chart && erpnext.account_chart.ctype != ctype) {
		wrapper.$company_select.change();
	}

	pscript.set_title(wrapper, ctype);
}

erpnext.AccountsChart = Class.extend({
	init: function(ctype, company, wrapper) {
		$(wrapper).find('.tree-area').empty();
		var me = this;
		me.ctype = ctype;
		me.can_create = wn.model.can_create(this.ctype);
		me.can_delete = wn.model.can_delete(this.ctype);
		me.can_write = wn.model.can_write(this.ctype);

		me.company = company;
		this.tree = new wn.ui.Tree({
			parent: $(wrapper).find('.tree-area'),
			label: company,
			args: {ctype: ctype, comp: company},
			method: 'accounts.page.accounts_browser.accounts_browser.get_children',
			click: function(link) {
				if(me.cur_toolbar)
					$(me.cur_toolbar).toggle(false);

				if(!link.toolbar)
					me.make_link_toolbar(link);

				if(link.toolbar) {
					me.cur_toolbar = link.toolbar;
					$(me.cur_toolbar).toggle(true);
				}

				// bold
				$('.balance-bold').removeClass('balance-bold'); // deselect
				$(link).parent().find('.balance-area:first').addClass('balance-bold'); // select

			},
			onrender: function(treenode) {
				if (ctype == 'Account' && treenode.data) {
					if(treenode.data.balance) {
						treenode.parent.append(''
							+ format_currency(treenode.data.balance, treenode.data.currency)
							+ '');
					}
				}
			}
		});
		this.tree.rootnode.$a.click();
	},
	make_link_toolbar: function(link) {
		var data = $(link).data('node-data');
		if(!data) return;

		link.toolbar = $('').insertAfter(link);

		var node_links = [];
		// edit
		if (wn.model.can_read(this.ctype) !== -1) {
			node_links.push('Edit');
		}
		if (data.expandable) {
			node_links.push('Add Child');
		} else if (this.ctype === 'Account' && wn.boot.profile.can_read.indexOf("GL Entry") !== -1) {
			node_links.push('View Ledger');
		}

		if (this.can_write) {
			node_links.push('Rename');
		};

		if (this.can_delete) {
			node_links.push('Delete');
		};

		link.toolbar.append(node_links.join(" | "));
	},
	open: function() {
		var node = this.selected_node();
		wn.set_route("Form", this.ctype, node.data("label"));
	},
	show_ledger: function() {
		var me = this;
		var node = me.selected_node();
		wn.set_route("general-ledger", "account=" + node.data('label'));
	},
	rename: function() {
		var node = this.selected_node();
		wn.model.rename_doc(this.ctype, node.data('label'), function(new_name) {
			node.data('label', new_name).find(".tree-label").html(new_name);
		});
	},
	delete: function() {
		var node = this.selected_node();
		wn.model.delete_doc(this.ctype, node.data('label'), function() {
			node.parent().remove();
		});
	},
	new_node: function() {
		if(this.ctype=='Account') {
			this.new_account();
		} else {
			this.new_cost_center();
		}
	},
	selected_node: function() {
		return this.tree.$w.find('.tree-link.selected');
	},
	new_account: function() {
		var me = this;

		// the dialog
		var d = new wn.ui.Dialog({
			title:'New Account',
			fields: [
				{fieldtype:'Data', fieldname:'account_name', label:'New Account Name', reqd:true,
					description: "Name of new Account. Note: Please don't create accounts for Customers and Suppliers, \
					they are created automatically from the Customer and Supplier master"},
				{fieldtype:'Select', fieldname:'group_or_ledger', label:'Group or Ledger',
					options:'Group\nLedger', description:'Further accounts can be made under Groups,\
					 	but entries can be made against Ledger'},
				{fieldtype:'Select', fieldname:'account_type', label:'Account Type',
					options: ['', 'Fixed Asset Account', 'Bank or Cash', 'Expense Account', 'Tax',
						'Income Account', 'Chargeable'].join('\n'),
					description: "Optional. This setting will be used to filter in various transactions." },
				{fieldtype:'Float', fieldname:'tax_rate', label:'Tax Rate'},
				{fieldtype:'Button', fieldname:'create_new', label:'Create New' }
			]
		})

		var fd = d.fields_dict;

		// account type if ledger
		$(fd.group_or_ledger.input).change(function() {
			if($(this).val()=='Group') {
				$(fd.account_type.wrapper).toggle(false);
				$(fd.tax_rate.wrapper).toggle(false);
			} else {
				$(fd.account_type.wrapper).toggle(true);
				if(fd.account_type.get_value()=='Tax') {
					$(fd.tax_rate.wrapper).toggle(true);
				}
			}
		});

		// tax rate if tax
		$(fd.account_type.input).change(function() {
			if($(this).val()=='Tax') {
				$(fd.tax_rate.wrapper).toggle(true);
			} else {
				$(fd.tax_rate.wrapper).toggle(false);
			}
		})

		// create
		$(fd.create_new.input).click(function() {
			var btn = this;
			$(btn).set_working();
			var v = d.get_values();
			if(!v) return;

			var node = me.selected_node();
			v.parent_account = node.data('label');
			v.master_type = '';
			v.company = me.company;

			wn.call({
				args: v,
				method:'accounts.utils.add_ac',
				callback: function(r) {
					$(btn).done_working();
					d.hide();
					node.trigger('reload');
				}
			});
		});

		// show
		d.onshow = function() {
			$(fd.group_or_ledger.input).change();
			$(fd.account_type.input).change();
		}

		$(fd.group_or_ledger.input).val("Ledger").change();
		d.show();
	},

	new_cost_center: function(){
		var me = this;
		// the dialog
		var d = new wn.ui.Dialog({
			title:'New Cost Center',
			fields: [
				{fieldtype:'Data', fieldname:'cost_center_name', label:'New Cost Center Name', reqd:true},
				{fieldtype:'Select', fieldname:'group_or_ledger', label:'Group or Ledger',
					options:'Group\nLedger', description:'Further accounts can be made under Groups,\
					 	but entries can be made against Ledger'},
				{fieldtype:'Button', fieldname:'create_new', label:'Create New' }
			]
		});

		// create
		$(d.fields_dict.create_new.input).click(function() {
			var btn = this;
			$(btn).set_working();
			var v = d.get_values();
			if(!v) return;

			var node = me.selected_node();

			v.parent_cost_center = node.data('label');
			v.company_name = me.company;

			wn.call({
				args: v,
				method:'accounts.utils.add_cc',
				callback: function(r) {
					$(btn).done_working();
					d.hide();
					node.trigger('reload');
				}
			});
		});
		d.show();
	}
});<commit_msg>[rename] [feature] added merge feature to rename_doc<commit_after>// ERPNext - web based ERP (http://erpnext.com)
// Copyright (C) 2012 Web Notes Technologies Pvt Ltd
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// tree of chart of accounts / cost centers
// multiple companies
// add node
// edit node
// see ledger

pscript['onload_Accounts Browser'] = function(wrapper){
	wrapper.appframe = new wn.ui.AppFrame($(wrapper).find('.appframe-area'));
	wrapper.appframe.add_home_breadcrumb()
	wrapper.appframe.add_module_breadcrumb("Accounts")

	if (wn.boot.profile.can_create.indexOf("Company") !== -1) {
		wrapper.appframe.add_button('New Company', function() { newdoc('Company'); },
			'icon-plus');
	}

	wrapper.appframe.add_button('Refresh', function() {
			wrapper.$company_select.change();
		}, 'icon-refresh');

	// company-select
	wrapper.$company_select = $('<select class="accbrowser-company-select"></select>')
		.change(function() {
			var ctype = wn.get_route()[1] || 'Account';
			erpnext.account_chart = new erpnext.AccountsChart(ctype, $(this).val(), wrapper);
			pscript.set_title(wrapper, ctype, $(this).val());
		})
		.appendTo(wrapper.appframe.$w.find('.appframe-toolbar'));

	// load up companies
	wn.call({
		method:'accounts.page.accounts_browser.accounts_browser.get_companies',
		callback: function(r) {
			wrapper.$company_select.empty();
			$.each(r.message, function(i, v) {
				$('<option>').html(v).attr('value', v).appendTo(wrapper.$company_select);
			});
			wrapper.$company_select.val(sys_defaults.company || r[0]).change();
		}
	});
}

pscript.set_title = function(wrapper, ctype, val) {
	if(val) {
		wrapper.appframe.set_title('Chart of '+ctype+'s' + " - " + cstr(val));
	} else {
		wrapper.appframe.set_title('Chart of '+ctype+'s');
	}
}

pscript['onshow_Accounts Browser'] = function(wrapper){
	// set route
	var ctype = wn.get_route()[1] || 'Account';

	if(erpnext.account_chart && erpnext.account_chart.ctype != ctype) {
		wrapper.$company_select.change();
	}

	pscript.set_title(wrapper, ctype);
}

erpnext.AccountsChart = Class.extend({
	init: function(ctype, company, wrapper) {
		$(wrapper).find('.tree-area').empty();
		var me = this;
		me.ctype = ctype;
		me.can_create = wn.model.can_create(this.ctype);
		me.can_delete = wn.model.can_delete(this.ctype);
		me.can_write = wn.model.can_write(this.ctype);

		me.company = company;
		this.tree = new wn.ui.Tree({
			parent: $(wrapper).find('.tree-area'),
			label: company,
			args: {ctype: ctype, comp: company},
			method: 'accounts.page.accounts_browser.accounts_browser.get_children',
			click: function(link) {
				if(me.cur_toolbar)
					$(me.cur_toolbar).toggle(false);

				if(!link.toolbar)
					me.make_link_toolbar(link);

				if(link.toolbar) {
					me.cur_toolbar = link.toolbar;
					$(me.cur_toolbar).toggle(true);
				}

				// bold
				$('.balance-bold').removeClass('balance-bold'); // deselect
				$(link).parent().find('.balance-area:first').addClass('balance-bold'); // select

			},
			onrender: function(treenode) {
				if (ctype == 'Account' && treenode.data) {
					if(treenode.data.balance) {
						treenode.parent.append(''
							+ format_currency(treenode.data.balance, treenode.data.currency)
							+ '');
					}
				}
			}
		});
		this.tree.rootnode.$a.click();
	},
	make_link_toolbar: function(link) {
		var data = $(link).data('node-data');
		if(!data) return;

		link.toolbar = $('').insertAfter(link);

		var node_links = [];
		// edit
		if (wn.model.can_read(this.ctype) !== -1) {
			node_links.push('Edit');
		}
		if (data.expandable) {
			node_links.push('Add Child');
		} else if (this.ctype === 'Account' && wn.boot.profile.can_read.indexOf("GL Entry") !== -1) {
			node_links.push('View Ledger');
		}

		if (this.can_write) {
			node_links.push('Rename');
		};

		if (this.can_delete) {
			node_links.push('Delete');
		};

		link.toolbar.append(node_links.join(" | "));
	},
	open: function() {
		var node = this.selected_node();
		wn.set_route("Form", this.ctype, node.data("label"));
	},
	show_ledger: function() {
		var me = this;
		var node = me.selected_node();
		wn.set_route("general-ledger", "account=" + node.data('label'));
	},
	rename: function() {
		var node = this.selected_node();
		wn.model.rename_doc(this.ctype, node.data('label'), function(new_name) {
			node.parents("ul:first").parent().find(".tree-link:first").trigger("reload");
		});
	},
	delete: function() {
		var node = this.selected_node();
		wn.model.delete_doc(this.ctype, node.data('label'), function() {
			node.parent().remove();
		});
	},
	new_node: function() {
		if(this.ctype=='Account') {
			this.new_account();
		} else {
			this.new_cost_center();
		}
	},
	selected_node: function() {
		return this.tree.$w.find('.tree-link.selected');
	},
	new_account: function() {
		var me = this;

		// the dialog
		var d = new wn.ui.Dialog({
			title:'New Account',
			fields: [
				{fieldtype:'Data', fieldname:'account_name', label:'New Account Name', reqd:true,
					description: "Name of new Account. Note: Please don't create accounts for Customers and Suppliers, \
					they are created automatically from the Customer and Supplier master"},
				{fieldtype:'Select', fieldname:'group_or_ledger', label:'Group or Ledger',
					options:'Group\nLedger', description:'Further accounts can be made under Groups,\
					 	but entries can be made against Ledger'},
				{fieldtype:'Select', fieldname:'account_type', label:'Account Type',
					options: ['', 'Fixed Asset Account', 'Bank or Cash', 'Expense Account', 'Tax',
						'Income Account', 'Chargeable'].join('\n'),
					description: "Optional. This setting will be used to filter in various transactions." },
				{fieldtype:'Float', fieldname:'tax_rate', label:'Tax Rate'},
				{fieldtype:'Button', fieldname:'create_new', label:'Create New' }
			]
		})

		var fd = d.fields_dict;

		// account type if ledger
		$(fd.group_or_ledger.input).change(function() {
			if($(this).val()=='Group') {
				$(fd.account_type.wrapper).toggle(false);
				$(fd.tax_rate.wrapper).toggle(false);
			} else {
				$(fd.account_type.wrapper).toggle(true);
				if(fd.account_type.get_value()=='Tax') {
					$(fd.tax_rate.wrapper).toggle(true);
				}
			}
		});

		// tax rate if tax
		$(fd.account_type.input).change(function() {
			if($(this).val()=='Tax') {
				$(fd.tax_rate.wrapper).toggle(true);
			} else {
				$(fd.tax_rate.wrapper).toggle(false);
			}
		})

		// create
		$(fd.create_new.input).click(function() {
			var btn = this;
			$(btn).set_working();
			var v = d.get_values();
			if(!v) return;

			var node = me.selected_node();
			v.parent_account = node.data('label');
			v.master_type = '';
			v.company = me.company;

			wn.call({
				args: v,
				method:'accounts.utils.add_ac',
				callback: function(r) {
					$(btn).done_working();
					d.hide();
					node.trigger('reload');
				}
			});
		});

		// show
		d.onshow = function() {
			$(fd.group_or_ledger.input).change();
			$(fd.account_type.input).change();
		}

		$(fd.group_or_ledger.input).val("Ledger").change();
		d.show();
	},

	new_cost_center: function(){
		var me = this;
		// the dialog
		var d = new wn.ui.Dialog({
			title:'New Cost Center',
			fields: [
				{fieldtype:'Data', fieldname:'cost_center_name', label:'New Cost Center Name', reqd:true},
				{fieldtype:'Select', fieldname:'group_or_ledger', label:'Group or Ledger',
					options:'Group\nLedger', description:'Further accounts can be made under Groups,\
					 	but entries can be made against Ledger'},
				{fieldtype:'Button', fieldname:'create_new', label:'Create New' }
			]
		});

		// create
		$(d.fields_dict.create_new.input).click(function() {
			var btn = this;
			$(btn).set_working();
			var v = d.get_values();
			if(!v) return;

			var node = me.selected_node();

			v.parent_cost_center = node.data('label');
			v.company_name = me.company;

			wn.call({
				args: v,
				method:'accounts.utils.add_cc',
				callback: function(r) {
					$(btn).done_working();
					d.hide();
					node.trigger('reload');
				}
			});
		});
		d.show();
	}
});<|endoftext|>


<commit_before>SQUARE_DOCK = true;
ROUND_DOCK = false;
DEFAULT_LANG = 'en_US';

FACTORY_SIDE = 'factory';
BLOCK_SIDE = 'block';

VERT_ARRANGE = 1;
HORIZ_ARRANGE = 2;
NA_ARRANGE = 3;

MOBILE_VER = (/Android/i.test(navigator.userAgent));
SUGAR_VER = (window.localStorage.getItem('sugar_settings') !== null || window.top.sugar);

COLOR_TABLE2 = [
    0xFF0000, 0xFF0D00, 0xFF1A00, 0xFF2600, 0xFF3300,
    0xFF4000, 0xFF4D00, 0xFF5900, 0xFF6600, 0xFF7300,
    0xFF8000, 0xFF8C00, 0xFF9900, 0xFFA600, 0xFFB300,
    0xFFBF00, 0xFFCC00, 0xFFD900, 0xFFE600, 0xFFF200,
    0xFFFF00, 0xE6FF00, 0xCCFF00, 0xB3FF00, 0x99FF00,
    0x80FF00, 0x66FF00, 0x4DFF00, 0x33FF00, 0x1AFF00,
    0x00FF00, 0x00FF0D, 0x00FF1A, 0x00FF26, 0x00FF33,
    0x00FF40, 0x00FF4D, 0x00FF59, 0x00FF66, 0x00FF73,
    0x00FF80, 0x00FF8C, 0x00FF99, 0x00FFA6, 0x00FFB3,
    0x00FFBF, 0x00FFCC, 0x00FFD9, 0x00FFE6, 0x00FFF2,
    0x00FFFF, 0x00F2FF, 0x00E6FF, 0x00D9FF, 0x00CCFF,
    0x00BFFF, 0x00B3FF, 0x00A6FF, 0x0099FF, 0x008CFF,
    0x0080FF, 0x0073FF, 0x0066FF, 0x0059FF, 0x004DFF,
    0x0040FF, 0x0033FF, 0x0026FF, 0x001AFF, 0x000DFF,
    0x0000FF, 0x0D00FF, 0x1A00FF, 0x2600FF, 0x3300FF,
    0x4000FF, 0x4D00FF, 0x5900FF, 0x6600FF, 0x7300FF,
    0x8000FF, 0x8C00FF, 0x9900FF, 0xA600FF, 0xB300FF,
    0xBF00FF, 0xCC00FF, 0xD900FF, 0xE600FF, 0xF200FF,
    0xFF00FF, 0xFF00E6, 0xFF00CC, 0xFF00B3, 0xFF0099,
    0xFF0080, 0xFF0066, 0xFF004D, 0xFF0033, 0xFF001A];

NORMAL_BLOCKS_NAMES = {};
NORMAL_BLOCKS_NAMES['forward_block'] = 'forward';
NORMAL_BLOCKS_NAMES['backward_block'] = 'back';
NORMAL_BLOCKS_NAMES['right_block'] = 'right';
NORMAL_BLOCKS_NAMES['left_block'] = 'left';
NORMAL_BLOCKS_NAMES['set_heading_block'] = 'seth';
NORMAL_BLOCKS_NAMES['clean_block'] = 'clean';

NORMAL_BLOCKS_NAMES['penup_block'] = 'penup';
NORMAL_BLOCKS_NAMES['pendown_block'] = 'pendown';
NORMAL_BLOCKS_NAMES['set_pen_size_block'] = 'setpensize';
NORMAL_BLOCKS_NAMES['start_fill_block'] = 'startfill';
NORMAL_BLOCKS_NAMES['end_fill_block'] = 'stopfill';
NORMAL_BLOCKS_NAMES['set_shade_block'] = 'setshade';
NORMAL_BLOCKS_NAMES['set_color_block'] = 'setcolor';
NORMAL_BLOCKS_NAMES['set_gray_block'] = 'setgray';

NORMAL_BLOCKS_NAMES['wait_block'] = 'wait';

NORMAL_BLOCKS_NAMES['start_block'] = 'start2';
NORMAL_BLOCKS_NAMES['action_make_block'] = 'hat';
NORMAL_BLOCKS_NAMES['action_call_block'] = 'stack';

NORMAL_RESIZE_BLOCK_NAMES = {};
NORMAL_RESIZE_BLOCK_NAMES['setxy_block'] = 'setxy2';
NORMAL_RESIZE_BLOCK_NAMES['arc_block'] = 'arc';
NORMAL_RESIZE_BLOCK_NAMES['space_block'] = 'vspace';

NORMAL_RESIZE_BLOCK_NAMES['fill_screen_block'] = 'fillscreen2';

NORMAL_RESIZE_BLOCK_NAMES['identity_block'] = 'identity2';
NORMAL_RESIZE_BLOCK_NAMES['sqrt_block'] = 'sqrt';

NORMAL_RESIZE_BLOCK_NAMES['store_in_box_block'] = 'storein';

STANDALONE_PARAM_BLOCK_NAMES = {};
STANDALONE_PARAM_BLOCK_NAMES['box_block'] = 'number';
STANDALONE_PARAM_BLOCK_NAMES['text_block'] = 'string';

SPECIAL_VAR_GET_BLOCK_NAMES = {};
SPECIAL_VAR_GET_BLOCK_NAMES['xcor_block'] = 'xcor';
SPECIAL_VAR_GET_BLOCK_NAMES['ycor_block'] = 'ycor';
SPECIAL_VAR_GET_BLOCK_NAMES['heading_block'] = 'heading';

SPECIAL_VAR_GET_BLOCK_NAMES['pensize_block'] = 'pensize';
SPECIAL_VAR_GET_BLOCK_NAMES['color_block'] = 'color';
SPECIAL_VAR_GET_BLOCK_NAMES['shade_block'] = 'shade';
SPECIAL_VAR_GET_BLOCK_NAMES['gray_block'] = 'gray';

SPECIAL_VAR_GET_BLOCK_NAMES['red_block'] = 'red';
SPECIAL_VAR_GET_BLOCK_NAMES['green_block'] = 'green';
SPECIAL_VAR_GET_BLOCK_NAMES['purple_block'] = 'purple';
SPECIAL_VAR_GET_BLOCK_NAMES['orange_block'] = 'orange';
SPECIAL_VAR_GET_BLOCK_NAMES['cyan_block'] = 'cyan';
SPECIAL_VAR_GET_BLOCK_NAMES['white_block'] = 'white';
SPECIAL_VAR_GET_BLOCK_NAMES['yellow_block'] = 'yellow';
SPECIAL_VAR_GET_BLOCK_NAMES['blue_block'] = 'blue';
SPECIAL_VAR_GET_BLOCK_NAMES['black_block'] = 'black';

SPECIAL_VAR_GET_BLOCK_NAMES['width_block'] = 'width';
SPECIAL_VAR_GET_BLOCK_NAMES['height_block'] = 'height';
SPECIAL_VAR_GET_BLOCK_NAMES['left2_block'] = 'leftpos';
SPECIAL_VAR_GET_BLOCK_NAMES['right2_block'] = 'rightpos';
SPECIAL_VAR_GET_BLOCK_NAMES['bottom_block'] = 'bottompos';
SPECIAL_VAR_GET_BLOCK_NAMES['top_block'] = 'toppos';

COMPLEX_PARAM_BLOCK_NAMES = {};
COMPLEX_PARAM_BLOCK_NAMES['substract_block'] = 'minus2';
COMPLEX_PARAM_BLOCK_NAMES['add_block'] = 'plus2';
COMPLEX_PARAM_BLOCK_NAMES['multiply_block'] = 'product2';
COMPLEX_PARAM_BLOCK_NAMES['divide_block'] = 'division2';
COMPLEX_PARAM_BLOCK_NAMES['rand_block'] = 'random';
COMPLEX_PARAM_BLOCK_NAMES['mod_block'] = 'remainder2';
COMPLEX_PARAM_BLOCK_NAMES['lowerthan_block'] = 'less2';
COMPLEX_PARAM_BLOCK_NAMES['equals_block'] = 'equal2';
COMPLEX_PARAM_BLOCK_NAMES['greaterthan_block'] = 'greater2';

COMPLEX_FLOW_BLOCK_NAMES = {};
COMPLEX_FLOW_BLOCK_NAMES['repeat_block'] = 'repeat';
COMPLEX_FLOW_BLOCK_NAMES['ifthen_block'] = 'if';
COMPLEX_FLOW_BLOCK_NAMES['while_block'] = 'while';
COMPLEX_FLOW_BLOCK_NAMES['until_block'] = 'until';

NORMAL_FLOW_BLOCK_NAMES = {};
NORMAL_FLOW_BLOCK_NAMES['forever_block'] = 'forever';
<commit_msg>Force TurtleJS mobile version on Firefox OS<commit_after>SQUARE_DOCK = true;
ROUND_DOCK = false;
DEFAULT_LANG = 'en_US';

FACTORY_SIDE = 'factory';
BLOCK_SIDE = 'block';

VERT_ARRANGE = 1;
HORIZ_ARRANGE = 2;
NA_ARRANGE = 3;

MOBILE_VER = (/Android/i.test(navigator.userAgent)||navigator.userAgent.indexOf('Mozilla/5.0 (Mobile;')!=-1);
SUGAR_VER = (window.localStorage.getItem('sugar_settings') !== null || window.top.sugar);

COLOR_TABLE2 = [
    0xFF0000, 0xFF0D00, 0xFF1A00, 0xFF2600, 0xFF3300,
    0xFF4000, 0xFF4D00, 0xFF5900, 0xFF6600, 0xFF7300,
    0xFF8000, 0xFF8C00, 0xFF9900, 0xFFA600, 0xFFB300,
    0xFFBF00, 0xFFCC00, 0xFFD900, 0xFFE600, 0xFFF200,
    0xFFFF00, 0xE6FF00, 0xCCFF00, 0xB3FF00, 0x99FF00,
    0x80FF00, 0x66FF00, 0x4DFF00, 0x33FF00, 0x1AFF00,
    0x00FF00, 0x00FF0D, 0x00FF1A, 0x00FF26, 0x00FF33,
    0x00FF40, 0x00FF4D, 0x00FF59, 0x00FF66, 0x00FF73,
    0x00FF80, 0x00FF8C, 0x00FF99, 0x00FFA6, 0x00FFB3,
    0x00FFBF, 0x00FFCC, 0x00FFD9, 0x00FFE6, 0x00FFF2,
    0x00FFFF, 0x00F2FF, 0x00E6FF, 0x00D9FF, 0x00CCFF,
    0x00BFFF, 0x00B3FF, 0x00A6FF, 0x0099FF, 0x008CFF,
    0x0080FF, 0x0073FF, 0x0066FF, 0x0059FF, 0x004DFF,
    0x0040FF, 0x0033FF, 0x0026FF, 0x001AFF, 0x000DFF,
    0x0000FF, 0x0D00FF, 0x1A00FF, 0x2600FF, 0x3300FF,
    0x4000FF, 0x4D00FF, 0x5900FF, 0x6600FF, 0x7300FF,
    0x8000FF, 0x8C00FF, 0x9900FF, 0xA600FF, 0xB300FF,
    0xBF00FF, 0xCC00FF, 0xD900FF, 0xE600FF, 0xF200FF,
    0xFF00FF, 0xFF00E6, 0xFF00CC, 0xFF00B3, 0xFF0099,
    0xFF0080, 0xFF0066, 0xFF004D, 0xFF0033, 0xFF001A];

NORMAL_BLOCKS_NAMES = {};
NORMAL_BLOCKS_NAMES['forward_block'] = 'forward';
NORMAL_BLOCKS_NAMES['backward_block'] = 'back';
NORMAL_BLOCKS_NAMES['right_block'] = 'right';
NORMAL_BLOCKS_NAMES['left_block'] = 'left';
NORMAL_BLOCKS_NAMES['set_heading_block'] = 'seth';
NORMAL_BLOCKS_NAMES['clean_block'] = 'clean';

NORMAL_BLOCKS_NAMES['penup_block'] = 'penup';
NORMAL_BLOCKS_NAMES['pendown_block'] = 'pendown';
NORMAL_BLOCKS_NAMES['set_pen_size_block'] = 'setpensize';
NORMAL_BLOCKS_NAMES['start_fill_block'] = 'startfill';
NORMAL_BLOCKS_NAMES['end_fill_block'] = 'stopfill';
NORMAL_BLOCKS_NAMES['set_shade_block'] = 'setshade';
NORMAL_BLOCKS_NAMES['set_color_block'] = 'setcolor';
NORMAL_BLOCKS_NAMES['set_gray_block'] = 'setgray';

NORMAL_BLOCKS_NAMES['wait_block'] = 'wait';

NORMAL_BLOCKS_NAMES['start_block'] = 'start2';
NORMAL_BLOCKS_NAMES['action_make_block'] = 'hat';
NORMAL_BLOCKS_NAMES['action_call_block'] = 'stack';

NORMAL_RESIZE_BLOCK_NAMES = {};
NORMAL_RESIZE_BLOCK_NAMES['setxy_block'] = 'setxy2';
NORMAL_RESIZE_BLOCK_NAMES['arc_block'] = 'arc';
NORMAL_RESIZE_BLOCK_NAMES['space_block'] = 'vspace';

NORMAL_RESIZE_BLOCK_NAMES['fill_screen_block'] = 'fillscreen2';

NORMAL_RESIZE_BLOCK_NAMES['identity_block'] = 'identity2';
NORMAL_RESIZE_BLOCK_NAMES['sqrt_block'] = 'sqrt';

NORMAL_RESIZE_BLOCK_NAMES['store_in_box_block'] = 'storein';

STANDALONE_PARAM_BLOCK_NAMES = {};
STANDALONE_PARAM_BLOCK_NAMES['box_block'] = 'number';
STANDALONE_PARAM_BLOCK_NAMES['text_block'] = 'string';

SPECIAL_VAR_GET_BLOCK_NAMES = {};
SPECIAL_VAR_GET_BLOCK_NAMES['xcor_block'] = 'xcor';
SPECIAL_VAR_GET_BLOCK_NAMES['ycor_block'] = 'ycor';
SPECIAL_VAR_GET_BLOCK_NAMES['heading_block'] = 'heading';

SPECIAL_VAR_GET_BLOCK_NAMES['pensize_block'] = 'pensize';
SPECIAL_VAR_GET_BLOCK_NAMES['color_block'] = 'color';
SPECIAL_VAR_GET_BLOCK_NAMES['shade_block'] = 'shade';
SPECIAL_VAR_GET_BLOCK_NAMES['gray_block'] = 'gray';

SPECIAL_VAR_GET_BLOCK_NAMES['red_block'] = 'red';
SPECIAL_VAR_GET_BLOCK_NAMES['green_block'] = 'green';
SPECIAL_VAR_GET_BLOCK_NAMES['purple_block'] = 'purple';
SPECIAL_VAR_GET_BLOCK_NAMES['orange_block'] = 'orange';
SPECIAL_VAR_GET_BLOCK_NAMES['cyan_block'] = 'cyan';
SPECIAL_VAR_GET_BLOCK_NAMES['white_block'] = 'white';
SPECIAL_VAR_GET_BLOCK_NAMES['yellow_block'] = 'yellow';
SPECIAL_VAR_GET_BLOCK_NAMES['blue_block'] = 'blue';
SPECIAL_VAR_GET_BLOCK_NAMES['black_block'] = 'black';

SPECIAL_VAR_GET_BLOCK_NAMES['width_block'] = 'width';
SPECIAL_VAR_GET_BLOCK_NAMES['height_block'] = 'height';
SPECIAL_VAR_GET_BLOCK_NAMES['left2_block'] = 'leftpos';
SPECIAL_VAR_GET_BLOCK_NAMES['right2_block'] = 'rightpos';
SPECIAL_VAR_GET_BLOCK_NAMES['bottom_block'] = 'bottompos';
SPECIAL_VAR_GET_BLOCK_NAMES['top_block'] = 'toppos';

COMPLEX_PARAM_BLOCK_NAMES = {};
COMPLEX_PARAM_BLOCK_NAMES['substract_block'] = 'minus2';
COMPLEX_PARAM_BLOCK_NAMES['add_block'] = 'plus2';
COMPLEX_PARAM_BLOCK_NAMES['multiply_block'] = 'product2';
COMPLEX_PARAM_BLOCK_NAMES['divide_block'] = 'division2';
COMPLEX_PARAM_BLOCK_NAMES['rand_block'] = 'random';
COMPLEX_PARAM_BLOCK_NAMES['mod_block'] = 'remainder2';
COMPLEX_PARAM_BLOCK_NAMES['lowerthan_block'] = 'less2';
COMPLEX_PARAM_BLOCK_NAMES['equals_block'] = 'equal2';
COMPLEX_PARAM_BLOCK_NAMES['greaterthan_block'] = 'greater2';

COMPLEX_FLOW_BLOCK_NAMES = {};
COMPLEX_FLOW_BLOCK_NAMES['repeat_block'] = 'repeat';
COMPLEX_FLOW_BLOCK_NAMES['ifthen_block'] = 'if';
COMPLEX_FLOW_BLOCK_NAMES['while_block'] = 'while';
COMPLEX_FLOW_BLOCK_NAMES['until_block'] = 'until';

NORMAL_FLOW_BLOCK_NAMES = {};
NORMAL_FLOW_BLOCK_NAMES['forever_block'] = 'forever';
<|endoftext|>


<commit_before>odoo.define('payment.processing', function (require) {
    'use strict';

    var publicWidget = require('web.public.widget');
    var ajax = require('web.ajax');
    var core = require('web.core');

    var _t = core._t;

    $.blockUI.defaults.css.border = '0';
    $.blockUI.defaults.css["background-color"] = '';
    $.blockUI.defaults.overlayCSS["opacity"] = '0.9';

    publicWidget.registry.PaymentProcessing = publicWidget.Widget.extend({
        selector: '.o_payment_processing',
        xmlDependencies: ['/payment/static/src/xml/payment_processing.xml'],

        _pollCount: 0,

        start: function() {
            this.displayLoading();
            this.poll();
            return this._super.apply(this, arguments);
        },
        /* Methods */
        startPolling: function () {
            var timeout = 3000;
            //
            if(this._pollCount >= 10 && this._pollCount < 20) {
                timeout = 10000;
            }
            else if(this._pollCount >= 20) {
                timeout = 30000;
            }
            //
            setTimeout(this.poll.bind(this), timeout);
            this._pollCount ++;
        },
        poll: function () {
            var self = this;
            ajax.jsonRpc('/payment/process/poll', 'call', {}).then(function(data) {
                if(data.success === true) {
                    self.processPolledData(data.transactions);
                }
                else {
                    switch(data.error) {
                    case "tx_process_retry":
                        break;
                    case "no_tx_found":
                        self.displayContent("payment.no_tx_found", {});
                        break;
                    default: // if an exception is raised
                        self.displayContent("payment.exception", {exception_msg: data.error});
                        break;
                    }
                }
                self.startPolling();

            }).guardedCatch(function() {
                self.displayContent("payment.rpc_error", {});
                self.startPolling();
            });
        },
        processPolledData: function (transactions) {
            var render_values = {
                'tx_draft': [],
                'tx_pending': [],
                'tx_authorized': [],
                'tx_done': [],
                'tx_cancel': [],
                'tx_error': [],
            };

            if (transactions.length > 0 && ['transfer', 'sepa_direct_debit'].indexOf(transactions[0].acquirer_provider) >= 0) {
                window.location = transactions[0].return_url;
                return;
            }

            // group the transaction according to their state
            transactions.forEach(function (tx) {
                var key = 'tx_' + tx.state;
                if(key in render_values) {
                    render_values[key].push(tx);
                }
            });

            function countTxInState(states) {
                var nbTx = 0;
                for (var prop in render_values) {
                    if (states.indexOf(prop) > -1 && render_values.hasOwnProperty(prop)) {
                        nbTx += render_values[prop].length;
                    }
                }
                return nbTx;
            }
            // if there's only one tx to manage
            if(countTxInState(['tx_done', 'tx_error', 'tx_pending']) === 1) {
                var tx = render_values['tx_done'][0] || render_values['tx_error'][0];
                if (tx) {
                    window.location = tx.return_url;
                    return;
                }
            }

            this.displayContent("payment.display_tx_list", render_values);
        },
        displayContent: function (xmlid, render_values) {
            var html = core.qweb.render(xmlid, render_values);
            $.unblockUI();
            this.$el.find('.o_payment_processing_content').html(html);
        },
        displayLoading: function () {
            var msg = _t("We are processing your payment, please wait ...");
            $.blockUI({
                'message': '
' +
                    '
' + msg +
                    '
'
            });
        },
    });
});
<commit_msg>[FIX] payment: authorized payment should redirect automatically<commit_after>odoo.define('payment.processing', function (require) {
    'use strict';

    var publicWidget = require('web.public.widget');
    var ajax = require('web.ajax');
    var core = require('web.core');

    var _t = core._t;

    $.blockUI.defaults.css.border = '0';
    $.blockUI.defaults.css["background-color"] = '';
    $.blockUI.defaults.overlayCSS["opacity"] = '0.9';

    publicWidget.registry.PaymentProcessing = publicWidget.Widget.extend({
        selector: '.o_payment_processing',
        xmlDependencies: ['/payment/static/src/xml/payment_processing.xml'],

        _pollCount: 0,

        start: function() {
            this.displayLoading();
            this.poll();
            return this._super.apply(this, arguments);
        },
        /* Methods */
        startPolling: function () {
            var timeout = 3000;
            //
            if(this._pollCount >= 10 && this._pollCount < 20) {
                timeout = 10000;
            }
            else if(this._pollCount >= 20) {
                timeout = 30000;
            }
            //
            setTimeout(this.poll.bind(this), timeout);
            this._pollCount ++;
        },
        poll: function () {
            var self = this;
            ajax.jsonRpc('/payment/process/poll', 'call', {}).then(function(data) {
                if(data.success === true) {
                    self.processPolledData(data.transactions);
                }
                else {
                    switch(data.error) {
                    case "tx_process_retry":
                        break;
                    case "no_tx_found":
                        self.displayContent("payment.no_tx_found", {});
                        break;
                    default: // if an exception is raised
                        self.displayContent("payment.exception", {exception_msg: data.error});
                        break;
                    }
                }
                self.startPolling();

            }).guardedCatch(function() {
                self.displayContent("payment.rpc_error", {});
                self.startPolling();
            });
        },
        processPolledData: function (transactions) {
            var render_values = {
                'tx_draft': [],
                'tx_pending': [],
                'tx_authorized': [],
                'tx_done': [],
                'tx_cancel': [],
                'tx_error': [],
            };

            if (transactions.length > 0 && ['transfer', 'sepa_direct_debit'].indexOf(transactions[0].acquirer_provider) >= 0) {
                window.location = transactions[0].return_url;
                return;
            }

            // group the transaction according to their state
            transactions.forEach(function (tx) {
                var key = 'tx_' + tx.state;
                if(key in render_values) {
                    render_values[key].push(tx);
                }
            });

            function countTxInState(states) {
                var nbTx = 0;
                for (var prop in render_values) {
                    if (states.indexOf(prop) > -1 && render_values.hasOwnProperty(prop)) {
                        nbTx += render_values[prop].length;
                    }
                }
                return nbTx;
            }
            // if there's only one tx to manage
            if(countTxInState(['tx_done', 'tx_error', 'tx_pending', 'tx_authorized']) === 1) {
                var tx = render_values['tx_done'][0] || render_values['tx_authorized'][0] || render_values['tx_error'][0];
                if (tx) {
                    window.location = tx.return_url;
                    return;
                }
            }

            this.displayContent("payment.display_tx_list", render_values);
        },
        displayContent: function (xmlid, render_values) {
            var html = core.qweb.render(xmlid, render_values);
            $.unblockUI();
            this.$el.find('.o_payment_processing_content').html(html);
        },
        displayLoading: function () {
            var msg = _t("We are processing your payment, please wait ...");
            $.blockUI({
                'message': '
' +
                    '
' + msg +
                    '
'
            });
        },
    });
});
<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 Moodle mobile file system lib.
 * @author Juan Leyva
 * @version 1.2
 */

/**
 * @namespace Holds all the MoodleMobile file system functionality.
 */
MM.fs = {

    basePath: '',

    init: function(callBack) {
        MM.log('Loading File System', 'FS');

        // This means that or Cordova or the emulator are not yet loaded, so we must delay this.
        if (typeof(LocalFileSystem) == "undefined") {
            MM.errorLog("LocalFileSystem not defined yet", "FS");
            setTimeout(function() {
                MM.fs.init(callBack);
            }, 5000);
            return;
        }

        if (!callBack) {
            callBack = function() {};
        }

        if (MM.fs.loaded()) {
            // The file system is not yet loaded.
            MM.log('The file system was previously loaded', 'FS');
            callBack();
            return;
        }

        if (MM.getOS() == 'android') {
            MM.fs.basePath = 'Android/data/com.moodle.moodlemobile';
        }
        MM.fs.loadFS(callBack);
    },

    loaded: function() {
        return typeof(MM.fs.fileSystemRoot) != 'undefined';
    },

    getRoot: function() {
        if (!MM.fs.fileSystemRoot) {
            MM.fs.loadFS(function() {
                MM.fs.fileSystemRoot.toNativeURL()
            });
        } else {
            var path = MM.fs.fileSystemRoot.toNativeURL();
            // Android 4.2 and onwards
            path = path.replace("storage/emulated/0", "sdcard");
            return path;
        }
    },

    loadFS: function(callBack) {
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
            function(fileSystem) {
                MM.fs.fileSystemRoot = fileSystem.root;
                if (MM.getOS() == 'android') {
                    MM.fs.fileSystemRoot.getDirectory(
                        MM.fs.basePath, {create: true, exclusive: false},
                        function(entry) {
                            MM.fs.fileSystemRoot = entry;
                            callBack();
                        },
                        function() {
                            MM.popErrorMessage(
                                'Critical error accessing file system, Android/data/app_id does not exists or can be created'
                            );
                        }
                    );
                } else {
                    callBack();
                }
            }, function() {
                MM.popErrorMessage('Critical error accessing file system');
            }
       );
    },

    fileExists: function(path, successCallback, errorCallback) {
        var directory = path.substring(0, filename.lastIndexOf('/') - 1);
        var filename = path.substr(filename.lastIndexOf('/') + 1);
        MM.fs.fileSystemRoot.getDirectory(
            directory,
            { create: false },
            function(entry) {
                entry.getFile(
                    filename, { create: false },
                    function(entryFile) {
                        successCallback(entryFile.toNativeURL());
                    },
                    errorCallback
                );
            },
            errorCallback
        );
    },

    createDir: function(path, successCallback, dirEntry) {
        path = path.replace('file:///', '');
        MM.log('FS: Creating full directory ' + path);

        var paths = path.split('/');

        var baseRoot = MM.fs.fileSystemRoot;
        if (dirEntry) {
            baseRoot = dirEntry;
        }

        MM.log('FS: Creating directory ' + paths[0] + 'in' + baseRoot.toNativeURL());
        baseRoot.getDirectory(
            paths[0],
            {create: true, exclusive: false},
            function(newDirEntry) {
                if (paths.length == 1) {
                    successCallback(newDirEntry);
                    return;
                }
                // Recursively, create next directories
                paths.shift();
                MM.fs.createDir(paths.join('/'), successCallback, newDirEntry);
            },
            function() {
                MM.popErrorMessage('Critical error accessing file system');
            }
        );
    },

    /**
     * Remove recursively a directory
     * @param  {string} path            The relative path of the directory
     * @param  {object} successCallback Success callback function
     * @param  {object} errorCallback   Error callback function
     */
    removeDirectory: function(path, successCallback, errorCallback) {
        MM.log('FS: Removing full directory ' + path);

        var baseRoot = MM.fs.fileSystemRoot;
        baseRoot.getDirectory(
            path,
            {create: false, exclusive: false},
            function(dirEntry) {
                dirEntry.removeRecursively(successCallback, errorCallback);
            },
            errorCallback
        );
    },

    /**
     * Recursive and asynchronous function for calculating the size of a directory.
     * We use several controls global vars in order to know when the algorithm has finished.
     *
     * @param  {string} path                Relative path to the directory
     * @param  {[type]} successCallback     Success Callback
     * @param  {[type]} errorCallback       Error Callback
     */
    directorySize: function(path, successCallback, errorCallback) {
        var baseRoot = MM.fs.fileSystemRoot;
        var fileCounter = 1;    // Are files sizes pending to be retrieved?
        var totalSize = 0;      // Total size of files.
        var running = 0;        // There are async calls pending?
        var directoryReader;

        MM.log('Calculating directory size: ' + path , 'FS');
        var sizeHelper = function(entry) {
            if (entry.isDirectory) {
                fileCounter--;
                running++;
                directoryReader = entry.createReader();
                directoryReader.readEntries(function(entries) {
                    running--;
                    fileCounter += entries.length;
                    var i;
                    for (i=0; i<entries.length; i++) {
                        sizeHelper(entries[i]);
                    }
                }, errorCallback);
            } else if (entry.isFile) {
                entry.file(
                    function(file) {
                        totalSize += file.size;
                        fileCounter--;
                        if (!fileCounter && !running) {
                            MM.log('Directory size for: ' + path + ' is ' + totalSize + ' bytes', 'FS');
                            successCallback(totalSize);
                        }
                    },
                    function() {
                        fileCounter--;
                        if (!fileCounter && !running) {
                            MM.log('Directory size for: ' + path + ' is ' + totalSize + ' bytes', 'FS');
                            successCallback(totalSize);
                        }
                    });
            } else {
                fileCounter--;
            }
        };

        if (baseRoot) {
            baseRoot.getDirectory(
                path,
                {create: false},
                function(entry) {
                    sizeHelper(entry);
                },
                function() {
                    errorCallback();
                }
            );
        } else {
            errorCallback();
        }
    },

    /**
     * This function attemps to calculate the free space in the disck (or sandbox or whatever)
     * Since there is not Phonegap API, what we do is request a file system instance with a minimum size until we get an error
     * We call this function two times for accurate results.
     *
     * @param  {object} callBack        Success callback
     * @param  {object} errorCallback   Error Callback
     * @return {float}                  The estimated free space in bytes
     */
    calculateFreeSpace: function(callBack, errorCallback) {

        var tooMuch = false;
        var tooLessCounter = 0;
        var iterations = 50;

        calculateByRequest = function(size, ratio, iterations, callBack) {
            window.requestFileSystem(LocalFileSystem.PERSISTENT, size,
                function() {

                    tooLessCounter++;

                    if (tooMuch) {
                        callBack(size);
                        return;
                    }
                    // This is to prevent infinite loops.
                    if (tooLessCounter > iterations) {
                        return;
                    }
                    calculateByRequest(size * ratio, ratio, iterations, callBack);
                },
                function() {
                    tooMuch = true;
                    calculateByRequest(size / ratio, ratio, iterations, callBack);
                }
            );
        };

        if (window.requestFileSystem) {
            // General calculation, base 1MB and increasing factor 1.3.
            calculateByRequest(1048576, 1.3, iterations, function(size) {
                tooMuch = false;
                tooLessCounter = 0;
                iterations = 10;
                // More accurate. Factor is 1.1.
                calculateByRequest(size, 1.1, iterations, callBack);
            });
        } else {
            errorCallback();
        }
    }

};<commit_msg>Disable deletion of emulated storage<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 Moodle mobile file system lib.
 * @author Juan Leyva
 * @version 1.2
 */

/**
 * @namespace Holds all the MoodleMobile file system functionality.
 */
MM.fs = {

    basePath: '',

    init: function(callBack) {
        MM.log('Loading File System', 'FS');

        // This means that or Cordova or the emulator are not yet loaded, so we must delay this.
        if (typeof(LocalFileSystem) == "undefined") {
            MM.errorLog("LocalFileSystem not defined yet", "FS");
            setTimeout(function() {
                MM.fs.init(callBack);
            }, 5000);
            return;
        }

        if (!callBack) {
            callBack = function() {};
        }

        if (MM.fs.loaded()) {
            // The file system is not yet loaded.
            MM.log('The file system was previously loaded', 'FS');
            callBack();
            return;
        }

        if (MM.getOS() == 'android') {
            MM.fs.basePath = 'Android/data/com.moodle.moodlemobile';
        }
        MM.fs.loadFS(callBack);
    },

    loaded: function() {
        return typeof(MM.fs.fileSystemRoot) != 'undefined';
    },

    getRoot: function() {
        if (!MM.fs.fileSystemRoot) {
            MM.fs.loadFS(function() {
                MM.fs.fileSystemRoot.toNativeURL()
            });
        } else {
            var path = MM.fs.fileSystemRoot.toNativeURL();
            // Android 4.2 and onwards
            //path = path.replace("storage/emulated/0", "sdcard");
            return path;
        }
    },

    loadFS: function(callBack) {
        window.requestFileSystem(LocalFileSystem.PERSISTENT, 0,
            function(fileSystem) {
                MM.fs.fileSystemRoot = fileSystem.root;
                if (MM.getOS() == 'android') {
                    MM.fs.fileSystemRoot.getDirectory(
                        MM.fs.basePath, {create: true, exclusive: false},
                        function(entry) {
                            MM.fs.fileSystemRoot = entry;
                            callBack();
                        },
                        function() {
                            MM.popErrorMessage(
                                'Critical error accessing file system, Android/data/app_id does not exists or can be created'
                            );
                        }
                    );
                } else {
                    callBack();
                }
            }, function() {
                MM.popErrorMessage('Critical error accessing file system');
            }
       );
    },

    fileExists: function(path, successCallback, errorCallback) {
        var directory = path.substring(0, filename.lastIndexOf('/') - 1);
        var filename = path.substr(filename.lastIndexOf('/') + 1);
        MM.fs.fileSystemRoot.getDirectory(
            directory,
            { create: false },
            function(entry) {
                entry.getFile(
                    filename, { create: false },
                    function(entryFile) {
                        successCallback(entryFile.toNativeURL());
                    },
                    errorCallback
                );
            },
            errorCallback
        );
    },

    createDir: function(path, successCallback, dirEntry) {
        path = path.replace('file:///', '');
        MM.log('FS: Creating full directory ' + path);

        var paths = path.split('/');

        var baseRoot = MM.fs.fileSystemRoot;
        if (dirEntry) {
            baseRoot = dirEntry;
        }

        MM.log('FS: Creating directory ' + paths[0] + 'in' + baseRoot.toNativeURL());
        baseRoot.getDirectory(
            paths[0],
            {create: true, exclusive: false},
            function(newDirEntry) {
                if (paths.length == 1) {
                    successCallback(newDirEntry);
                    return;
                }
                // Recursively, create next directories
                paths.shift();
                MM.fs.createDir(paths.join('/'), successCallback, newDirEntry);
            },
            function() {
                MM.popErrorMessage('Critical error accessing file system');
            }
        );
    },

    /**
     * Remove recursively a directory
     * @param  {string} path            The relative path of the directory
     * @param  {object} successCallback Success callback function
     * @param  {object} errorCallback   Error callback function
     */
    removeDirectory: function(path, successCallback, errorCallback) {
        MM.log('FS: Removing full directory ' + path);

        var baseRoot = MM.fs.fileSystemRoot;
        baseRoot.getDirectory(
            path,
            {create: false, exclusive: false},
            function(dirEntry) {
                dirEntry.removeRecursively(successCallback, errorCallback);
            },
            errorCallback
        );
    },

    /**
     * Recursive and asynchronous function for calculating the size of a directory.
     * We use several controls global vars in order to know when the algorithm has finished.
     *
     * @param  {string} path                Relative path to the directory
     * @param  {[type]} successCallback     Success Callback
     * @param  {[type]} errorCallback       Error Callback
     */
    directorySize: function(path, successCallback, errorCallback) {
        var baseRoot = MM.fs.fileSystemRoot;
        var fileCounter = 1;    // Are files sizes pending to be retrieved?
        var totalSize = 0;      // Total size of files.
        var running = 0;        // There are async calls pending?
        var directoryReader;

        MM.log('Calculating directory size: ' + path , 'FS');
        var sizeHelper = function(entry) {
            if (entry.isDirectory) {
                fileCounter--;
                running++;
                directoryReader = entry.createReader();
                directoryReader.readEntries(function(entries) {
                    running--;
                    fileCounter += entries.length;
                    var i;
                    for (i=0; i<entries.length; i++) {
                        sizeHelper(entries[i]);
                    }
                }, errorCallback);
            } else if (entry.isFile) {
                entry.file(
                    function(file) {
                        totalSize += file.size;
                        fileCounter--;
                        if (!fileCounter && !running) {
                            MM.log('Directory size for: ' + path + ' is ' + totalSize + ' bytes', 'FS');
                            successCallback(totalSize);
                        }
                    },
                    function() {
                        fileCounter--;
                        if (!fileCounter && !running) {
                            MM.log('Directory size for: ' + path + ' is ' + totalSize + ' bytes', 'FS');
                            successCallback(totalSize);
                        }
                    });
            } else {
                fileCounter--;
            }
        };

        if (baseRoot) {
            baseRoot.getDirectory(
                path,
                {create: false},
                function(entry) {
                    sizeHelper(entry);
                },
                function() {
                    errorCallback();
                }
            );
        } else {
            errorCallback();
        }
    },

    /**
     * This function attemps to calculate the free space in the disck (or sandbox or whatever)
     * Since there is not Phonegap API, what we do is request a file system instance with a minimum size until we get an error
     * We call this function two times for accurate results.
     *
     * @param  {object} callBack        Success callback
     * @param  {object} errorCallback   Error Callback
     * @return {float}                  The estimated free space in bytes
     */
    calculateFreeSpace: function(callBack, errorCallback) {

        var tooMuch = false;
        var tooLessCounter = 0;
        var iterations = 50;

        calculateByRequest = function(size, ratio, iterations, callBack) {
            window.requestFileSystem(LocalFileSystem.PERSISTENT, size,
                function() {

                    tooLessCounter++;

                    if (tooMuch) {
                        callBack(size);
                        return;
                    }
                    // This is to prevent infinite loops.
                    if (tooLessCounter > iterations) {
                        return;
                    }
                    calculateByRequest(size * ratio, ratio, iterations, callBack);
                },
                function() {
                    tooMuch = true;
                    calculateByRequest(size / ratio, ratio, iterations, callBack);
                }
            );
        };

        if (window.requestFileSystem) {
            // General calculation, base 1MB and increasing factor 1.3.
            calculateByRequest(1048576, 1.3, iterations, function(size) {
                tooMuch = false;
                tooLessCounter = 0;
                iterations = 10;
                // More accurate. Factor is 1.1.
                calculateByRequest(size, 1.1, iterations, callBack);
            });
        } else {
            errorCallback();
        }
    }

};<|endoftext|>


<commit_before>/*jslint node: true */
'use strict';

module.exports = function (C) {
    describe('API Validator tests reports by ' + C.planName, function () {
        var I,
            assert = require('assert');

        if (C.results) {
            for (I in C.results) {
                 it('[Saved in ' + C.savedFiles[I] + '] ' + C.results[I].input.url + ' should pass schema "' + C.results[I].input.schema + '"', function () {
                     if (C.validates[C.savedFiles[I]]) {
                        assert(false, JSON.stringify(C.validates[C.savedFiles[I]]));
                     }
                 });
            }
        } else {
            it('should executes all steps', function () {
                assert.fail(JSON.stringify(C.error));
            });
        }
    });
};
<commit_msg>fix for report error<commit_after>/*jslint node: true */
'use strict';

var assert = require('assert'),
    createSpec = function (C, I) {
        return function () {
            if (C.validates[C.savedFiles[I]]) {
                assert(false, JSON.stringify(C.validates[C.savedFiles[I]]));
            }
        }
    };

module.exports = function (C) {
    describe('API Validator tests reports by ' + C.planName, function () {
        var I;

        if (C.results) {
            for (I in C.results) {
                 it('[Saved in ' + C.savedFiles[I] + '] ' + C.results[I].input.url + ' should pass schema "' + C.results[I].input.schema + '"', createSpec(C, I));
            }
        } else {
            it('should executes all steps', function () {
                assert.fail(JSON.stringify(C.error));
            });
        }
    });
};
<|endoftext|>


<commit_before>$j.modal = {

	display: function(config) {

		var $this = this;

		// extend config object
		var config = $.extend({
			container:"modal",
		},config);

		// create popup
		new Popup(config);

		// set popup for easy use
		this.popup = $j.popups.modal;

		// display overlay
		$j.canopy.display();

		// hide canopy
		$('#canopy-close-btn').hide();

		// twitter bootstrap classes
		$("#modal").find(".popup-close").addClass('btn btn-large');

		// on popup close, close canopy
		$("#modal").find(".popup-close").click(function($this) {
			$j.canopy.close();
		});

	},

	// close modal
	close: function () {
		$j.canopy.close();
		this.popup.close();
	}

};<commit_msg>Compeess fix for JSC_TRAILING_COMMA: error<commit_after>$j.modal = {

	display: function(config) {

		var $this = this;

		// extend config object
		var config = $.extend({
			container:"modal"
		},config);

		// create popup
		new Popup(config);

		// set popup for easy use
		this.popup = $j.popups.modal;

		// display overlay
		$j.canopy.display();

		// hide canopy
		$('#canopy-close-btn').hide();

		// twitter bootstrap classes
		$("#modal").find(".popup-close").addClass('btn btn-large');

		// on popup close, close canopy
		$("#modal").find(".popup-close").click(function($this) {
			$j.canopy.close();
		});

	},

	// close modal
	close: function () {
		$j.canopy.close();
		this.popup.close();
	}

};<|endoftext|>


<commit_before>'use strict'

let fs = require('fs')
let path = require('path')
let _ = require('lodash')
let assert = require('assert-plus')
let mkdirp = require('mkdirp')
let del = require('del')
let uuid = require('node-uuid')

let imponderous = require('./')

class Model {
  constructor (keyOrData) {
    let args = Array.prototype.slice.call(arguments)

    if (_.isString(keyOrData)) {
      this._key = keyOrData
      args.shift()
    }

    this.data = _.extend({}, this.defaults/*, ...args*/)

    // TODO: Use ES6 spread operator
    _.each(args, (obj) => {
      _.extend(this.data, obj)
    })

    // Setup getters/setters
    _.each(this.constructor.schema, (props, field) => {
      Object.defineProperty(this, field, {
        enumerable: true,
        configurable: true,
        get: function () {
          return this.get(field)
        },
        set: function (v) {
          return this.set(field, v)
        }
      })
    })
  }

  // ----- Document methods -----

  get key () {
    if (this._key) {
      return this._key
    } else {
      // TODO: Consider using uuid.v4 instead
      this._key = uuid.v1()
      return this._key
    }
  }

  set key (newKey) {
    if (this._key) {
      this._oldKey = this._key
    }

    this._key = newKey
  }

  get (field) {
    return this._magicGetterSetter(this.data, field)
  }

  set (field, value) {
    if (_.isString(field) || _.isArray(field)) {
      return this._magicGetterSetter(this.data, field, value)
    }

    // TODO: Use ES6 spread operator
    _.each(arguments, (obj) => {
      _.extend(this.data, obj)
    })
  }

  _magicGetterSetter (obj, is, value) {
    if (typeof is === 'string') {
      return this._magicGetterSetter(obj, is.split('.'), value)
    } else if (is.length === 1 && value !== undefined) {
      obj[is[0]] = value
      return value
    } else if (is.length === 0) {
      return obj
    } else {
      return this._magicGetterSetter(obj[is[0]], is.slice(1), value)
    }
  }

  rm (field) {
    function reduce (obj, is) {
      if (is.length === 1) {
        try {
          delete obj[is[0]]
        } catch (ex) {
          // this means it didn't exist to begin with
        }
      } else if (is.length === 0) {
        return obj
      } else {
        return reduce(obj[is[0]], is.slice(1))
      }
    }

    // covert all fields requested to be deleted to path ararys
    let fields = _.map(arguments, (field) => {
      if (typeof field === 'string') {
        return field.split('.')
      } else {
        return field
      }
    })

    // do the deleting
    _.each(fields, (field) => {
      reduce(this.data, field)
    })

    return this
  }

  isValid (silent) {
    // ensure all requires fields are present
    let required = this.constructor.required
    assert.arrayOfString(required, 'Model.required')
    let missing = _.filter(required, (field) => {
      return _.isUndefined(this.get(field))
    })

    if (missing.length > 0) {
      if (!silent) {
        throw new Error('Missing required fields of: ' + missing.join(', '))
      }

      return false
    }

    // check values of properties
    // TODO

    return true
  }

  toJSON () {
    return this.data
  }

  toObject () {
    return this.data
  }

  toString () {
    return imponderous.formatter.toString(this.data)
  }

  get indexes () {
    return _.pick(this.data, this.constructor.indexed)
  }

  // return location of this document
  get file () {
    assert.string(this.key)
    assert.string(imponderous.formatter.ext)
    let filename = this.key + imponderous.formatter.ext
    let file = path.join(this.constructor.root, filename)
    return file
  }

  // save document to disk
  save () {
    if (arguments.length > 0) {
      this.set.apply(this, arguments)
    }

    let contents = imponderous.formatter.toString(this.data)

    return new Promise((resolve, reject) => {
      // ensure the colleciton directory exists
      mkdirp(this.constructor.root, (err) => {
        if (err) {
          reject(err)
          return
        }

        // delete old document if necessary
        if (this._oldKey) {
          let oldFile = path.join(this.constructor.root, this._oldKey + imponderous.formatter.ext)
          fs.unlinkSync(oldFile)
          // TODO: Make this use async
        }

        // write the document file
        fs.writeFile(this.file, contents, 'utf8', (err) => {
          if (err) {
            return reject(err)
          }

          console.log(this, this.constructor)

          // update the index
          this.constructor.reindex().then(() => {
            resolve(this)
          }, reject)
        })
      })
    })
  }

  // destroy a document, deleting it from disk
  destroy () {
    return new Promise((resolve, reject) => {
      fs.unlink(this.file, function (err) {
        if (err) {
          reject(err)
        } else {
          resolve()
        }
      })
    })
  }

  // load document's data from disk
  _load () {
    return new Promise((resolve, reject) => {
      fs.readFile(this.file, 'utf8', (err, contents) => {
        if (err) {
          reject(err)
        } else {
          try {
            this.data = imponderous.formatter.toObject(contents)
          } catch (ex) {
            reject(ex)
          } finally {
            resolve(this)
          }
        }
      })
    })
  }

  // ----- Model methods -----

  // retrieve a document by its key
  static get (key, load) {
    if (!this.cache) {
      this.cache = {}
    }

    if (typeof this.cache[key] === 'undefined') {
      let doc = new this(key)
      this.cache[key] = doc

      if (load !== false) {
        doc._load()
      }
    }

    return this.cache[key]
  }

  // static destroy (key) {}

  // delete an entire collection
  static destroyAll (collectionName) {
    return del(path.join(this.root, '**'))
  }

  // find documents matching search
  static find (query, options) {
    assert.object(query, 'query')
    assert.ok(_.keys(query).length > 0, 'query')
    options = options || {}

    return new Promise((resolve, reject) => {
      this._loadIndex().catch(reject).then((index) => {
        index = _.clone(index)

        if (_.keys(index).length === 0) {
          return resolve(options.limit === 1 ? null : [])
        }

        let items = {}

        _.each(index, (fields, key) => {
          let matches = _.every(query, (value, field) => {
            return fields[field] === value
          })

          if (matches) {
            items[key] = fields
          }
        })

        if (options.limit === 1) {
          items = items.slice(0, 1)
        }

        let docs = _.map(items, (fields, key) => {
          return this.get(key, false)
        })

        resolve(docs)
      })
    })
  }

  static findOne (params) {
    return this.find(params, { limit: 1 })
  }

  static all (loadAll) {
    return new Promise((resolve, reject) => {
      fs.readdir(this.root, (err, files) => {
        if (err) {
          return reject(err)
        }

        files = _.without(files, 'index.json')

        let docs = {}

        let promises = _.map(files, (filename) => {
          let key = path.basename(filename, imponderous.formatter.ext)
          assert.notEqual(key, filename)
          let doc = new this(key)
          docs[key] = doc
          if (loadAll) {
            return doc._load()
          }
        })

        if (loadAll) {
          Promise.all(promises).then(() => {
            resolve(docs)
          })
        } else {
          resolve(docs)
        }
      })
    })
  }

  // return the pluralized name for the collection
  static get collectionName () {
    return this.name.toLowerCase().trim() + 's'
  }

  // return the directory for this collection
  static get root () {
    return path.join(imponderous.root, this.collectionName)
  }

  // return the absolute path to the index file
  static get indexFile () {
    return path.join(this.root, 'index.json')
  }

  // return the default values for model, based on schema
  static get defaults () {
    return _.mapValues(this.schema, 'default')
  }

  // return the list of fields that are required, based on schema
  static get required () {
    return _.keys(this.schema).filter(field => {
      return this.schema[field].required
    })
  }

  // return the list of fields that should be indexed, based on schema
  static get indexed () {
    return _.keys(this.schema).filter(field => {
      return this.schema[field].index
    })
  }

  static reindex (useCache) {
    useCache = useCache !== false

    // using the cache assumes that the index file has some content,
    // and that it is still accurate, so we only need to update it
    // with the pending changes we know of
    if (useCache && this.cache) {
      // this.cache
      // TODO
    }

    this.all(true).then((docs) => {
      let data = _.mapValues(docs, 'indexes')
      data = imponderous.formatter.toString(data)

      fs.writeFile(this.indexFile, data, 'utf8', function (err) {
        if (err) {
          console.error(err)
        } else {
          console.log('Write index')
        }
      })
    })
  }

  static _loadIndex () {
    return new Promise((resolve, reject) => {
      fs.readFile(this.indexFile, 'utf8', (err, contents) => {
        if (err) {
          // If file does not exist, then no index has been created,
          // that's not necessarily an error
          if (err.code === 'ENOENT' && err.errno === -2) {
            resolve({})
          }

          reject(err)
          return
        }

        try {
          let data = imponderous.formatter.toObject(contents)
          resolve(data)
        } catch (ex) {
          reject(new Error('Invalid JSON formatting for collection index'))
        }
      })
    })
  }
}

module.exports = Model
<commit_msg>Allow a schema to provide a "key" field that will override the default use of UUIDs.<commit_after>'use strict'

let fs = require('fs')
let path = require('path')
let _ = require('lodash')
let assert = require('assert-plus')
let mkdirp = require('mkdirp')
let del = require('del')
let uuid = require('node-uuid')

let imponderous = require('./')

class Model {
  constructor (keyOrData) {
    let args = Array.prototype.slice.call(arguments)

    if (_.isString(keyOrData)) {
      this._key = keyOrData
      args.shift()
    }

    this.data = _.extend({}, this.defaults/*, ...args*/)

    // TODO: Use ES6 spread operator
    _.each(args, (obj) => {
      _.extend(this.data, obj)
    })

    // Setup getters/setters
    _.each(this.constructor.schema, (props, field) => {
      Object.defineProperty(this, field, {
        enumerable: true,
        configurable: true,
        get: function () {
          return this.get(field)
        },
        set: function (v) {
          return this.set(field, v)
        }
      })
    })
  }

  // ----- Document methods -----

  // return the document key
  get key () {
    if (_.isUndefined(this._key)) {
      let keyField = _.keys(this.schema).filter(field => {
        return this.schema[field].key
      })

      assert.ok(keyField.length === 0 || keyField.length === 1, 'Schema fields has multiple "key" properties')

      if (keyField.length > 0) {
        keyField = _.first(keyField)

        this._key = this.get(keyField)
      }

      if (_.isUndefined(this._key)) {
        // TODO: Consider using uuid.v4 instead
        this._key = uuid.v1()
      }
    }

    return this._key
  }

  set key (newKey) {
    if (this._key) {
      this._oldKey = this._key
    }

    this._key = newKey
  }

  get (field) {
    return this._magicGetterSetter(this.data, field)
  }

  set (field, value) {
    if (_.isString(field) || _.isArray(field)) {
      return this._magicGetterSetter(this.data, field, value)
    }

    // TODO: Use ES6 spread operator
    _.each(arguments, (obj) => {
      _.extend(this.data, obj)
    })
  }

  _magicGetterSetter (obj, is, value) {
    if (typeof is === 'string') {
      return this._magicGetterSetter(obj, is.split('.'), value)
    } else if (is.length === 1 && value !== undefined) {
      obj[is[0]] = value
      return value
    } else if (is.length === 0) {
      return obj
    } else {
      return this._magicGetterSetter(obj[is[0]], is.slice(1), value)
    }
  }

  rm (field) {
    function reduce (obj, is) {
      if (is.length === 1) {
        try {
          delete obj[is[0]]
        } catch (ex) {
          // this means it didn't exist to begin with
        }
      } else if (is.length === 0) {
        return obj
      } else {
        return reduce(obj[is[0]], is.slice(1))
      }
    }

    // covert all fields requested to be deleted to path ararys
    let fields = _.map(arguments, (field) => {
      if (typeof field === 'string') {
        return field.split('.')
      } else {
        return field
      }
    })

    // do the deleting
    _.each(fields, (field) => {
      reduce(this.data, field)
    })

    return this
  }

  isValid (silent) {
    // ensure all requires fields are present
    let required = this.constructor.required
    assert.arrayOfString(required, 'Model.required')
    let missing = _.filter(required, (field) => {
      return _.isUndefined(this.get(field))
    })

    if (missing.length > 0) {
      if (!silent) {
        throw new Error('Missing required fields of: ' + missing.join(', '))
      }

      return false
    }

    // check values of properties
    // TODO

    return true
  }

  toJSON () {
    return this.data
  }

  toObject () {
    return this.data
  }

  toString () {
    return imponderous.formatter.toString(this.data)
  }

  get indexes () {
    return _.pick(this.data, this.constructor.indexed)
  }

  // return location of this document
  get file () {
    assert.string(this.key)
    assert.string(imponderous.formatter.ext)
    let filename = this.key + imponderous.formatter.ext
    let file = path.join(this.constructor.root, filename)
    return file
  }

  // save document to disk
  save () {
    if (arguments.length > 0) {
      this.set.apply(this, arguments)
    }

    let contents = imponderous.formatter.toString(this.data)

    return new Promise((resolve, reject) => {
      // ensure the colleciton directory exists
      mkdirp(this.constructor.root, (err) => {
        if (err) {
          reject(err)
          return
        }

        // delete old document if necessary
        if (this._oldKey) {
          let oldFile = path.join(this.constructor.root, this._oldKey + imponderous.formatter.ext)
          fs.unlinkSync(oldFile)
          // TODO: Make this use async
        }

        // write the document file
        fs.writeFile(this.file, contents, 'utf8', (err) => {
          if (err) {
            return reject(err)
          }

          console.log(this, this.constructor)

          // update the index
          this.constructor.reindex().then(() => {
            resolve(this)
          }, reject)
        })
      })
    })
  }

  // destroy a document, deleting it from disk
  destroy () {
    return new Promise((resolve, reject) => {
      fs.unlink(this.file, function (err) {
        if (err) {
          reject(err)
        } else {
          resolve()
        }
      })
    })
  }

  // load document's data from disk
  _load () {
    return new Promise((resolve, reject) => {
      fs.readFile(this.file, 'utf8', (err, contents) => {
        if (err) {
          reject(err)
        } else {
          try {
            this.data = imponderous.formatter.toObject(contents)
          } catch (ex) {
            reject(ex)
          } finally {
            resolve(this)
          }
        }
      })
    })
  }

  // ----- Model methods -----

  // retrieve a document by its key
  static get (key, load) {
    if (!this.cache) {
      this.cache = {}
    }

    if (typeof this.cache[key] === 'undefined') {
      let doc = new this(key)
      this.cache[key] = doc

      if (load !== false) {
        doc._load()
      }
    }

    return this.cache[key]
  }

  // static destroy (key) {}

  // delete an entire collection
  static destroyAll (collectionName) {
    return del(path.join(this.root, '**'))
  }

  // find documents matching search
  static find (query, options) {
    assert.object(query, 'query')
    assert.ok(_.keys(query).length > 0, 'query')
    options = options || {}

    return new Promise((resolve, reject) => {
      this._loadIndex().catch(reject).then((index) => {
        index = _.clone(index)

        if (_.keys(index).length === 0) {
          return resolve(options.limit === 1 ? null : [])
        }

        let items = {}

        _.each(index, (fields, key) => {
          let matches = _.every(query, (value, field) => {
            return fields[field] === value
          })

          if (matches) {
            items[key] = fields
          }
        })

        if (options.limit === 1) {
          items = items.slice(0, 1)
        }

        let docs = _.map(items, (fields, key) => {
          return this.get(key, false)
        })

        resolve(docs)
      })
    })
  }

  static findOne (params) {
    return this.find(params, { limit: 1 })
  }

  static all (loadAll) {
    return new Promise((resolve, reject) => {
      fs.readdir(this.root, (err, files) => {
        if (err) {
          return reject(err)
        }

        files = _.without(files, 'index.json')

        let docs = {}

        let promises = _.map(files, (filename) => {
          let key = path.basename(filename, imponderous.formatter.ext)
          assert.notEqual(key, filename)
          let doc = new this(key)
          docs[key] = doc
          if (loadAll) {
            return doc._load()
          }
        })

        if (loadAll) {
          Promise.all(promises).then(() => {
            resolve(docs)
          })
        } else {
          resolve(docs)
        }
      })
    })
  }

  // return the pluralized name for the collection
  static get collectionName () {
    return this.name.toLowerCase().trim() + 's'
  }

  // return the directory for this collection
  static get root () {
    return path.join(imponderous.root, this.collectionName)
  }

  // return the absolute path to the index file
  static get indexFile () {
    return path.join(this.root, 'index.json')
  }

  // return the default values for model, based on schema
  static get defaults () {
    return _.mapValues(this.schema, 'default')
  }

  // return the list of fields that are required, based on schema
  static get required () {
    return _.keys(this.schema).filter(field => {
      return this.schema[field].required
    })
  }

  // return the list of fields that should be indexed, based on schema
  static get indexed () {
    return _.keys(this.schema).filter(field => {
      return this.schema[field].index
    })
  }

  static reindex (useCache) {
    useCache = useCache !== false

    // using the cache assumes that the index file has some content,
    // and that it is still accurate, so we only need to update it
    // with the pending changes we know of
    if (useCache && this.cache) {
      // this.cache
      // TODO
    }

    this.all(true).then((docs) => {
      let data = _.mapValues(docs, 'indexes')
      data = imponderous.formatter.toString(data)

      fs.writeFile(this.indexFile, data, 'utf8', function (err) {
        if (err) {
          console.error(err)
        } else {
          console.log('Write index')
        }
      })
    })
  }

  static _loadIndex () {
    return new Promise((resolve, reject) => {
      fs.readFile(this.indexFile, 'utf8', (err, contents) => {
        if (err) {
          // If file does not exist, then no index has been created,
          // that's not necessarily an error
          if (err.code === 'ENOENT' && err.errno === -2) {
            resolve({})
          }

          reject(err)
          return
        }

        try {
          let data = imponderous.formatter.toObject(contents)
          resolve(data)
        } catch (ex) {
          reject(new Error('Invalid JSON formatting for collection index'))
        }
      })
    })
  }
}

module.exports = Model
<|endoftext|>