<commit_before>"use strict";
/**
 * Copyright (c) 2013 Bruno Jouhier <bruno.jouhier@sage.com>
 * MIT License
 */
var util = require('../util');
var glob = util.getGlobals('generators');

var counters = {
	slowAwait: 0,
	fastAwait: 0,
};

function makeArgs(i) {
	if (i <= 0) return "";
	return i > 1 ? makeArgs(i - 1) + ', a' + i : "a1";
}

if (typeof glob.yielded === "undefined") glob.yielded = true;
glob.PENDING = glob.PENDING || {};

function isGenerator(val) {
	return val && (
	Object.prototype.toString.call(val) === "[object Generator]" || val.toString() === "[object Generator]");
}

function Frame(file, line, fn) {
	this.fn = fn;
	this.file = file || "unknown";
	this.line = line || 0;
}

Object.defineProperty(Frame.prototype, "name", {
	get: function() {
		var fn = this.fn;
		return (fn && (fn.__name__ || fn.name)) || "unknown";
	}
});

Object.defineProperty(Frame.prototype, "info", {
	get: function() {
		return this;
	}
});

function pushFrame(g) {
	if (glob.emitter) glob.emitter.emit('enter', g.frame);
	g.frame = g.frame || glob.frame;
	glob.frame = null;
}

function popFrame(g) {
	if (glob.emitter) glob.emitter.emit('exit', g.frame);
}

function run(fn, g, cb, options) {
	var rsm = glob.resume;
	var emit = function(ev, g) {
			if (glob.emitter) glob.emitter.emit(ev, g.frame);
		}

	try {
		if (glob.frame) {
			g.frame = glob.frame;
			g.frame.g = g;
			glob.frame = null;
		} else {
			g.frame = new Frame("", 0, fn);
		}

		glob.resume = function(err, val) {
			if (glob.yielded) {
				emit("resume", g);
				glob.yielded = false;
			}
			while (g) {
				if (options && options.interrupt && options.interrupt()) return;
				try {
					// ES6 is deprecating send in favor of next. Following line makes us compatible with both.
					var send = g.send || g.next;
					var v = err ? g.
					throw (err) : send.call(g, val);
					val = v.value;
					err = null;
					// if we get PENDING, the current call completed with a pending I/O
					// resume will be called again when the I/O completes. So just save the context and return here.
					if (val === glob.PENDING) {
						if (!glob.yielded) {
							emit("yield", g);
							glob.yielded = true;
						}
						return;
					}
					// if we get [PENDING, e, r], the current call invoked its callback synchronously
					// we just loop to send/throw what the callback gave us.
					if (val && val[0] === glob.PENDING) {
						err = val[1];
						val = val[2];
						if (err) err = wrapError(err, g);
					}
					// else, if g is done we unwind it we send val to the parent generator (or through cb if we are at the top)
					else if (v.done) {
						//g.close();
						popFrame(g);
						g = g.prev;
					}
					// else if val is not a generator we have an error. Yield was not applied to a generators
					else {
						if (!isGenerator(val)) {
							throw new Error("invalid value was yielded. Expected a generator, got " + val);
						}
						// we got a new generator which means that g called another generator function
						// the new generator become current and we loop with g.send(undefined) (equiv to g.next())
						val.prev = g;
						g = val;
						pushFrame(g);
						val = undefined;
					}
				} catch (ex) {
					// the send/throw call failed.
					// we unwind the current generator and we rethrow into the parent generator (or through cb if at the top)
					//g.close();
					err = wrapError(ex, g);
					popFrame(g);
					g = g.prev;
					val = undefined;
				}
			}
			// we have exhausted the stack of generators.
			// return the result or error through the callback.
			cb(err, val);
		}

		// start the resume loop
		glob.resume();
	} finally {
		// restore resume global
		glob.resume = rsm;
	}
}

function mapResults(options, args) {
	if (options && typeof options === "object") {
		if (options.returnArray) return args;
		if (options.returnObject) return options.returnObject.reduce(function(res, key, i) {
			res[key] = args[i];
			return res;
		}, {});
	}
	return args[0];
}

function invoke(that, fn, args, index, index2, returnArray) {
	// Set things up so that call returns:
	// * PENDING if it completes with a pending I/O (and cb will be called later)
	// * [PENDING, e, r] if the callback is called synchronously.
	var result = glob.PENDING,
		sync = true;
	var rsm = glob.resume;

	// convert args to array so that args.length gets correctly set if index is args.length
	args = Array.prototype.slice.call(args, 0);
	var cx = glob.context;
	var callback = function(e, r) {
			var oldContext = glob.context;
			var oldResume = glob.resume;
			try {
				if (returnArray) r = Array.prototype.slice.call(arguments, 1);
				glob.context = cx;
				glob.resume = rsm;
				if (sync) {
					result = [glob.PENDING, e, r];
				} else {
					glob.resume(e, r);
				}
			} finally {
				glob.context = oldContext;
				glob.resume = oldResume;
			}
		}
	if (index2 != null) {
		args[index] = function(r) {
			callback(null, r);
		}
		args[index2] = function(e) {
			callback(e);
		}
	} else {
		args[index] = callback;
	}
	fn.apply(that, args);
	sync = false;
	return result;
}

var star = function(fn, index, index2, returnArray) {
	return function *() {
		return (yield invoke(this, fn, arguments, index, index2, returnArray));
	};
}

var unstarTemplate = function(fn, options) {
		var index = (options && typeof options === 'object') ? options.callbackIndex : options;
		if (index == null) index = fn.length;

		var F = function F() {
			var cb = arguments[index];
			if (typeof cb !== "function") {
				if (glob.allowBooleanPlaceholders && typeof cb === 'boolean') {
					if (cb) cb = util.defaultCallback;
					else return exports.future("", 0, null, wrapper.bind(this), index)(arguments);
				}
				else throw util.argError(fn.name, index, "function", typeof cb);
			}
			var g = fn.apply(this, arguments);
			run.call(this, fn, g, cb);
		};
		// track the original name for stack frames
		F.__name__ = fn.name;
		return F;
	}

var unstarBody = unstarTemplate.toString();
unstarBody = unstarBody.substring(unstarBody.indexOf('{'));
var unstarrors = [];

function makeUnstarror(i) {
	return eval("(function(fn, options)" + unstarBody.replace(/function\s*F\(\)/, "function F(" + makeArgs(i) + ")") + ")");
}

function unstar(fn, index, arity) {
	var i = arity != null ? arity : (index == null ? fn.length + 1 : fn.length);
	var unstarror = unstarrors[i] || (unstarrors[i] = makeUnstarror(i));
	return unstarror(fn, index);
}

function wrapError(err, g) {
	if (!(err instanceof Error)) return err; // handle throw "some string";
	if (err.g) return err;
	err = Object.create(err);
	err.g = g;
	Object.defineProperty(err, 'stack', {
		get: function() {
			return stackTrace(this);
		}
	});
	return err;
}

function stackTrace(err) {
	var extra;
	var starredStack = "";
	var g;
	while (g = err.g) {
		for (g = g.prev; g; g = g.prev) {
			if (!g.frame) continue;
			var fname = (g.prev && g.prev.frame && g.prev.frame.name) || "";
			var m = /\$\$(.*)\$\$/.exec(fname);
			if (m) fname = m[1];
			starredStack += '    at ' + fname + ' (' + g.frame.file + ':' + g.frame.line + ')\n';
		}
		err = Object.getPrototypeOf(err);
	}
	var rawStack = Object.getOwnPropertyDescriptor(new Error(), 'stack').get.call(err);
	var cut = rawStack.indexOf('    at GeneratorFunctionPrototype');
	if (cut < 0) cut = rawStack.indexOf('\n') + 1;
	var result = rawStack.substring(0, cut).replace(/\n.*regenerator.runtime.*/g, '') + //
	'    <<< yield stack >>>\n' + starredStack + //
	'    <<< raw stack >>>\n' + rawStack.substring(cut);
	return result;
}

exports.await = function(file, line, object, property, index1, index2, returnArray) {
	var bound = typeof property !== "function";
	var fn = bound ? object[property] : property;
	glob.frame = new Frame(file, line, fn);
	var key = '';
	if (index2 == null && !returnArray) {
		key = 'awaitWrapper-' + index1;
		var wrapper = fn[key];
		if (wrapper) {
			counters.fastAwait++;
			return bound ? wrapper.bind(object) : wrapper;
		}
	}
	counters.slowAwait++;
	if (typeof fn !== "function") throw util.typeError("cannot call", "function", fn);
	wrapper = star(fn, index1, index2, returnArray);
	if (!bound && key) {
		fn[key] = wrapper;
	}
	return bound ? wrapper.bind(object) : wrapper;
};

exports.async = function(fn, index, arity) {
	if (typeof fn !== "function") throw util.typeError("cannot wrap function", "function", fn);
	var unstarred = unstar(fn, index, arity);
	unstarred["awaitWrapper-" + index] = fn;
	return unstarred;
}

exports.new = function(file, line, constructor, index) {
	if (typeof constructor !== "function") throw util.typeError("cannot instantiate", "function", constructor);
	glob.frame = new Frame(file, line, constructor);
	var starred = star(constructor, index);
	return function *() {
		var that = Object.create(constructor.prototype);
		yield starred.apply(that, arguments);
		return that;
	};
};

exports.future = require('../future');
require('./builtins');<commit_msg>improved generators stack traces<commit_after>"use strict";
/**
 * Copyright (c) 2013 Bruno Jouhier <bruno.jouhier@sage.com>
 * MIT License
 */
var util = require('../util');
var glob = util.getGlobals('generators');

var counters = {
	slowAwait: 0,
	fastAwait: 0,
};

function makeArgs(i) {
	if (i <= 0) return "";
	return i > 1 ? makeArgs(i - 1) + ', a' + i : "a1";
}

if (typeof glob.yielded === "undefined") glob.yielded = true;
glob.PENDING = glob.PENDING || {};

function isGenerator(val) {
	return val && (
	Object.prototype.toString.call(val) === "[object Generator]" || val.toString() === "[object Generator]");
}

function Frame(file, line, fn) {
	this.fn = fn;
	this.file = file || "unknown";
	this.line = line || 0;
}

Object.defineProperty(Frame.prototype, "name", {
	get: function() {
		var fn = this.fn;
		return (fn && (fn.__name__ || fn.name)) || "unknown";
	}
});

Object.defineProperty(Frame.prototype, "info", {
	get: function() {
		return this;
	}
});

function pushFrame(g) {
	if (glob.emitter) glob.emitter.emit('enter', g.frame);
	g.frame = g.frame || glob.frame;
	glob.frame = null;
}

function popFrame(g) {
	if (glob.emitter) glob.emitter.emit('exit', g.frame);
}

function run(fn, g, cb, options) {
	var rsm = glob.resume;
	var emit = function(ev, g) {
			if (glob.emitter) glob.emitter.emit(ev, g.frame);
		}

	try {
		if (glob.frame) {
			g.frame = glob.frame;
			g.frame.g = g;
			glob.frame = null;
		} else {
			g.frame = new Frame("", 0, fn);
		}

		glob.resume = function(err, val) {
			if (glob.yielded) {
				emit("resume", g);
				glob.yielded = false;
			}
			while (g) {
				if (options && options.interrupt && options.interrupt()) return;
				try {
					// ES6 is deprecating send in favor of next. Following line makes us compatible with both.
					var send = g.send || g.next;
					var v = err ? g.
					throw (err) : send.call(g, val);
					val = v.value;
					err = null;
					// if we get PENDING, the current call completed with a pending I/O
					// resume will be called again when the I/O completes. So just save the context and return here.
					if (val === glob.PENDING) {
						if (!glob.yielded) {
							emit("yield", g);
							glob.yielded = true;
						}
						return;
					}
					// if we get [PENDING, e, r], the current call invoked its callback synchronously
					// we just loop to send/throw what the callback gave us.
					if (val && val[0] === glob.PENDING) {
						err = val[1];
						val = val[2];
						if (err) err = wrapError(err, g);
					}
					// else, if g is done we unwind it we send val to the parent generator (or through cb if we are at the top)
					else if (v.done) {
						//g.close();
						popFrame(g);
						g = g.prev;
					}
					// else if val is not a generator we have an error. Yield was not applied to a generators
					else {
						if (!isGenerator(val)) {
							throw new Error("invalid value was yielded. Expected a generator, got " + val);
						}
						// we got a new generator which means that g called another generator function
						// the new generator become current and we loop with g.send(undefined) (equiv to g.next())
						val.prev = g;
						g = val;
						pushFrame(g);
						val = undefined;
					}
				} catch (ex) {
					// the send/throw call failed.
					// we unwind the current generator and we rethrow into the parent generator (or through cb if at the top)
					//g.close();
					err = wrapError(ex, g);
					popFrame(g);
					g = g.prev;
					val = undefined;
				}
			}
			// we have exhausted the stack of generators.
			// return the result or error through the callback.
			cb(err, val);
		}

		// start the resume loop
		glob.resume();
	} finally {
		// restore resume global
		glob.resume = rsm;
	}
}

function mapResults(options, args) {
	if (options && typeof options === "object") {
		if (options.returnArray) return args;
		if (options.returnObject) return options.returnObject.reduce(function(res, key, i) {
			res[key] = args[i];
			return res;
		}, {});
	}
	return args[0];
}

function invoke(that, fn, args, index, index2, returnArray) {
	// Set things up so that call returns:
	// * PENDING if it completes with a pending I/O (and cb will be called later)
	// * [PENDING, e, r] if the callback is called synchronously.
	var result = glob.PENDING,
		sync = true;
	var rsm = glob.resume;

	// convert args to array so that args.length gets correctly set if index is args.length
	args = Array.prototype.slice.call(args, 0);
	var cx = glob.context;
	var callback = function(e, r) {
			var oldContext = glob.context;
			var oldResume = glob.resume;
			try {
				if (returnArray) r = Array.prototype.slice.call(arguments, 1);
				glob.context = cx;
				glob.resume = rsm;
				if (sync) {
					result = [glob.PENDING, e, r];
				} else {
					glob.resume(e, r);
				}
			} finally {
				glob.context = oldContext;
				glob.resume = oldResume;
			}
		}
	if (index2 != null) {
		args[index] = function(r) {
			callback(null, r);
		}
		args[index2] = function(e) {
			callback(e);
		}
	} else {
		args[index] = callback;
	}
	fn.apply(that, args);
	sync = false;
	return result;
}

var star = function(fn, index, index2, returnArray) {
	return function *() {
		return (yield invoke(this, fn, arguments, index, index2, returnArray));
	};
}

var unstarTemplate = function(fn, options) {
		var index = (options && typeof options === 'object') ? options.callbackIndex : options;
		if (index == null) index = fn.length;

		var F = function F() {
			var cb = arguments[index];
			if (typeof cb !== "function") {
				if (glob.allowBooleanPlaceholders && typeof cb === 'boolean') {
					if (cb) cb = util.defaultCallback;
					else return exports.future("", 0, null, wrapper.bind(this), index)(arguments);
				}
				else throw util.argError(fn.name, index, "function", typeof cb);
			}
			var g = fn.apply(this, arguments);
			run.call(this, fn, g, cb);
		};
		// track the original name for stack frames
		F.__name__ = fn.name;
		return F;
	}

var unstarBody = unstarTemplate.toString();
unstarBody = unstarBody.substring(unstarBody.indexOf('{'));
var unstarrors = [];

function makeUnstarror(i) {
	return eval("(function(fn, options)" + unstarBody.replace(/function\s*F\(\)/, "function F(" + makeArgs(i) + ")") + ")");
}

function unstar(fn, index, arity) {
	var i = arity != null ? arity : (index == null ? fn.length + 1 : fn.length);
	var unstarror = unstarrors[i] || (unstarrors[i] = makeUnstarror(i));
	return unstarror(fn, index);
}

function wrapError(err, g) {
	if (!(err instanceof Error)) return err; // handle throw "some string";
	if (err.g) return err;
	err = Object.create(err);
	err.g = g;
	Object.defineProperty(err, 'stack', {
		get: function() {
			return stackTrace(this);
		}
	});
	return err;
}

function stackTrace(err) {
	var extra;
	var starredStack = "";
	var g;
	while (g = err.g) {
		for (; g.prev; g = g.prev) {
			var fname = g.prev.frame.name || "";
			var m = /\$\$(.*)\$\$/.exec(fname);
			if (m) fname = m[1];
			starredStack += '    at ' + fname + ' (' + g.frame.file + ':' + g.frame.line + ')\n';
		}
		err = Object.getPrototypeOf(err);
	}
	var rawStack = Object.getOwnPropertyDescriptor(new Error(), 'stack').get.call(err);
	var cut = rawStack.indexOf('    at GeneratorFunctionPrototype');
	if (cut < 0) cut = rawStack.indexOf('\n') + 1;
	var result = rawStack.substring(0, cut).replace(/\n.*regenerator.runtime.*/g, '') + //
	'    <<< yield stack >>>\n' + starredStack + //
	'    <<< raw stack >>>\n' + rawStack.substring(cut);
	return result;
}

exports.await = function(file, line, object, property, index1, index2, returnArray) {
	var bound = typeof property !== "function";
	var fn = bound ? object[property] : property;
	glob.frame = new Frame(file, line, fn);
	var key = '';
	if (index2 == null && !returnArray) {
		key = 'awaitWrapper-' + index1;
		var wrapper = fn[key];
		if (wrapper) {
			counters.fastAwait++;
			return bound ? wrapper.bind(object) : wrapper;
		}
	}
	counters.slowAwait++;
	if (typeof fn !== "function") throw util.typeError("cannot call", "function", fn);
	wrapper = star(fn, index1, index2, returnArray);
	if (!bound && key) {
		fn[key] = wrapper;
	}
	return bound ? wrapper.bind(object) : wrapper;
};

exports.async = function(fn, index, arity) {
	if (typeof fn !== "function") throw util.typeError("cannot wrap function", "function", fn);
	var unstarred = unstar(fn, index, arity);
	unstarred["awaitWrapper-" + index] = fn;
	return unstarred;
}

exports.new = function(file, line, constructor, index) {
	if (typeof constructor !== "function") throw util.typeError("cannot instantiate", "function", constructor);
	glob.frame = new Frame(file, line, constructor);
	var starred = star(constructor, index);
	return function *() {
		var that = Object.create(constructor.prototype);
		yield starred.apply(that, arguments);
		return that;
	};
};

exports.future = require('../future');
require('./builtins');<|endoftext|>


<commit_before><commit_msg>added quick sanity check<commit_after>var assert = require("assert")
describe('Array', function(){
  describe('#indexOf()', function(){
    it('should return -1 when the value is not present', function(){
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
    })
  })
})
<|endoftext|>


<commit_before>'use strict';

/* FIXME: problems with modules (exactly function 'require')
 * that's why we need to include files in header
 * and pray for God they will be in right order
*/

//const units = require('./units');
//const Point = require('./utils').Point;

var globalPig;
var globalWolf, globalWolf2, globalWolf3;
var globalWolves = [];
var globalField = {};
var globalPoints = 0;

function checkKey(e) {
  e = e || window.event;
  let dir = keyToDirection(e.keyCode);
  if (dir.direction != '')
    globalPig.tryMove(dir);
}

window.onload = function () {
  globalField = new Field(10, 10);
  document.getElementById('mainDiv').appendChild(globalField.table);

  globalField.changeCell(Point(5, 5), 'grass', [{'itemName':'wall'}]);
  globalField.changeCell(Point(5, 6), 'grass', [{'itemName':'wall'}]);
  globalField.changeCell(Point(6, 6), 'grass', [{'itemName':'wall'}]);
  globalField.changeCell(Point(7, 9), 'grass', [{'itemName':'wall'}]);

  globalField.changeCell(Point(8, 3), 'grass', [{'itemName':'food'}]);
  globalField.changeCell(Point(3, 8), 'grass', [{'itemName':'food'}]);
  globalField.changeCell(Point(2, 3), 'grass', [{'itemName':'food'}]);

  globalField.changeCell(Point(0, 7), 'ground', [{'itemName':'wall'}]);
  globalField.changeCell(Point(1, 7), 'grass', [{'itemName':'door'}, {'itemName':'food'}]);
  globalField.changeCell(Point(2, 7), 'ground', [{'itemName':'wall'}]);
  globalField.changeCell(Point(2, 8), 'ground', [{'itemName':'wall'}]);
  globalField.changeCell(Point(2, 9), 'ground', [{'itemName':'wall'}]);

  globalField.changeCell(Point(0, 8), 'wood');
  globalField.changeCell(Point(0, 9), 'wood');
  globalField.changeCell(Point(1, 8), 'wood');
  globalField.changeCell(Point(1, 9), 'wood');

  globalField.changeCell(Point(0, 9), 'wood', [{'itemName':'food'}]);
  let curDoor = globalField.pointToCell(Point(1, 7));
  globalField.changeCell(Point(9, 0), 'grass', [{'itemName':'button', 'cellDoor':curDoor}]);

  globalPig = new Pig(Point(1, 1));
  globalWolf = new Wolf(Point(2, 2), [Point(2, 2), Point(2, 3), Point (2, 4), Point(1, 4)]);
  globalWolf2 = new Wolf(Point(0, 0), [Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0)]);
  globalWolf3 = new Wolf(Point(8, 4), [Point(8, 2), Point(8, 3), Point(8, 4), Point(8, 5),
    Point(7, 5), Point(6, 5), Point(6, 6), Point(6, 7)]);

  globalWolves = [globalWolf, globalWolf2, globalWolf3];
  for (let i = 0; i < globalWolves.length; ++i)
    globalWolves[i].addTrajectoryLayerToField(globalField);

  initialDraw();

  let curLevel = new Level(globalField, globalPig, globalWolves);
  alert('before saving');
  curLevel.saveToFile('temp.txt');

  document.onkeydown = checkKey;
}
<commit_msg>Something I forgot from previous commit<commit_after>'use strict';

/* FIXME: problems with modules (exactly function 'require')
 * that's why we need to include files in header
 * and pray for God they will be in right order
*/

//const units = require('./units');
//const Point = require('./utils').Point;

var globalPig;
var globalWolf, globalWolf2, globalWolf3;
var globalWolves = [];
var globalField = {};
var globalPoints = 0;

function checkKey(e) {
  e = e || window.event;
  let dir = keyToDirection(e.keyCode);
  if (dir.direction != '')
    globalPig.tryMove(dir);
}

window.onload = function () {
  globalField = new Field(10, 10);
  document.getElementById('mainDiv').appendChild(globalField.table);

  globalField.changeCell(Point(5, 5), 'grass', [{'itemName':'wall'}]);
  globalField.changeCell(Point(5, 6), 'grass', [{'itemName':'wall'}]);
  globalField.changeCell(Point(6, 6), 'grass', [{'itemName':'wall'}]);
  globalField.changeCell(Point(7, 9), 'grass', [{'itemName':'wall'}]);

  globalField.changeCell(Point(8, 3), 'grass', [{'itemName':'food'}]);
  globalField.changeCell(Point(3, 8), 'grass', [{'itemName':'food'}]);
  globalField.changeCell(Point(2, 3), 'grass', [{'itemName':'food'}]);

  globalField.changeCell(Point(0, 7), 'ground', [{'itemName':'wall'}]);
  globalField.changeCell(Point(1, 7), 'grass', [{'itemName':'door'}, {'itemName':'food'}]);
  globalField.changeCell(Point(2, 7), 'ground', [{'itemName':'wall'}]);
  globalField.changeCell(Point(2, 8), 'ground', [{'itemName':'wall'}]);
  globalField.changeCell(Point(2, 9), 'ground', [{'itemName':'wall'}]);

  globalField.changeCell(Point(0, 8), 'wood');
  globalField.changeCell(Point(0, 9), 'wood');
  globalField.changeCell(Point(1, 8), 'wood');
  globalField.changeCell(Point(1, 9), 'wood');

  globalField.changeCell(Point(0, 9), 'wood', [{'itemName':'food'}]);
  let curDoor = globalField.pointToCell(Point(1, 7));
  globalField.changeCell(Point(9, 0), 'grass', [{'itemName':'button', 'cellDoor':curDoor}]);

  globalPig = new Pig(Point(1, 1));
  globalWolf = new Wolf(Point(2, 2), [Point(2, 2), Point(2, 3), Point (2, 4), Point(1, 4)]);
  globalWolf2 = new Wolf(Point(0, 0), [Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0)]);
  globalWolf3 = new Wolf(Point(8, 4), [Point(8, 2), Point(8, 3), Point(8, 4), Point(8, 5),
    Point(7, 5), Point(6, 5), Point(6, 6), Point(6, 7)]);

  globalWolves = [globalWolf, globalWolf2, globalWolf3];
  for (let i = 0; i < globalWolves.length; ++i)
    globalWolves[i].addTrajectoryLayerToField(globalField);

  initialDraw();

  let curLevel = new Level(globalField, globalPig, globalWolves);
  alert('before saving');
  curLevel.saveToFile('temp.txt');

  document.onkeydown = checkKey;
}
<|endoftext|>


<commit_before>"use strict";

var fs = require('fs')
  , path = require('path')
  , _ = require('underscore')
  , logger = require('../server/logger.js').get('appium')
  , helpers = require('../helpers.js')
  , isWindows = require('appium-support').system.isWindows
  , copyLocalZip = helpers.copyLocalZip
  , unzipApp = helpers.unzipApp
  , downloadFile = helpers.downloadFile
  , capConversion = require('../server/capabilities.js').capabilityConversions
  , DeviceSettings = require('./device-settings.js')
  , url = require('url');

var Device = function () {
  throw new Error("Cannot instantiate Device directly");
};

Device.prototype.init = function () {
  this.appExt = null;
  this.tempFiles = [];
  this.args = {};
  this.capabilities = {};
  this.settings = new DeviceSettings();
};

Device.prototype.configure = function (args, caps) {
  _.extend(this.args, args);
  _.extend(this.capabilities, caps);
  _.each(caps, function (val, cap) {
    this.setArgFromCap(cap, cap);
  }.bind(this));
  if (this.args.tmpDir === null) {
    // use a custom tmp dir to avoid loosing data and app
    // when computer is restarted
    this.args.tmpDir = process.env.APPIUM_TMP_DIR ||
      (isWindows() ? process.env.TEMP : "/tmp");
  }
  if (this.args.traceDir === null) {
    this.args.traceDir = path.resolve(this.args.tmpDir , 'appium-instruments');
  }
};

Device.prototype.setArgFromCap = function (arg, cap) {
  if (typeof this.capabilities[cap] !== "undefined") {
    if (_.has(capConversion, cap)) {
      var key = capConversion[cap];
      if (!_.has(this.capabilities, key)) {
        // if we have both 'version' and 'platformVersion' caps being sent,
        // make sure 'platformVersion' isn't overwritten by 'version'
        this.args[key] = this.capabilities[cap];
      }
    } else {
      this.args[arg] = this.capabilities[cap];
    }
  }
};

Device.prototype.appString = function () {
  return this.args.app ? this.args.app.toString() : '';
};

Device.prototype.configureApp = function (cb) {
  if (this.args.app.substring(0, 4).toLowerCase() === "http") {
    this.configureDownloadedApp(cb);
  } else {
    this.configureLocalApp(cb);
  }
};

Device.prototype.configureLocalApp = function (cb) {
  this.args.app = path.resolve(this.args.app);
  var appPath = this.args.app;
  var origin = this.capabilities.app ? "desired caps" : "command line";
  var ext = appPath.substring(appPath.length - 4).toLowerCase();
  if (ext === this.appExt) {
    this.args.app = appPath;
    logger.debug("Using local app from " + origin + ": " + appPath);
    fs.stat(appPath, function (err) {
      if (err) {
        return cb(new Error("Error locating the app: " + err.message));
      }
      cb();
    });
  } else if (ext === ".zip" || ext === ".ipa") {
    logger.debug("Using local " + ext + " from " + origin + ": " + appPath);
    this.unzipLocalApp(appPath, function (zipErr, newAppPath) {
      if (zipErr) return cb(zipErr);
      if (ext === ".ipa") {
        this.args.ipa = this.args.app;
      }
      this.args.app = newAppPath;
      logger.debug("Using locally extracted app: " + this.args.app);
      cb();
    }.bind(this));
  } else {
    var msg = "Using local app, but didn't end in .zip, .ipa or " + this.appExt;
    logger.error(msg);
    cb(new Error(msg));
  }
};

Device.prototype.appIsPackageOrBundle = function (app) {
  return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app);
};

Device.prototype.configureDownloadedApp = function (cb) {
  var origin = this.capabilities.app ? "desired caps" : "command line";
  var appUrl;
  try {
    appUrl = url.parse(this.args.app);
  } catch (err) {
    cb("Invalid App URL (" + this.args.app + ")");
  }
  var ext = path.extname(appUrl.pathname);
  if (ext === ".apk") {
    try {
      downloadFile(url.format(appUrl), ".apk", function (appPath) {
        this.tempFiles.push(appPath);
        this.args.app = appPath;
        cb();
      }.bind(this));
    } catch (e) {
      var err = e.toString();
      logger.error("Failed downloading app from appUrl " + appUrl);
      cb(err);
    }
  } else if (ext === ".zip" || ext === ".ipa") {
    try {
      this.downloadAndUnzipApp(url.format(appUrl), function (zipErr, appPath) {
        if (zipErr) {
          cb(zipErr);
        } else {
          this.args.app = appPath;
          logger.debug("Using extracted app: " + this.args.app);
          cb();
        }
      }.bind(this));
      logger.debug("Using downloadable app from " + origin + ": " + appUrl);
    } catch (e) {
      var err = e.toString();
      logger.error("Failed downloading app from appUrl " + appUrl);
      cb(err);
    }
  } else {
    cb("App URL (" + this.args.app + ") didn't seem to point to a .zip, " +
       ".apk, or .ipa file");
  }
};

Device.prototype.unzipLocalApp = function (localZipPath, cb) {
  try {
    copyLocalZip(localZipPath, function (err, zipPath) {
      if (err) return cb(err);
      this.unzipApp(zipPath, cb);
    }.bind(this));
  } catch (e) {
    logger.error("Failed copying and unzipping local app: " + localZipPath);
    cb(e);
  }
};

Device.prototype.unzipApp = function (zipPath, cb) {
  this.tempFiles.push(zipPath);
  unzipApp(zipPath, this.appExt, function (err, appPath) {
    if (err) {
      cb(err, null);
    } else {
      this.tempFiles.push(appPath);
      cb(null, appPath);
    }
  }.bind(this));
};

Device.prototype.downloadAndUnzipApp = function (appUrl, cb) {
  downloadFile(appUrl, ".zip", function (zipPath) {
    this.unzipApp(zipPath, cb);
  }.bind(this));
};

// get a specific setting
Device.prototype.getSetting = function (str) {
  return this.settings._settings[str];
};

module.exports = Device;
<commit_msg>Fix reporting of url in logs<commit_after>"use strict";

var fs = require('fs')
  , path = require('path')
  , _ = require('underscore')
  , logger = require('../server/logger.js').get('appium')
  , helpers = require('../helpers.js')
  , isWindows = require('appium-support').system.isWindows
  , copyLocalZip = helpers.copyLocalZip
  , unzipApp = helpers.unzipApp
  , downloadFile = helpers.downloadFile
  , capConversion = require('../server/capabilities.js').capabilityConversions
  , DeviceSettings = require('./device-settings.js')
  , url = require('url');

var Device = function () {
  throw new Error("Cannot instantiate Device directly");
};

Device.prototype.init = function () {
  this.appExt = null;
  this.tempFiles = [];
  this.args = {};
  this.capabilities = {};
  this.settings = new DeviceSettings();
};

Device.prototype.configure = function (args, caps) {
  _.extend(this.args, args);
  _.extend(this.capabilities, caps);
  _.each(caps, function (val, cap) {
    this.setArgFromCap(cap, cap);
  }.bind(this));
  if (this.args.tmpDir === null) {
    // use a custom tmp dir to avoid loosing data and app
    // when computer is restarted
    this.args.tmpDir = process.env.APPIUM_TMP_DIR ||
      (isWindows() ? process.env.TEMP : "/tmp");
  }
  if (this.args.traceDir === null) {
    this.args.traceDir = path.resolve(this.args.tmpDir , 'appium-instruments');
  }
};

Device.prototype.setArgFromCap = function (arg, cap) {
  if (typeof this.capabilities[cap] !== "undefined") {
    if (_.has(capConversion, cap)) {
      var key = capConversion[cap];
      if (!_.has(this.capabilities, key)) {
        // if we have both 'version' and 'platformVersion' caps being sent,
        // make sure 'platformVersion' isn't overwritten by 'version'
        this.args[key] = this.capabilities[cap];
      }
    } else {
      this.args[arg] = this.capabilities[cap];
    }
  }
};

Device.prototype.appString = function () {
  return this.args.app ? this.args.app.toString() : '';
};

Device.prototype.configureApp = function (cb) {
  if (this.args.app.substring(0, 4).toLowerCase() === "http") {
    this.configureDownloadedApp(cb);
  } else {
    this.configureLocalApp(cb);
  }
};

Device.prototype.configureLocalApp = function (cb) {
  this.args.app = path.resolve(this.args.app);
  var appPath = this.args.app;
  var origin = this.capabilities.app ? "desired caps" : "command line";
  var ext = appPath.substring(appPath.length - 4).toLowerCase();
  if (ext === this.appExt) {
    this.args.app = appPath;
    logger.debug("Using local app from " + origin + ": " + appPath);
    fs.stat(appPath, function (err) {
      if (err) {
        return cb(new Error("Error locating the app: " + err.message));
      }
      cb();
    });
  } else if (ext === ".zip" || ext === ".ipa") {
    logger.debug("Using local " + ext + " from " + origin + ": " + appPath);
    this.unzipLocalApp(appPath, function (zipErr, newAppPath) {
      if (zipErr) return cb(zipErr);
      if (ext === ".ipa") {
        this.args.ipa = this.args.app;
      }
      this.args.app = newAppPath;
      logger.debug("Using locally extracted app: " + this.args.app);
      cb();
    }.bind(this));
  } else {
    var msg = "Using local app, but didn't end in .zip, .ipa or " + this.appExt;
    logger.error(msg);
    cb(new Error(msg));
  }
};

Device.prototype.appIsPackageOrBundle = function (app) {
  return (/^([a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+)+$/).test(app);
};

Device.prototype.configureDownloadedApp = function (cb) {
  var origin = this.capabilities.app ? "desired caps" : "command line";
  var appUrl;
  try {
    appUrl = url.parse(this.args.app);
  } catch (err) {
    cb("Invalid App URL (" + this.args.app + ")");
  }
  var ext = path.extname(appUrl.pathname);
  if (ext === ".apk") {
    try {
      downloadFile(url.format(appUrl), ".apk", function (appPath) {
        this.tempFiles.push(appPath);
        this.args.app = appPath;
        cb();
      }.bind(this));
    } catch (e) {
      var err = e.toString();
      logger.error("Failed downloading app from appUrl " + appUrl.href);
      cb(err);
    }
  } else if (ext === ".zip" || ext === ".ipa") {
    try {
      this.downloadAndUnzipApp(url.format(appUrl), function (zipErr, appPath) {
        if (zipErr) {
          cb(zipErr);
        } else {
          this.args.app = appPath;
          logger.debug("Using extracted app: " + this.args.app);
          cb();
        }
      }.bind(this));
      logger.debug("Using downloadable app from " + origin + ": " + appUrl.href);
    } catch (e) {
      var err = e.toString();
      logger.error("Failed downloading app from appUrl " + appUrl.href);
      cb(err);
    }
  } else {
    cb("App URL (" + this.args.app + ") didn't seem to point to a .zip, " +
       ".apk, or .ipa file");
  }
};

Device.prototype.unzipLocalApp = function (localZipPath, cb) {
  try {
    copyLocalZip(localZipPath, function (err, zipPath) {
      if (err) return cb(err);
      this.unzipApp(zipPath, cb);
    }.bind(this));
  } catch (e) {
    logger.error("Failed copying and unzipping local app: " + localZipPath);
    cb(e);
  }
};

Device.prototype.unzipApp = function (zipPath, cb) {
  this.tempFiles.push(zipPath);
  unzipApp(zipPath, this.appExt, function (err, appPath) {
    if (err) {
      cb(err, null);
    } else {
      this.tempFiles.push(appPath);
      cb(null, appPath);
    }
  }.bind(this));
};

Device.prototype.downloadAndUnzipApp = function (appUrl, cb) {
  downloadFile(appUrl, ".zip", function (zipPath) {
    this.unzipApp(zipPath, cb);
  }.bind(this));
};

// get a specific setting
Device.prototype.getSetting = function (str) {
  return this.settings._settings[str];
};

module.exports = Device;
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

/* eslint-disable no-await-in-loop */

import DeviceInfo from 'react-native-device-info';
import { Client as BugsnagClient } from 'bugsnag-react-native';

import {
  incrementSyncProgress,
  setSyncProgress,
  setSyncProgressMessage,
  setSyncTotal,
  setSyncError,
  setSyncIsSyncing,
  setSyncCompletionTime,
} from '../actions';
import { syncStrings } from '../localization';
import { integrateRecord } from './incomingSyncUtils';
import { generateSyncJson } from './outgoingSyncUtils';
import { SyncDatabase } from './SyncDatabase';
import { SyncQueue } from './SyncQueue';
import { SETTINGS_KEYS } from '../settings';

import { version as mobileVersion } from '../../package.json';

const bugsnagClient = new BugsnagClient();

const {
  SYNC_IS_INITIALISED,
  SYNC_PRIOR_FAILED,
  SYNC_SITE_NAME,
  SYNC_SERVER_ID,
  SYNC_SITE_ID,
  SYNC_URL,
} = SETTINGS_KEYS;

const MIN_SYNC_BATCH_SIZE = 10;
const MAX_SYNC_BATCH_SIZE = 500;
const OPTIMAL_BATCH_SPEED = 5;

/**
 * Provides core synchronization functionality, initilising the database with an
 * initial full sync, pushing, and pulling (any regular scheduling of synchronization
 * must be coordinated externally).
 *
 * @param  {Realm}              database       The local database.
 * @param  {SyncAuthenticator}  authenticator  Provides authentication with the sync server.
 * @param  {Settings}           settings       Access to locally stored settings.
 */
export class Synchroniser {
  constructor(database, authenticator, settings, dispatch) {
    this.database = new SyncDatabase(database);
    this.authenticator = authenticator;
    this.settings = settings;
    this.syncQueue = new SyncQueue(this.database);
    this.dispatch = dispatch;
    this.extraHeaders = {
      'msupply-site-uuid': DeviceInfo.getUniqueId(),
      'mobile-version': mobileVersion,
    };
    console.log(this.extraHeaders);
    this.refreshSyncParams();
    if (this.isInitialised()) this.syncQueue.enable();
  }

  /**
   * Redux progress setting functions.
   */
  setTotal = totalCount => this.dispatch(setSyncTotal(totalCount));

  incrementProgress = increment => this.dispatch(incrementSyncProgress(increment));

  setProgress = currentCount => this.dispatch(setSyncProgress(currentCount));

  setProgressMessage = message => this.dispatch(setSyncProgressMessage(message));

  setError = errorMessage => this.dispatch(setSyncError(errorMessage));

  setIsSyncing = isSyncing => this.dispatch(setSyncIsSyncing(isSyncing));

  setCompletionTime = time => this.dispatch(setSyncCompletionTime(time));

  refreshSyncParams = () => {
    this.serverURL = this.settings.get(SYNC_URL);
    this.thisSiteId = this.settings.get(SYNC_SITE_ID);
    this.serverId = this.settings.get(SYNC_SERVER_ID);
    this.thisSiteName = this.settings.get(SYNC_SITE_NAME);
    this.authHeader = this.authenticator.getAuthHeader();
    this.batchSize = MIN_SYNC_BATCH_SIZE;
  };

  fetchWrapper = async (url, opts) => {
    const { body, headers } = opts;

    const bugsnagNotify = async message => {
      const responseText = await responseClone.text();
      bugsnagClient.notify(new Error(message), report => {
        report.context = 'SYNC ERROR';
        report.metadata = { error: { url, headers, body, responseText } };
      });
    };

    const response = await fetch(url, opts);
    const responseClone = response.clone();

    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while attempting to sync.');
    }
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (error) {
      bugsnagNotify('Failed parsing response from server', response);
      throw new Error('Unexpected response from sync server');
    }

    if (responseJson.error) {
      if (responseJson.error.startsWith("Site registration doesn't match.")) {
        throw new Error(responseJson.error);
      } else {
        bugsnagNotify(responseJson.error, response);
        throw new Error('Server rejected pushed records');
      }
    }
    return { responseJson };
  };

  /**
   * Wipe the current database, check that the given URL can be synced against
   * using the given name and password, and if so populate the database by pulling
   * from the remote server.
   *
   * @param   {string}   serverURL         The URL to be synced against.
   * @param   {string}   syncSiteName      The username to use in sync auth.
   * @param   {string}   syncSitePassword  The password to use in sync auth.
   * @return  {Promise}                    Resolve if successfully authenticated and
   *                                       initialised, otherwise throw error.
   */
  initialise = async (serverURL, syncSiteName, syncSitePassword) => {
    this.setIsSyncing(true);
    this.setProgressMessage(syncStrings.initialising);
    this.syncQueue.disable(); // Stop sync queue listening to database changes.

    // Check if the |serverURL| passed in is the same as that which has been used during
    // initialisation. If this is the case, a failed partial initialisation is being continued.
    // If the |serverURL| is different, it is either completely fresh, or the URL has been
    // changed. In either case, initialisation should be started afresh. The same applies to
    // |syncSiteName|, so that it isn't possible to start syncing data from a site different
    // to what initialisation was previously started with.

    const oldSyncURL = this.serverURL;
    const oldSyncSiteName = this.thisSiteName;
    const isFresh =
      !oldSyncURL || serverURL !== oldSyncURL || !syncSiteName || syncSiteName !== oldSyncSiteName;

    if (isFresh) this.database.write(() => this.database.deleteAll());

    try {
      await this.authenticator.authenticate(serverURL, syncSiteName, syncSitePassword);
      this.refreshSyncParams(); // Authenticate sets all the sync settings in database, so refresh.

      if (isFresh) {
        // If a fresh initialisation, send request to server to prepare required sync records.
        await this.fetchWrapper(
          `${this.serverURL}/sync/v3/initial_dump/` +
            `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
          {
            headers: {
              Authorization: this.authHeader,
              ...this.extraHeaders,
            },
          }
        );
        // If the initial_dump has been successful, serverURL is valid, and should now have all sync
        // records queued and ready to send. Safe to store as this.serverURL
        this.serverURL = serverURL;
      }
      await this.pull();
    } catch (error) {
      // Did not authenticate, sync error or no internet, bubble up error.
      this.setError(error.message);
      this.setIsSyncing(false);
      throw error;
    }

    this.settings.set(SYNC_IS_INITIALISED, 'true');
    this.syncQueue.enable(); // Begin the sync queue listening to database changes.
    this.setIsSyncing(false);
    this.setCompletionTime(new Date().getTime());
  };

  /**
   * Return whether the synchroniser has been initialised.
   *
   * @return  {boolean}  True if initial sync has been completed successfully.
   */
  isInitialised = () => {
    const syncIsInitialised = this.settings.get(SYNC_IS_INITIALISED);
    return syncIsInitialised && syncIsInitialised === 'true';
  };

  /**
   * Return whether or not the last sync of the app failed.
   *
   * @return  {boolean}  True if the last call of synchronise failed.
   */
  lastSyncFailed = () => {
    const lastSyncFailed = this.settings.get(SYNC_PRIOR_FAILED);
    return lastSyncFailed && lastSyncFailed === 'true';
  };

  /**
   * Carry out a synchronisation, first pushing any local changes, then pulling
   * down remote changes and integrating them into the local database.
   *
   * @return  {none}
   */
  synchronise = async () => {
    try {
      if (!this.isInitialised()) throw new Error('Not yet initialised');
      this.setIsSyncing(true);
      this.refreshSyncParams();

      // Keeps track between app close/open whether last sync was successful.
      this.settings.set(SYNC_PRIOR_FAILED, 'true');

      // Using async/await here causes any errors thrown by push or pull will be caught by the
      // outer try/catch.
      await this.push();
      await this.pull();

      // Store persistent sync details in settings.
      this.settings.set(SYNC_PRIOR_FAILED, 'false');

      // Store sync completion progress in redux.
      this.setIsSyncing(false);
      this.setCompletionTime(new Date().getTime());
    } catch (error) {
      this.setError(error.message);
      this.setIsSyncing(false);
    }
  };

  /**
   * Push batches of changes to the local database up to the remote server, until
   * all local changes have been synced.
   *
   * @return  {Promise}  Resolves if successful, or bubbles up any errors thrown.
   */
  push = async () => {
    this.setProgressMessage(syncStrings.pushing_changes);
    this.setProgress(0);
    this.setTotal(this.syncQueue.length);
    while (this.syncQueue.length > 0) {
      const batchComplete = this.batchSizeAdjustor();
      const recordsToSync = this.syncQueue.next(this.batchSize);

      const translatedRecords = recordsToSync
        .map(this.translateRecord) // Get translated records.
        .filter(record => !!record); // If error thrown, may be null so filter falsy values out.

      await this.pushRecords(translatedRecords);
      // Records that threw errors in |translateRecord()| are still removed.
      this.syncQueue.use(recordsToSync);
      this.incrementProgress(recordsToSync.length);
      batchComplete();
    }
  };

  /**
   * Handles translating records for |push()| and catching errors.
   *
   * @param   {object}  record  The outgoing record to be translated.
   * @return  {object}          The translated record output.
   */
  translateRecord = record => {
    try {
      // Try translate the record.
      return generateSyncJson(this.database, this.settings, record);
    } catch (error) {
      // If error not safe to continue sync, bubble up to next error handler.
      if (!error.canDeleteSyncOut) throw error; // See outgoingSyncUtils.js.
      return null;
    }
  };

  /**
   * Pushes a collection of records to the remote sync server.
   *
   * @param   {array}   records  The records to push.
   * @return  {Promise}          Resolves if successful, or passes up any error thrown.
   */
  pushRecords = async records => {
    await this.fetchWrapper(
      `${this.serverURL}/sync/v3/queued_records/` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
          ...this.extraHeaders,
        },
        body: JSON.stringify(records),
      }
    );
  };

  /**
   * Pulls any changes to data on the sync server down to the local database.
   *
   * @return  {Promise}  Resolves if successful, or bubbles up any errors thrown.
   */
  pull = async () => {
    let waitingRecordCount = await this.getWaitingRecordCount();
    let total = waitingRecordCount;
    let progress = 0;

    this.setProgressMessage(syncStrings.pulling_changes);
    this.setProgress(progress);
    this.setTotal(total);

    // Pull |this.batchSize| records at a time from server.
    while (progress < total) {
      const batchComplete = this.batchSizeAdjustor();
      // Get a batch of records and integrate them.
      const incomingRecords = await this.getIncomingRecords();
      this.integrateRecords(incomingRecords);
      await this.acknowledgeRecords(incomingRecords);
      // Break out if incoming records is actually empty (unexpected behaviour from server API).
      // Ensure no infinite loops.
      if (incomingRecords.length <= 0) break;
      progress += incomingRecords.length;

      // Check no new records added to incoming queue on last iteration.
      if (progress >= total) {
        waitingRecordCount = await this.getWaitingRecordCount();
        if (waitingRecordCount > 0) {
          total = progress + waitingRecordCount;
          this.setTotal(total);
        }
      }

      this.incrementProgress(incomingRecords.length);
      batchComplete();
    }
  };

  /**
   * Returns the number of records left to pull.
   *
   * @return  {Promise}  Resolves with the record count, or bubbles up any errors thrown.
   */
  getWaitingRecordCount = async () => {
    const { responseJson } = await this.fetchWrapper(
      `${this.serverURL}/sync/v3/queued_records/count` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        headers: {
          Authorization: this.authHeader,
          ...this.extraHeaders,
        },
      }
    );
    return responseJson.NumRecords;
  };

  /**
   * Returns the next batch of incoming sync records.
   *
   * @return  {Promise}  Resolves with the records, or bubbles up any errors thrown.
   */
  getIncomingRecords = async () => {
    const { responseJson } = await this.fetchWrapper(
      `${this.serverURL}/sync/v3/queued_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}&limit=${this.batchSize}`,
      {
        headers: {
          Authorization: this.authHeader,
          ...this.extraHeaders,
        },
      }
    );
    return responseJson;
  };

  /**
   * Parse the batch of incoming records, and integrate them into the local database.
   *
   * @param   {object}  syncJson  The json object the server sent to represent records.
   * @return  {none}
   */
  integrateRecords(syncJson) {
    this.database.write(() => {
      syncJson.forEach(syncRecord => {
        integrateRecord(this.database, this.settings, syncRecord);
      });
    });
  }

  /**
   * Sends the sync server a message to indicate the sync records have been consumed.
   *
   * @param   {array}  records  Sync records that have been integrated.
   * @return  {none}
   */
  acknowledgeRecords = async records => {
    const syncIds = records.map(record => record.SyncID);

    const requestBody = {
      SyncRecordIDs: syncIds,
    };

    await this.fetchWrapper(
      `${this.serverURL}/sync/v3/acknowledged_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
          ...this.extraHeaders,
        },
        body: JSON.stringify(requestBody),
      }
    );
  };

  /**
   * Closure for handling adjusting the batch size that sync uses.
   *
   * @return  {function}  A closure function that when called ends timing and adjusts batch size.
   */
  batchSizeAdjustor = () => {
    const start = Date.now();
    return () => {
      const timeTaken = (Date.now() - start) / 1000;
      const durationPerRecord = timeTaken / this.batchSize;
      const optimalBatchSize = OPTIMAL_BATCH_SPEED / durationPerRecord;
      let newBatchSize = optimalBatchSize;

      newBatchSize = Math.floor(newBatchSize);
      newBatchSize = Math.max(newBatchSize, MIN_SYNC_BATCH_SIZE);
      newBatchSize = Math.min(newBatchSize, MAX_SYNC_BATCH_SIZE);

      this.batchSize = newBatchSize;
    };
  };
}

export default Synchroniser;
<commit_msg>Remove sychroniser debug logging<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

/* eslint-disable no-await-in-loop */

import DeviceInfo from 'react-native-device-info';
import { Client as BugsnagClient } from 'bugsnag-react-native';

import {
  incrementSyncProgress,
  setSyncProgress,
  setSyncProgressMessage,
  setSyncTotal,
  setSyncError,
  setSyncIsSyncing,
  setSyncCompletionTime,
} from '../actions';
import { syncStrings } from '../localization';
import { integrateRecord } from './incomingSyncUtils';
import { generateSyncJson } from './outgoingSyncUtils';
import { SyncDatabase } from './SyncDatabase';
import { SyncQueue } from './SyncQueue';
import { SETTINGS_KEYS } from '../settings';

import { version as mobileVersion } from '../../package.json';

const bugsnagClient = new BugsnagClient();

const {
  SYNC_IS_INITIALISED,
  SYNC_PRIOR_FAILED,
  SYNC_SITE_NAME,
  SYNC_SERVER_ID,
  SYNC_SITE_ID,
  SYNC_URL,
} = SETTINGS_KEYS;

const MIN_SYNC_BATCH_SIZE = 10;
const MAX_SYNC_BATCH_SIZE = 500;
const OPTIMAL_BATCH_SPEED = 5;

/**
 * Provides core synchronization functionality, initilising the database with an
 * initial full sync, pushing, and pulling (any regular scheduling of synchronization
 * must be coordinated externally).
 *
 * @param  {Realm}              database       The local database.
 * @param  {SyncAuthenticator}  authenticator  Provides authentication with the sync server.
 * @param  {Settings}           settings       Access to locally stored settings.
 */
export class Synchroniser {
  constructor(database, authenticator, settings, dispatch) {
    this.database = new SyncDatabase(database);
    this.authenticator = authenticator;
    this.settings = settings;
    this.syncQueue = new SyncQueue(this.database);
    this.dispatch = dispatch;
    this.extraHeaders = {
      'msupply-site-uuid': DeviceInfo.getUniqueId(),
      'mobile-version': mobileVersion,
    };
    this.refreshSyncParams();
    if (this.isInitialised()) this.syncQueue.enable();
  }

  /**
   * Redux progress setting functions.
   */
  setTotal = totalCount => this.dispatch(setSyncTotal(totalCount));

  incrementProgress = increment => this.dispatch(incrementSyncProgress(increment));

  setProgress = currentCount => this.dispatch(setSyncProgress(currentCount));

  setProgressMessage = message => this.dispatch(setSyncProgressMessage(message));

  setError = errorMessage => this.dispatch(setSyncError(errorMessage));

  setIsSyncing = isSyncing => this.dispatch(setSyncIsSyncing(isSyncing));

  setCompletionTime = time => this.dispatch(setSyncCompletionTime(time));

  refreshSyncParams = () => {
    this.serverURL = this.settings.get(SYNC_URL);
    this.thisSiteId = this.settings.get(SYNC_SITE_ID);
    this.serverId = this.settings.get(SYNC_SERVER_ID);
    this.thisSiteName = this.settings.get(SYNC_SITE_NAME);
    this.authHeader = this.authenticator.getAuthHeader();
    this.batchSize = MIN_SYNC_BATCH_SIZE;
  };

  fetchWrapper = async (url, opts) => {
    const { body, headers } = opts;

    const bugsnagNotify = async message => {
      const responseText = await responseClone.text();
      bugsnagClient.notify(new Error(message), report => {
        report.context = 'SYNC ERROR';
        report.metadata = { error: { url, headers, body, responseText } };
      });
    };

    const response = await fetch(url, opts);
    const responseClone = response.clone();

    if (response.status < 200 || response.status >= 300) {
      throw new Error('Connection failure while attempting to sync.');
    }
    let responseJson;
    try {
      responseJson = await response.json();
    } catch (error) {
      bugsnagNotify('Failed parsing response from server', response);
      throw new Error('Unexpected response from sync server');
    }

    if (responseJson.error) {
      if (responseJson.error.startsWith("Site registration doesn't match.")) {
        throw new Error(responseJson.error);
      } else {
        bugsnagNotify(responseJson.error, response);
        throw new Error('Server rejected pushed records');
      }
    }
    return { responseJson };
  };

  /**
   * Wipe the current database, check that the given URL can be synced against
   * using the given name and password, and if so populate the database by pulling
   * from the remote server.
   *
   * @param   {string}   serverURL         The URL to be synced against.
   * @param   {string}   syncSiteName      The username to use in sync auth.
   * @param   {string}   syncSitePassword  The password to use in sync auth.
   * @return  {Promise}                    Resolve if successfully authenticated and
   *                                       initialised, otherwise throw error.
   */
  initialise = async (serverURL, syncSiteName, syncSitePassword) => {
    this.setIsSyncing(true);
    this.setProgressMessage(syncStrings.initialising);
    this.syncQueue.disable(); // Stop sync queue listening to database changes.

    // Check if the |serverURL| passed in is the same as that which has been used during
    // initialisation. If this is the case, a failed partial initialisation is being continued.
    // If the |serverURL| is different, it is either completely fresh, or the URL has been
    // changed. In either case, initialisation should be started afresh. The same applies to
    // |syncSiteName|, so that it isn't possible to start syncing data from a site different
    // to what initialisation was previously started with.

    const oldSyncURL = this.serverURL;
    const oldSyncSiteName = this.thisSiteName;
    const isFresh =
      !oldSyncURL || serverURL !== oldSyncURL || !syncSiteName || syncSiteName !== oldSyncSiteName;

    if (isFresh) this.database.write(() => this.database.deleteAll());

    try {
      await this.authenticator.authenticate(serverURL, syncSiteName, syncSitePassword);
      this.refreshSyncParams(); // Authenticate sets all the sync settings in database, so refresh.

      if (isFresh) {
        // If a fresh initialisation, send request to server to prepare required sync records.
        await this.fetchWrapper(
          `${this.serverURL}/sync/v3/initial_dump/` +
            `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
          {
            headers: {
              Authorization: this.authHeader,
              ...this.extraHeaders,
            },
          }
        );
        // If the initial_dump has been successful, serverURL is valid, and should now have all sync
        // records queued and ready to send. Safe to store as this.serverURL
        this.serverURL = serverURL;
      }
      await this.pull();
    } catch (error) {
      // Did not authenticate, sync error or no internet, bubble up error.
      this.setError(error.message);
      this.setIsSyncing(false);
      throw error;
    }

    this.settings.set(SYNC_IS_INITIALISED, 'true');
    this.syncQueue.enable(); // Begin the sync queue listening to database changes.
    this.setIsSyncing(false);
    this.setCompletionTime(new Date().getTime());
  };

  /**
   * Return whether the synchroniser has been initialised.
   *
   * @return  {boolean}  True if initial sync has been completed successfully.
   */
  isInitialised = () => {
    const syncIsInitialised = this.settings.get(SYNC_IS_INITIALISED);
    return syncIsInitialised && syncIsInitialised === 'true';
  };

  /**
   * Return whether or not the last sync of the app failed.
   *
   * @return  {boolean}  True if the last call of synchronise failed.
   */
  lastSyncFailed = () => {
    const lastSyncFailed = this.settings.get(SYNC_PRIOR_FAILED);
    return lastSyncFailed && lastSyncFailed === 'true';
  };

  /**
   * Carry out a synchronisation, first pushing any local changes, then pulling
   * down remote changes and integrating them into the local database.
   *
   * @return  {none}
   */
  synchronise = async () => {
    try {
      if (!this.isInitialised()) throw new Error('Not yet initialised');
      this.setIsSyncing(true);
      this.refreshSyncParams();

      // Keeps track between app close/open whether last sync was successful.
      this.settings.set(SYNC_PRIOR_FAILED, 'true');

      // Using async/await here causes any errors thrown by push or pull will be caught by the
      // outer try/catch.
      await this.push();
      await this.pull();

      // Store persistent sync details in settings.
      this.settings.set(SYNC_PRIOR_FAILED, 'false');

      // Store sync completion progress in redux.
      this.setIsSyncing(false);
      this.setCompletionTime(new Date().getTime());
    } catch (error) {
      this.setError(error.message);
      this.setIsSyncing(false);
    }
  };

  /**
   * Push batches of changes to the local database up to the remote server, until
   * all local changes have been synced.
   *
   * @return  {Promise}  Resolves if successful, or bubbles up any errors thrown.
   */
  push = async () => {
    this.setProgressMessage(syncStrings.pushing_changes);
    this.setProgress(0);
    this.setTotal(this.syncQueue.length);
    while (this.syncQueue.length > 0) {
      const batchComplete = this.batchSizeAdjustor();
      const recordsToSync = this.syncQueue.next(this.batchSize);

      const translatedRecords = recordsToSync
        .map(this.translateRecord) // Get translated records.
        .filter(record => !!record); // If error thrown, may be null so filter falsy values out.

      await this.pushRecords(translatedRecords);
      // Records that threw errors in |translateRecord()| are still removed.
      this.syncQueue.use(recordsToSync);
      this.incrementProgress(recordsToSync.length);
      batchComplete();
    }
  };

  /**
   * Handles translating records for |push()| and catching errors.
   *
   * @param   {object}  record  The outgoing record to be translated.
   * @return  {object}          The translated record output.
   */
  translateRecord = record => {
    try {
      // Try translate the record.
      return generateSyncJson(this.database, this.settings, record);
    } catch (error) {
      // If error not safe to continue sync, bubble up to next error handler.
      if (!error.canDeleteSyncOut) throw error; // See outgoingSyncUtils.js.
      return null;
    }
  };

  /**
   * Pushes a collection of records to the remote sync server.
   *
   * @param   {array}   records  The records to push.
   * @return  {Promise}          Resolves if successful, or passes up any error thrown.
   */
  pushRecords = async records => {
    await this.fetchWrapper(
      `${this.serverURL}/sync/v3/queued_records/` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
          ...this.extraHeaders,
        },
        body: JSON.stringify(records),
      }
    );
  };

  /**
   * Pulls any changes to data on the sync server down to the local database.
   *
   * @return  {Promise}  Resolves if successful, or bubbles up any errors thrown.
   */
  pull = async () => {
    let waitingRecordCount = await this.getWaitingRecordCount();
    let total = waitingRecordCount;
    let progress = 0;

    this.setProgressMessage(syncStrings.pulling_changes);
    this.setProgress(progress);
    this.setTotal(total);

    // Pull |this.batchSize| records at a time from server.
    while (progress < total) {
      const batchComplete = this.batchSizeAdjustor();
      // Get a batch of records and integrate them.
      const incomingRecords = await this.getIncomingRecords();
      this.integrateRecords(incomingRecords);
      await this.acknowledgeRecords(incomingRecords);
      // Break out if incoming records is actually empty (unexpected behaviour from server API).
      // Ensure no infinite loops.
      if (incomingRecords.length <= 0) break;
      progress += incomingRecords.length;

      // Check no new records added to incoming queue on last iteration.
      if (progress >= total) {
        waitingRecordCount = await this.getWaitingRecordCount();
        if (waitingRecordCount > 0) {
          total = progress + waitingRecordCount;
          this.setTotal(total);
        }
      }

      this.incrementProgress(incomingRecords.length);
      batchComplete();
    }
  };

  /**
   * Returns the number of records left to pull.
   *
   * @return  {Promise}  Resolves with the record count, or bubbles up any errors thrown.
   */
  getWaitingRecordCount = async () => {
    const { responseJson } = await this.fetchWrapper(
      `${this.serverURL}/sync/v3/queued_records/count` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        headers: {
          Authorization: this.authHeader,
          ...this.extraHeaders,
        },
      }
    );
    return responseJson.NumRecords;
  };

  /**
   * Returns the next batch of incoming sync records.
   *
   * @return  {Promise}  Resolves with the records, or bubbles up any errors thrown.
   */
  getIncomingRecords = async () => {
    const { responseJson } = await this.fetchWrapper(
      `${this.serverURL}/sync/v3/queued_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}&limit=${this.batchSize}`,
      {
        headers: {
          Authorization: this.authHeader,
          ...this.extraHeaders,
        },
      }
    );
    return responseJson;
  };

  /**
   * Parse the batch of incoming records, and integrate them into the local database.
   *
   * @param   {object}  syncJson  The json object the server sent to represent records.
   * @return  {none}
   */
  integrateRecords(syncJson) {
    this.database.write(() => {
      syncJson.forEach(syncRecord => {
        integrateRecord(this.database, this.settings, syncRecord);
      });
    });
  }

  /**
   * Sends the sync server a message to indicate the sync records have been consumed.
   *
   * @param   {array}  records  Sync records that have been integrated.
   * @return  {none}
   */
  acknowledgeRecords = async records => {
    const syncIds = records.map(record => record.SyncID);

    const requestBody = {
      SyncRecordIDs: syncIds,
    };

    await this.fetchWrapper(
      `${this.serverURL}/sync/v3/acknowledged_records` +
        `?from_site=${this.thisSiteId}&to_site=${this.serverId}`,
      {
        method: 'POST',
        headers: {
          Authorization: this.authHeader,
          ...this.extraHeaders,
        },
        body: JSON.stringify(requestBody),
      }
    );
  };

  /**
   * Closure for handling adjusting the batch size that sync uses.
   *
   * @return  {function}  A closure function that when called ends timing and adjusts batch size.
   */
  batchSizeAdjustor = () => {
    const start = Date.now();
    return () => {
      const timeTaken = (Date.now() - start) / 1000;
      const durationPerRecord = timeTaken / this.batchSize;
      const optimalBatchSize = OPTIMAL_BATCH_SPEED / durationPerRecord;
      let newBatchSize = optimalBatchSize;

      newBatchSize = Math.floor(newBatchSize);
      newBatchSize = Math.max(newBatchSize, MIN_SYNC_BATCH_SIZE);
      newBatchSize = Math.min(newBatchSize, MAX_SYNC_BATCH_SIZE);

      this.batchSize = newBatchSize;
    };
  };
}

export default Synchroniser;
<|endoftext|>


<commit_before>'use strict';

const inherits = require('util').inherits;
const MongoError = require('mongodb-core').MongoError;
const Readable = require('stream').Readable;
const CoreCursor = require('./cursor');

/**
 * @fileOverview The **AggregationCursor** class is an internal class that embodies an aggregation cursor on MongoDB
 * allowing for iteration over the results returned from the underlying query. It supports
 * one by one document iteration, conversion to an array or can be iterated as a Node 4.X
 * or higher stream
 *
 * **AGGREGATIONCURSOR Cannot directly be instantiated**
 * @example
 * const MongoClient = require('mongodb').MongoClient;
 * const test = require('assert');
 * // Connection url
 * const url = 'mongodb://localhost:27017';
 * // Database Name
 * const dbName = 'test';
 * // Connect using MongoClient
 * MongoClient.connect(url, function(err, client) {
 *   // Create a collection we want to drop later
 *   const col = client.db(dbName).collection('createIndexExample1');
 *   // Insert a bunch of documents
 *   col.insert([{a:1, b:1}
 *     , {a:2, b:2}, {a:3, b:3}
 *     , {a:4, b:4}], {w:1}, function(err, result) {
 *     test.equal(null, err);
 *     // Show that duplicate records got dropped
 *     col.aggregation({}, {cursor: {}}).toArray(function(err, items) {
 *       test.equal(null, err);
 *       test.equal(4, items.length);
 *       client.close();
 *     });
 *   });
 * });
 */

/**
 * Namespace provided by the browser.
 * @external Readable
 */

/**
 * Creates a new Aggregation Cursor instance (INTERNAL TYPE, do not instantiate directly)
 * @class AggregationCursor
 * @extends external:Readable
 * @fires AggregationCursor#data
 * @fires AggregationCursor#end
 * @fires AggregationCursor#close
 * @fires AggregationCursor#readable
 * @return {AggregationCursor} an AggregationCursor instance.
 */
var AggregationCursor = function(bson, ns, cmd, options, topology, topologyOptions) {
  CoreCursor.apply(this, Array.prototype.slice.call(arguments, 0));
  var state = AggregationCursor.INIT;
  var streamOptions = {};

  // MaxTimeMS
  var maxTimeMS = null;

  // Get the promiseLibrary
  var promiseLibrary = options.promiseLibrary || Promise;

  // Set up
  Readable.call(this, { objectMode: true });

  // Internal state
  this.s = {
    // MaxTimeMS
    maxTimeMS: maxTimeMS,
    // State
    state: state,
    // Stream options
    streamOptions: streamOptions,
    // BSON
    bson: bson,
    // Namespace
    ns: ns,
    // Command
    cmd: cmd,
    // Options
    options: options,
    // Topology
    topology: topology,
    // Topology Options
    topologyOptions: topologyOptions,
    // Promise library
    promiseLibrary: promiseLibrary,
    // Optional ClientSession
    session: options.session
  };
};

/**
 * AggregationCursor stream data event, fired for each document in the cursor.
 *
 * @event AggregationCursor#data
 * @type {object}
 */

/**
 * AggregationCursor stream end event
 *
 * @event AggregationCursor#end
 * @type {null}
 */

/**
 * AggregationCursor stream close event
 *
 * @event AggregationCursor#close
 * @type {null}
 */

/**
 * AggregationCursor stream readable event
 *
 * @event AggregationCursor#readable
 * @type {null}
 */

// Inherit from Readable
inherits(AggregationCursor, Readable);

// Extend the Cursor
for (var name in CoreCursor.prototype) {
  AggregationCursor.prototype[name] = CoreCursor.prototype[name];
}

/**
 * Set the batch size for the cursor.
 * @method
 * @param {number} value The batchSize for the cursor.
 * @throws {MongoError}
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.batchSize = function(value) {
  if (this.s.state === AggregationCursor.CLOSED || this.isDead())
    throw MongoError.create({ message: 'Cursor is closed', driver: true });
  if (typeof value !== 'number')
    throw MongoError.create({ message: 'batchSize requires an integer', drvier: true });
  if (this.s.cmd.cursor) this.s.cmd.cursor.batchSize = value;
  this.setCursorBatchSize(value);
  return this;
};

/**
 * Add a geoNear stage to the aggregation pipeline
 * @method
 * @param {object} document The geoNear stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.geoNear = function(document) {
  this.s.cmd.pipeline.push({ $geoNear: document });
  return this;
};

/**
 * Add a group stage to the aggregation pipeline
 * @method
 * @param {object} document The group stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.group = function(document) {
  this.s.cmd.pipeline.push({ $group: document });
  return this;
};

/**
 * Add a limit stage to the aggregation pipeline
 * @method
 * @param {number} value The state limit value.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.limit = function(value) {
  this.s.cmd.pipeline.push({ $limit: value });
  return this;
};

/**
 * Add a match stage to the aggregation pipeline
 * @method
 * @param {object} document The match stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.match = function(document) {
  this.s.cmd.pipeline.push({ $match: document });
  return this;
};

/**
 * Add a maxTimeMS stage to the aggregation pipeline
 * @method
 * @param {number} value The state maxTimeMS value.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.maxTimeMS = function(value) {
  if (this.s.topology.lastIsMaster().minWireVersion > 2) {
    this.s.cmd.maxTimeMS = value;
  }
  return this;
};

/**
 * Add a out stage to the aggregation pipeline
 * @method
 * @param {number} destination The destination name.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.out = function(destination) {
  this.s.cmd.pipeline.push({ $out: destination });
  return this;
};

/**
 * Add a project stage to the aggregation pipeline
 * @method
 * @param {object} document The project stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.project = function(document) {
  this.s.cmd.pipeline.push({ $project: document });
  return this;
};

/**
 * Add a lookup stage to the aggregation pipeline
 * @method
 * @param {object} document The lookup stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.lookup = function(document) {
  this.s.cmd.pipeline.push({ $lookup: document });
  return this;
};

/**
 * Add a redact stage to the aggregation pipeline
 * @method
 * @param {object} document The redact stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.redact = function(document) {
  this.s.cmd.pipeline.push({ $redact: document });
  return this;
};

/**
 * Add a skip stage to the aggregation pipeline
 * @method
 * @param {number} value The state skip value.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.skip = function(value) {
  this.s.cmd.pipeline.push({ $skip: value });
  return this;
};

/**
 * Add a sort stage to the aggregation pipeline
 * @method
 * @param {object} document The sort stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.sort = function(document) {
  this.s.cmd.pipeline.push({ $sort: document });
  return this;
};

/**
 * Add a unwind stage to the aggregation pipeline
 * @method
 * @param {number} field The unwind field name.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.unwind = function(field) {
  this.s.cmd.pipeline.push({ $unwind: field });
  return this;
};

/**
 * Return the cursor logger
 * @method
 * @return {Logger} return the cursor logger
 * @ignore
 */
AggregationCursor.prototype.getLogger = function() {
  return this.logger;
};

AggregationCursor.prototype.get = AggregationCursor.prototype.toArray;

/**
 * Get the next available document from the cursor, returns null if no more documents are available.
 * @function AggregationCursor.prototype.next
 * @param {AggregationCursor~resultCallback} [callback] The result callback.
 * @throws {MongoError}
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * Check if there is any document still available in the cursor
 * @function AggregationCursor.prototype.hasNext
 * @param {AggregationCursor~resultCallback} [callback] The result callback.
 * @throws {MongoError}
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * The callback format for results
 * @callback AggregationCursor~toArrayResultCallback
 * @param {MongoError} error An error instance representing the error during the execution.
 * @param {object[]} documents All the documents the satisfy the cursor.
 */

/**
 * Returns an array of documents. The caller is responsible for making sure that there
 * is enough memory to store the results. Note that the array only contain partial
 * results when this cursor had been previouly accessed. In that case,
 * cursor.rewind() can be used to reset the cursor.
 * @method AggregationCursor.prototype.toArray
 * @param {AggregationCursor~toArrayResultCallback} [callback] The result callback.
 * @throws {MongoError}
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * The callback format for results
 * @callback AggregationCursor~resultCallback
 * @param {MongoError} error An error instance representing the error during the execution.
 * @param {(object|null)} result The result object if the command was executed successfully.
 */

/**
 * Iterates over all the documents for this cursor. As with **{cursor.toArray}**,
 * not all of the elements will be iterated if this cursor had been previouly accessed.
 * In that case, **{cursor.rewind}** can be used to reset the cursor. However, unlike
 * **{cursor.toArray}**, the cursor will only hold a maximum of batch size elements
 * at any given time if batch size is specified. Otherwise, the caller is responsible
 * for making sure that the entire result can fit the memory.
 * @method AggregationCursor.prototype.each
 * @param {AggregationCursor~resultCallback} callback The result callback.
 * @throws {MongoError}
 * @return {null}
 */

/**
 * Close the cursor, sending a AggregationCursor command and emitting close.
 * @method AggregationCursor.prototype.close
 * @param {AggregationCursor~resultCallback} [callback] The result callback.
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * Is the cursor closed
 * @method AggregationCursor.prototype.isClosed
 * @return {boolean}
 */

/**
 * Execute the explain for the cursor
 * @method AggregationCursor.prototype.explain
 * @param {AggregationCursor~resultCallback} [callback] The result callback.
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * Clone the cursor
 * @function AggregationCursor.prototype.clone
 * @return {AggregationCursor}
 */

/**
 * Resets the cursor
 * @function AggregationCursor.prototype.rewind
 * @return {AggregationCursor}
 */

/**
 * The callback format for the forEach iterator method
 * @callback AggregationCursor~iteratorCallback
 * @param {Object} doc An emitted document for the iterator
 */

/**
 * The callback error format for the forEach iterator method
 * @callback AggregationCursor~endCallback
 * @param {MongoError} error An error instance representing the error during the execution.
 */

/*
 * Iterates over all the documents for this cursor using the iterator, callback pattern.
 * @method AggregationCursor.prototype.forEach
 * @param {AggregationCursor~iteratorCallback} iterator The iteration callback.
 * @param {AggregationCursor~endCallback} callback The end callback.
 * @throws {MongoError}
 * @return {null}
 */

AggregationCursor.INIT = 0;
AggregationCursor.OPEN = 1;
AggregationCursor.CLOSED = 2;

module.exports = AggregationCursor;
<commit_msg>refactor(aggregation): deprecate `$geoNear` agg stage helper<commit_after>'use strict';

const inherits = require('util').inherits;
const MongoError = require('mongodb-core').MongoError;
const Readable = require('stream').Readable;
const CoreCursor = require('./cursor');
const deprecate = require('util').deprecate;

/**
 * @fileOverview The **AggregationCursor** class is an internal class that embodies an aggregation cursor on MongoDB
 * allowing for iteration over the results returned from the underlying query. It supports
 * one by one document iteration, conversion to an array or can be iterated as a Node 4.X
 * or higher stream
 *
 * **AGGREGATIONCURSOR Cannot directly be instantiated**
 * @example
 * const MongoClient = require('mongodb').MongoClient;
 * const test = require('assert');
 * // Connection url
 * const url = 'mongodb://localhost:27017';
 * // Database Name
 * const dbName = 'test';
 * // Connect using MongoClient
 * MongoClient.connect(url, function(err, client) {
 *   // Create a collection we want to drop later
 *   const col = client.db(dbName).collection('createIndexExample1');
 *   // Insert a bunch of documents
 *   col.insert([{a:1, b:1}
 *     , {a:2, b:2}, {a:3, b:3}
 *     , {a:4, b:4}], {w:1}, function(err, result) {
 *     test.equal(null, err);
 *     // Show that duplicate records got dropped
 *     col.aggregation({}, {cursor: {}}).toArray(function(err, items) {
 *       test.equal(null, err);
 *       test.equal(4, items.length);
 *       client.close();
 *     });
 *   });
 * });
 */

/**
 * Namespace provided by the browser.
 * @external Readable
 */

/**
 * Creates a new Aggregation Cursor instance (INTERNAL TYPE, do not instantiate directly)
 * @class AggregationCursor
 * @extends external:Readable
 * @fires AggregationCursor#data
 * @fires AggregationCursor#end
 * @fires AggregationCursor#close
 * @fires AggregationCursor#readable
 * @return {AggregationCursor} an AggregationCursor instance.
 */
var AggregationCursor = function(bson, ns, cmd, options, topology, topologyOptions) {
  CoreCursor.apply(this, Array.prototype.slice.call(arguments, 0));
  var state = AggregationCursor.INIT;
  var streamOptions = {};

  // MaxTimeMS
  var maxTimeMS = null;

  // Get the promiseLibrary
  var promiseLibrary = options.promiseLibrary || Promise;

  // Set up
  Readable.call(this, { objectMode: true });

  // Internal state
  this.s = {
    // MaxTimeMS
    maxTimeMS: maxTimeMS,
    // State
    state: state,
    // Stream options
    streamOptions: streamOptions,
    // BSON
    bson: bson,
    // Namespace
    ns: ns,
    // Command
    cmd: cmd,
    // Options
    options: options,
    // Topology
    topology: topology,
    // Topology Options
    topologyOptions: topologyOptions,
    // Promise library
    promiseLibrary: promiseLibrary,
    // Optional ClientSession
    session: options.session
  };
};

/**
 * AggregationCursor stream data event, fired for each document in the cursor.
 *
 * @event AggregationCursor#data
 * @type {object}
 */

/**
 * AggregationCursor stream end event
 *
 * @event AggregationCursor#end
 * @type {null}
 */

/**
 * AggregationCursor stream close event
 *
 * @event AggregationCursor#close
 * @type {null}
 */

/**
 * AggregationCursor stream readable event
 *
 * @event AggregationCursor#readable
 * @type {null}
 */

// Inherit from Readable
inherits(AggregationCursor, Readable);

// Extend the Cursor
for (var name in CoreCursor.prototype) {
  AggregationCursor.prototype[name] = CoreCursor.prototype[name];
}

/**
 * Set the batch size for the cursor.
 * @method
 * @param {number} value The batchSize for the cursor.
 * @throws {MongoError}
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.batchSize = function(value) {
  if (this.s.state === AggregationCursor.CLOSED || this.isDead())
    throw MongoError.create({ message: 'Cursor is closed', driver: true });
  if (typeof value !== 'number')
    throw MongoError.create({ message: 'batchSize requires an integer', drvier: true });
  if (this.s.cmd.cursor) this.s.cmd.cursor.batchSize = value;
  this.setCursorBatchSize(value);
  return this;
};

/**
 * Add a geoNear stage to the aggregation pipeline
 * @method
 * @param {object} document The geoNear stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.geoNear = deprecate(function(document) {
  this.s.cmd.pipeline.push({ $geoNear: document });
  return this;
}, 'The `$geoNear` stage is deprecated in MongoDB 4.0, and removed in version 4.2.');

/**
 * Add a group stage to the aggregation pipeline
 * @method
 * @param {object} document The group stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.group = function(document) {
  this.s.cmd.pipeline.push({ $group: document });
  return this;
};

/**
 * Add a limit stage to the aggregation pipeline
 * @method
 * @param {number} value The state limit value.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.limit = function(value) {
  this.s.cmd.pipeline.push({ $limit: value });
  return this;
};

/**
 * Add a match stage to the aggregation pipeline
 * @method
 * @param {object} document The match stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.match = function(document) {
  this.s.cmd.pipeline.push({ $match: document });
  return this;
};

/**
 * Add a maxTimeMS stage to the aggregation pipeline
 * @method
 * @param {number} value The state maxTimeMS value.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.maxTimeMS = function(value) {
  if (this.s.topology.lastIsMaster().minWireVersion > 2) {
    this.s.cmd.maxTimeMS = value;
  }
  return this;
};

/**
 * Add a out stage to the aggregation pipeline
 * @method
 * @param {number} destination The destination name.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.out = function(destination) {
  this.s.cmd.pipeline.push({ $out: destination });
  return this;
};

/**
 * Add a project stage to the aggregation pipeline
 * @method
 * @param {object} document The project stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.project = function(document) {
  this.s.cmd.pipeline.push({ $project: document });
  return this;
};

/**
 * Add a lookup stage to the aggregation pipeline
 * @method
 * @param {object} document The lookup stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.lookup = function(document) {
  this.s.cmd.pipeline.push({ $lookup: document });
  return this;
};

/**
 * Add a redact stage to the aggregation pipeline
 * @method
 * @param {object} document The redact stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.redact = function(document) {
  this.s.cmd.pipeline.push({ $redact: document });
  return this;
};

/**
 * Add a skip stage to the aggregation pipeline
 * @method
 * @param {number} value The state skip value.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.skip = function(value) {
  this.s.cmd.pipeline.push({ $skip: value });
  return this;
};

/**
 * Add a sort stage to the aggregation pipeline
 * @method
 * @param {object} document The sort stage document.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.sort = function(document) {
  this.s.cmd.pipeline.push({ $sort: document });
  return this;
};

/**
 * Add a unwind stage to the aggregation pipeline
 * @method
 * @param {number} field The unwind field name.
 * @return {AggregationCursor}
 */
AggregationCursor.prototype.unwind = function(field) {
  this.s.cmd.pipeline.push({ $unwind: field });
  return this;
};

/**
 * Return the cursor logger
 * @method
 * @return {Logger} return the cursor logger
 * @ignore
 */
AggregationCursor.prototype.getLogger = function() {
  return this.logger;
};

AggregationCursor.prototype.get = AggregationCursor.prototype.toArray;

/**
 * Get the next available document from the cursor, returns null if no more documents are available.
 * @function AggregationCursor.prototype.next
 * @param {AggregationCursor~resultCallback} [callback] The result callback.
 * @throws {MongoError}
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * Check if there is any document still available in the cursor
 * @function AggregationCursor.prototype.hasNext
 * @param {AggregationCursor~resultCallback} [callback] The result callback.
 * @throws {MongoError}
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * The callback format for results
 * @callback AggregationCursor~toArrayResultCallback
 * @param {MongoError} error An error instance representing the error during the execution.
 * @param {object[]} documents All the documents the satisfy the cursor.
 */

/**
 * Returns an array of documents. The caller is responsible for making sure that there
 * is enough memory to store the results. Note that the array only contain partial
 * results when this cursor had been previouly accessed. In that case,
 * cursor.rewind() can be used to reset the cursor.
 * @method AggregationCursor.prototype.toArray
 * @param {AggregationCursor~toArrayResultCallback} [callback] The result callback.
 * @throws {MongoError}
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * The callback format for results
 * @callback AggregationCursor~resultCallback
 * @param {MongoError} error An error instance representing the error during the execution.
 * @param {(object|null)} result The result object if the command was executed successfully.
 */

/**
 * Iterates over all the documents for this cursor. As with **{cursor.toArray}**,
 * not all of the elements will be iterated if this cursor had been previouly accessed.
 * In that case, **{cursor.rewind}** can be used to reset the cursor. However, unlike
 * **{cursor.toArray}**, the cursor will only hold a maximum of batch size elements
 * at any given time if batch size is specified. Otherwise, the caller is responsible
 * for making sure that the entire result can fit the memory.
 * @method AggregationCursor.prototype.each
 * @param {AggregationCursor~resultCallback} callback The result callback.
 * @throws {MongoError}
 * @return {null}
 */

/**
 * Close the cursor, sending a AggregationCursor command and emitting close.
 * @method AggregationCursor.prototype.close
 * @param {AggregationCursor~resultCallback} [callback] The result callback.
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * Is the cursor closed
 * @method AggregationCursor.prototype.isClosed
 * @return {boolean}
 */

/**
 * Execute the explain for the cursor
 * @method AggregationCursor.prototype.explain
 * @param {AggregationCursor~resultCallback} [callback] The result callback.
 * @return {Promise} returns Promise if no callback passed
 */

/**
 * Clone the cursor
 * @function AggregationCursor.prototype.clone
 * @return {AggregationCursor}
 */

/**
 * Resets the cursor
 * @function AggregationCursor.prototype.rewind
 * @return {AggregationCursor}
 */

/**
 * The callback format for the forEach iterator method
 * @callback AggregationCursor~iteratorCallback
 * @param {Object} doc An emitted document for the iterator
 */

/**
 * The callback error format for the forEach iterator method
 * @callback AggregationCursor~endCallback
 * @param {MongoError} error An error instance representing the error during the execution.
 */

/*
 * Iterates over all the documents for this cursor using the iterator, callback pattern.
 * @method AggregationCursor.prototype.forEach
 * @param {AggregationCursor~iteratorCallback} iterator The iteration callback.
 * @param {AggregationCursor~endCallback} callback The end callback.
 * @throws {MongoError}
 * @return {null}
 */

AggregationCursor.INIT = 0;
AggregationCursor.OPEN = 1;
AggregationCursor.CLOSED = 2;

module.exports = AggregationCursor;
<|endoftext|>


<commit_before>var _ = require("underscore")
  , routeMethods
  , routes = routes = {
               get: []
             , post: []
             , put: []
             , "delete": []
             }

routeMethods = ["get", "post", "put", "delete"]

$("a").live("click", function(e) {
  e.preventDefault()
  var url = $(this).attr("href")
  try {
    Router.get(url, true)
    //prevent the default if router doesnt throw a 404 exception
    e.preventDefault()
  } catch(e) {
    //just swallow the 404 and do the regular link action
  }
})

var zipObject = function(k, v) {
  var arr = _.zip(k, v)
    ,red = function(m, x) {
      m[x[0]] = x[1]
      return m
    }
  return _.reduce(arr, red, {})
}

var normalizePath = function(path, keys) {
  var buildRegex
  buildRegex = function(_, slash, format, key, capture, optional) {
    keys.push(key)
    if (slash == null) slash = ''
    return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || '([^/]+?)') + ')' + (optional || '')
  }
  path = path.concat('/?').replace(/\/\(/g, '(?:/').replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, buildRegex).replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.+)')
  return new RegExp('^' + path + '$', 'i')
}

window.onpopstate = function(event) {
  event = event = {}
  var state = event.state || {}
    , method = state.method || "get"
    , params = state.params || {}
  return Router[method](document.location.pathname, params)
}

var matchRoute = function(method, url, params, push) {
  params = params || {}
  var matched = false
  _(routes[method]).each(function(obj) {
    var matches = url.match(obj.regex)
    if (matches) {
      matched = true
      _.extend(params, zipObject(obj.keys, matches.slice(1)))
      methods = {
        url: url
      }
      // this should be made async with a callback when done
      var stopRoute = false
      _.each(obj.filters.before, function(filter) {
        if (!stopRoute && filter.call(methods, params) === false) {
          return stopRoute = true
        }
      })
      if (stopRoute) {
        return
      }
      if (push) {
        $("body").attr("class", url.replace(/\//g, " ").trim())
        window.history.pushState({
          method: method
        , params: params
        }, "", url)
      }
      obj.callback.call(methods, params)
      _.each(obj.filters.after, function(filter) {
        return filter(params)
      })
    }
  })
  if (!matched) {
    throw new Error("404 Route not found")
  }
}

registerRoute = function(method, route, filters, callback) {
  var keys
  keys = []
  return routes[method].push({
    "regex": normalizePath(route, keys),
    "keys": keys,
    "original": route,
    "filters": filters,
    "callback": callback
  })
}
Controller = function(base, scope) {
  var filters, router
  router = {}
  filters = {
    before: [],
    after: []
  }
  _(routeMethods).each(function(method) {
    router[method] = function(route, callback) {
      if (typeof route !== "string") {
        callback = route
        route = ""
      }
      registerRoute(method, base + route, filters, callback)
    }
  })
  router.before = function(filter) {
    filters.before.push(filter)
  }
  router.after = function(filter) {
    filters.after.push(filter)
  }
  scope(router)
}
module.exports.Controller = Controller
module.exports.Router = Router = {
  get: function(url, push) {
    return matchRoute("get", url, {}, push)
  },
  post: function(url, params, push) {
    return matchRoute("post", url, params, push)
  },
  put: function(url, params, push) {
    return matchRoute("put", url, params, push)
  },
  "delete": function(url, push) {
    return matchRoute("delete", url, {}, push)
  }
}

<commit_msg>have option to not use pushstate and reload the page<commit_after>var _ = require("underscore")
  , routeMethods
  , routes = routes = {
               get: []
             , post: []
             , put: []
             , "delete": []
             }

routeMethods = ["get", "post", "put", "delete"]

$("a").live("click", function(e) {
  e.preventDefault()
  var url = $(this).attr("href")
  try {
    Router.get(url, true)
    //prevent the default if router doesnt throw a 404 exception
    e.preventDefault()
  } catch(e) {
    //just swallow the 404 and do the regular link action
  }
})

var zipObject = function(k, v) {
  var arr = _.zip(k, v)
    ,red = function(m, x) {
      m[x[0]] = x[1]
      return m
    }
  return _.reduce(arr, red, {})
}

var normalizePath = function(path, keys) {
  var buildRegex
  buildRegex = function(_, slash, format, key, capture, optional) {
    keys.push(key)
    if (slash == null) slash = ''
    return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || '([^/]+?)') + ')' + (optional || '')
  }
  path = path.concat('/?').replace(/\/\(/g, '(?:/').replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, buildRegex).replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.+)')
  return new RegExp('^' + path + '$', 'i')
}

window.onpopstate = function(event) {
  event = event = {}
  var state = event.state || {}
    , method = state.method || "get"
    , params = state.params || {}
  return Router[method](document.location.pathname, params)
}

var matchRoute = function(method, url, params, push) {
  params = params || {}
  var matched = false
  _(routes[method]).each(function(obj) {
    var matches = url.match(obj.regex)
    if (matches) {
      matched = true
      _.extend(params, zipObject(obj.keys, matches.slice(1)))
      methods = {
        url: url
      }
      // this should be made async with a callback when done
      var stopRoute = false
      _.each(obj.filters.before, function(filter) {
        if (!stopRoute && filter.call(methods, params) === false) {
          return stopRoute = true
        }
      })
      if (stopRoute) {
        return
      }
      if (push) {
        $("body").attr("class", url.replace(/\//g, " ").trim())
        window.history.pushState({
          method: method
        , params: params
        }, "", url)
      }
      obj.callback.call(methods, params)
      _.each(obj.filters.after, function(filter) {
        return filter(params)
      })
    }
  })
  if (!matched) {
    throw new Error("404 Route not found")
  }
}

registerRoute = function(method, route, filters, callback) {
  var keys
  keys = []
  return routes[method].push({
    "regex": normalizePath(route, keys),
    "keys": keys,
    "original": route,
    "filters": filters,
    "callback": callback
  })
}
Controller = function(base, scope) {
  var filters, router
  router = {}
  filters = {
    before: [],
    after: []
  }
  _(routeMethods).each(function(method) {
    router[method] = function(route, callback) {
      if (typeof route !== "string") {
        callback = route
        route = ""
      }
      registerRoute(method, base + route, filters, callback)
    }
  })
  router.before = function(filter) {
    filters.before.push(filter)
  }
  router.after = function(filter) {
    filters.after.push(filter)
  }
  scope(router)
}
module.exports.Controller = Controller
module.exports.Router = Router = {
  get: function(url, opts) {
    var push
      , reload
    if(typeof opts === "object") {
      push = opts.push
      reload = opts.reload
    }
    if(typeof push === "undefined") {
      push = true
    }
    if(reload) {
      document.location = url
    } else {
      return matchRoute("get", url, {}, push)
    }
  },
  post: function(url, params, push) {
    return matchRoute("post", url, params, push)
  },
  put: function(url, params, push) {
    return matchRoute("put", url, params, push)
  },
  "delete": function(url, push) {
    return matchRoute("delete", url, {}, push)
  }
}

<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';

import Main from './components/Main.jsx';

var node = document.getElementById('app');

ReactDOM.render(<Main />, node);
ç
<commit_msg>remove typo...<commit_after>import React from 'react';
import ReactDOM from 'react-dom';

import Main from './components/Main.jsx';

var node = document.getElementById('app');

ReactDOM.render(<Main />, node);
<|endoftext|>