<commit_before>define('web-ext-utils/options', function() { 'use strict';

let context = null; // a OptionsRoot during its constrution

const Defaults = new WeakMap;
const Values = new WeakMap;
const OnTrue = new WeakMap;
const OnFalse = new WeakMap;
const OnChange = new WeakMap;
const OnAnyChange = new WeakMap;

const callbackMaps = [ OnTrue, OnFalse, OnChange, OnAnyChange, ];

const Storage = new WeakMap;
const _uniqueCache = new WeakMap;

class Option {
	constructor(_default, parent) {
		Defaults.set(this, _default);
		this.parent = parent;
		this.path = (parent ? parent.path +'.' : '') + (_default.name || '');
		_default.name && (this.name = _default.name +'');
		_default.type && (this.type = _default.type +'');
		_default.title && (this.title = _default.title +'');
		_default.description && (this.description = _default.description +'');
		_default.prefix && (this.prefix = _default.prefix +'');
		_default.infix && (this.infix = _default.infix +'');
		_default.suffix && (this.suffix = _default.suffix +'');
		_default.addDefault && (this.addDefault = _default.addDefault);
		_default.expanded != null && (this.expanded = !!_default.expanded);
		_default.placeholder && (this.placeholder = _default.placeholder +'');
		_default.options && (this.options = Object.freeze(
			Array.prototype.filter.call(_default.options, option => option && option.label)
			.map(({ label, value, }) => Object.freeze({ label, value, }))
		));
		if (!_default.hasOwnProperty('default')) {
			this.defaults = Object.freeze([ ]);
		} else if (Array.isArray(_default.default)) {
			this.defaults = Object.freeze(_default.default.map(Object.freeze));
			this.default = _default.default[0];
		} else {
			this.default = Object.freeze(_default.default);
			this.defaults = Object.freeze([ this.default ]);
		}

		_default.restrict && (this.restrict = _default.restrict === 'inherit' ? parent.restrict : new Restriction(this, _default.restrict));

		this.children = new OptionList((_default.children || [ ]).map(child => new Option(child, this)), this);

		context.options.set(this.path, this);
		return Object.freeze(this);
	}

	get value() { return Values.get(this).get(0); }
	set value(value) { return Values.get(this).set(0, value); }
	get values() { return Values.get(this); }
	set values(values) { return Values.get(this).replace(values); }
	reset() { return Values.get(this).reset(); }
	resetAll() {
		this.reset();
		this.children.forEach(child => child.resetAll());
	}

	whenTrue(listener) {
		const values = this.values;
		let listeners = OnTrue.get(this) || new Set;
		OnTrue.set(this, listeners);
		const added = listeners.add(listener);
		added && values.is && callAll([ listener, ], values.get(0), values, null, this.path);
		return added;
	}
	whenFalse(listener) {
		const values = this.values;
		let listeners = OnFalse.get(this) || new Set;
		OnFalse.set(this, listeners);
		const added = listeners.add(listener);
		added && !values.is && callAll([ listener, ], values.get(0), values, null, this.path);
		return added;
	}
	when(true_false) {
		true_false && true_false.true && this.whenTrue(true_false.true);
		true_false && true_false.false && this.whenFalse(true_false.false);
	}
	whenChange(listener) {
		const values = this.values;
		let listeners = OnChange.get(this) || new Set;
		OnChange.set(this, listeners);
		const added = listeners.add(listener);
		added && callAll([ listener, ], values.get(0), values, null, this.path);
		return added;
	}
	onChange(listener) {
		let listeners = OnChange.get(this) || new Set;
		OnChange.set(this, listeners);
		return listeners.add(listener);
	}
	onAnyChange(listener) {
		let listeners = OnAnyChange.get(this) || new Set;
		OnAnyChange.set(this, listeners);
		return listeners.add(listener);
	}
	offAnyChange(listener) {
		let listeners = OnAnyChange.get(this);
		return listeners && listeners.delete(listener);
	}

}

class OptionList extends Array {
	constructor(items, parent) {
		super();
		Object.defineProperty(this, 'parent', { value: parent, });
		items.forEach((item, index, array) => this[item.name] = this[index] = item);
		return Object.freeze(this);
	}
	static get [Symbol.species]() { return Array; }
}

class ValueList {
	constructor(parent, values) {
		this.parent = parent;
		Storage.set(this, context.storage);

		this.key = context.prefix + this.parent.path;
		Values.set(this, Object.freeze(values));
		const _default = Defaults.get(parent);
		this.max = _default.hasOwnProperty('maxLength') ? +_default.maxLength : 1;
		this.min = _default.hasOwnProperty('minLength') ? +_default.minLength : +!_default.hasOwnProperty('maxLength');
		return Object.freeze(this);
	}
	get current() { return Values.get(this); }
	get is() { return !!Values.get(this).find(x => x); }
	get(index) {
		return Values.get(this)[index];
	}
	set(index, value) {
		const values = Values.get(this).slice();
		values[index] = value;
		this.parent.restrict.validate(value, values, this.parent);
		return Storage.get(this).set({ [this.key]: values, });
	}
	replace(values) {
		if (values.length < this.min || values.length > this.max) {
			throw new Error('the number of values for the option "'+ this.key +'" must be between '+ this.min +' and '+ this.max);
		}
		this.parent.restrict && this.parent.restrict.validateAll(values, this.parent);
		return Storage.get(this).set({ [this.key]: values, });
	}
	splice(index, remove, ...insert) {
		const values = Values.get(this).slice();
		values.splice.apply(values, arguments);
		if (values.length < this.min || values.length > this.max) {
			throw new Error('the number of values for the option "'+ this.key +'" must be between '+ this.min +' and '+ this.max);
		}
		this.parent.restrict && this.parent.restrict.validateAll(insert, this.parent, index);
		return Storage.get(this).set({ [this.key]: values, });
	}
	reset() {
		return Storage.get(this).remove(this.key);
	}
}

class Restriction {
	constructor(parent, restrict) {
		this._parent = parent;
		const from = this.from = restrict.from;
		const to = this.to = restrict.to;
		const match = this.match = restrict.match && Object.freeze({
			exp: restrict.match.exp ? new RegExp(restrict.match.exp) : new RegExp(restrict.match.source, restrict.match.flags),
			message: restrict.match.message || 'This value must match '+ (restrict.match.exp || new RegExp(restrict.match.source, restrict.match.flags)),
		});
		const type = this.type = restrict.type;
		const isRegExp = this.isRegExp = restrict.isRegExp;
		const unique = this.unique = Object.freeze(restrict.unique);
		const checks = [ ];
		restrict.hasOwnProperty('from') && checks.push(value => value < from && ('This value must be at least '+ from));
		restrict.hasOwnProperty('to') && checks.push(value => value > to && ('This value can be at most '+ to));
		match && checks.push(value => !match.exp.test(value) && match.message);
		type && checks.push(value => typeof value !== type && ('This value must be of type "'+ type +'" but is "'+ (typeof value) +'"'));
		isRegExp && checks.push(value => void RegExp(value));
		parent.type !== 'interval' && restrict.hasOwnProperty('unique') && (() => {
			checks.push((value, values, option) => this._unique.map(other => {
				if (other === option) {
					return values.filter(v => v === value).length > 1 && 'This value must be unique winthin this option';
				}
				return other && other.values.current.indexOf(value) !== -1 && 'This value must be unique, but it is already used in "'+ other.title +'"';
			}).find(x => x));
		})();
		this.checks = Object.freeze(checks);
		return Object.freeze(this);
	}
	validate(value, values, option) {
		const message = this._parent.type === 'interval'
		? this.checks.map(check => typeof value === 'object' && (check(value.from) || check(value.to))).find(x => x)
		: this.checks.map(check => check(value, values, option)).find(x => x);
		if (message) { throw new Error(message); }
	}
	validateAll(values, option, offset = 0) {
		values.forEach((value, index) => { try {
			this.validate(value, values, option);
		} catch (error) {
			try { error.index = index + offset; } catch (e) { }
			throw error;
		} });
	}
	get _unique() {
		let options = _uniqueCache.get(this);
		if (options) { return options; }
		const paths = (typeof this.unique === 'string' ? [ this.unique, ] : this.unique || [ ]).map(path => path.split(/[\/\\]/));
		const result = new Set;
		paths.forEach(path => walk(this._parent, path));
		options = Object.freeze(Array.from(result));
		_uniqueCache.set(this, options);
		return options;

		function walk(option, path) {
			if (!path.length) { return result.add(option); }
			const segment = path.shift();
			switch (segment) {
				case '.': {
					walk(option, path);
				} break;
				case '..': {
					walk(option.parent, path);
				} break;
				case '*': {
					option.children.forEach(child => walk(child, path));
				} break;
				default: {
					walk(option.children[segment], path);
				} break;
			}
		}
	}
}

function callAll(callbacks, value, values, old, path) {
	callbacks && callbacks.forEach(listener => { try {
		listener(value, values, old);
	} catch (error) { console.error('Options change listener for "'+ path +'" threw', error); } });
}

function crawlDown(option, callback) {
	callback(option);
	option.children.forEach(option => crawlDown(option, callback));
}

function climbUp(option, callback) {
	callback(option);
	option.parent && climbUp(option.parent, callback);
}

function inContext(ctx, callback) {
	try {
		context = ctx;
		return callback();
	} finally {
		context = null;
	}
}

return class OptionsRoot {
	constructor({ defaults, prefix, storage, addChangeListener, removeChangeListener, }) {
		const options = this.options = new Map;
		this.prefix = prefix; this.storage = storage;
		inContext(this, () => this._shadow = new Option({ children: defaults, }, null));
		this.children = this._shadow.children;
		this.onChange = this.onChange.bind(this);
		this._removeChangeListener = removeChangeListener;

		return storage.get(Array.from(options.keys()).map(path => prefix + path))
		.then(data => inContext(this, () => {
			const { hasOwnProperty, } = Object.prototype;
			options.forEach(option => Values.set(option, new ValueList(
				option,
				hasOwnProperty.call(data, prefix + option.path) ? data[prefix + option.path] : option.defaults
			)));
			addChangeListener && addChangeListener.call(this, this.onChange);

			return this;
		}));
	}

	onChange(key, values) {
		if (!key.startsWith(this.prefix) || this.destroyed) { return; }
		const path = key.slice(this.prefix.length);
		const option = this.options.get(path);
		!values && (values = option.defaults);
		const list = option.values;
		const old = Values.get(list);
		Values.set(list, values);

		const is = values.find(x => x);
		const was = old.find(x => x);

		callAll(OnChange.get(option), values[0], list, old, path);
		climbUp(option, option => callAll(OnAnyChange.get(option), values[0], list, old, path));
		is && !was && callAll(OnTrue.get(option), values[0], list, old, path);
		!is && was && callAll(OnFalse.get(option), values[0], list, old, path);
	}

	resetAll() {
		return this.storage.remove(Array.from(this.options.keys()));
	}

	onAnyChange() {
		return this._shadow.onAnyChange(...arguments);
	}

	destroy() {
		this.destroyed = true;
		crawlDown(this._shadow, option => callbackMaps.forEach(map => map.delete(option)));
		this._removeChangeListener && this._removeChangeListener(this.onChange);
	}
};

});
<commit_msg>options: rename defaults to model<commit_after>define('web-ext-utils/options', function() { 'use strict';

let context = null; // a OptionsRoot during its constrution

const Models = new WeakMap;
const Values = new WeakMap;
const OnTrue = new WeakMap;
const OnFalse = new WeakMap;
const OnChange = new WeakMap;
const OnAnyChange = new WeakMap;

const callbackMaps = [ OnTrue, OnFalse, OnChange, OnAnyChange, ];

const Storage = new WeakMap;
const _uniqueCache = new WeakMap;

class Option {
	constructor(model, parent) {
		Models.set(this, model);
		this.parent = parent;
		this.path = (parent ? parent.path +'.' : '') + (model.name || '');
		model.name && (this.name = model.name +'');
		model.type && (this.type = model.type +'');
		model.title && (this.title = model.title +'');
		model.description && (this.description = model.description +'');
		model.prefix && (this.prefix = model.prefix +'');
		model.infix && (this.infix = model.infix +'');
		model.suffix && (this.suffix = model.suffix +'');
		model.addDefault && (this.addDefault = model.addDefault);
		model.expanded != null && (this.expanded = !!model.expanded);
		model.placeholder && (this.placeholder = model.placeholder +'');
		model.options && (this.options = Object.freeze(
			Array.prototype.filter.call(model.options, option => option && option.label)
			.map(({ label, value, }) => Object.freeze({ label, value, }))
		));
		if (!model.hasOwnProperty('default')) {
			this.defaults = Object.freeze([ ]);
		} else if (Array.isArray(model.default)) {
			this.defaults = Object.freeze(model.default.map(Object.freeze));
			this.default = model.default[0];
		} else {
			this.default = Object.freeze(model.default);
			this.defaults = Object.freeze([ this.default ]);
		}

		model.restrict && (this.restrict = model.restrict === 'inherit' ? parent.restrict : new Restriction(this, model.restrict));

		this.children = new OptionList((model.children || [ ]).map(model => new Option(model, this)), this);

		context.options.set(this.path, this);
		return Object.freeze(this);
	}

	get value() { return Values.get(this).get(0); }
	set value(value) { return Values.get(this).set(0, value); }
	get values() { return Values.get(this); }
	set values(values) { return Values.get(this).replace(values); }
	reset() { return Values.get(this).reset(); }
	resetAll() {
		this.reset();
		this.children.forEach(child => child.resetAll());
	}

	whenTrue(listener) {
		const values = this.values;
		let listeners = OnTrue.get(this) || new Set;
		OnTrue.set(this, listeners);
		const added = listeners.add(listener);
		added && values.is && callAll([ listener, ], values.get(0), values, null, this.path);
		return added;
	}
	whenFalse(listener) {
		const values = this.values;
		let listeners = OnFalse.get(this) || new Set;
		OnFalse.set(this, listeners);
		const added = listeners.add(listener);
		added && !values.is && callAll([ listener, ], values.get(0), values, null, this.path);
		return added;
	}
	when(true_false) {
		true_false && true_false.true && this.whenTrue(true_false.true);
		true_false && true_false.false && this.whenFalse(true_false.false);
	}
	whenChange(listener) {
		const values = this.values;
		let listeners = OnChange.get(this) || new Set;
		OnChange.set(this, listeners);
		const added = listeners.add(listener);
		added && callAll([ listener, ], values.get(0), values, null, this.path);
		return added;
	}
	onChange(listener) {
		let listeners = OnChange.get(this) || new Set;
		OnChange.set(this, listeners);
		return listeners.add(listener);
	}
	onAnyChange(listener) {
		let listeners = OnAnyChange.get(this) || new Set;
		OnAnyChange.set(this, listeners);
		return listeners.add(listener);
	}
	offAnyChange(listener) {
		let listeners = OnAnyChange.get(this);
		return listeners && listeners.delete(listener);
	}

}

class OptionList extends Array {
	constructor(items, parent) {
		super();
		Object.defineProperty(this, 'parent', { value: parent, });
		items.forEach((item, index, array) => this[item.name] = this[index] = item);
		return Object.freeze(this);
	}
	static get [Symbol.species]() { return Array; }
}

class ValueList {
	constructor(parent, values) {
		this.parent = parent;
		Storage.set(this, context.storage);

		this.key = context.prefix + this.parent.path;
		Values.set(this, Object.freeze(values));
		const model = Models.get(parent);
		this.max = model.hasOwnProperty('maxLength') ? +model.maxLength : 1;
		this.min = model.hasOwnProperty('minLength') ? +model.minLength : +!model.hasOwnProperty('maxLength');
		return Object.freeze(this);
	}
	get current() { return Values.get(this); }
	get is() { return !!Values.get(this).find(x => x); }
	get(index) {
		return Values.get(this)[index];
	}
	set(index, value) {
		const values = Values.get(this).slice();
		values[index] = value;
		this.parent.restrict.validate(value, values, this.parent);
		return Storage.get(this).set({ [this.key]: values, });
	}
	replace(values) {
		if (values.length < this.min || values.length > this.max) {
			throw new Error('the number of values for the option "'+ this.key +'" must be between '+ this.min +' and '+ this.max);
		}
		this.parent.restrict && this.parent.restrict.validateAll(values, this.parent);
		return Storage.get(this).set({ [this.key]: values, });
	}
	splice(index, remove, ...insert) {
		const values = Values.get(this).slice();
		values.splice.apply(values, arguments);
		if (values.length < this.min || values.length > this.max) {
			throw new Error('the number of values for the option "'+ this.key +'" must be between '+ this.min +' and '+ this.max);
		}
		this.parent.restrict && this.parent.restrict.validateAll(insert, this.parent, index);
		return Storage.get(this).set({ [this.key]: values, });
	}
	reset() {
		return Storage.get(this).remove(this.key);
	}
}

class Restriction {
	constructor(parent, restrict) {
		this._parent = parent;
		const from = this.from = restrict.from;
		const to = this.to = restrict.to;
		const match = this.match = restrict.match && Object.freeze({
			exp: restrict.match.exp ? new RegExp(restrict.match.exp) : new RegExp(restrict.match.source, restrict.match.flags),
			message: restrict.match.message || 'This value must match '+ (restrict.match.exp || new RegExp(restrict.match.source, restrict.match.flags)),
		});
		const type = this.type = restrict.type;
		const isRegExp = this.isRegExp = restrict.isRegExp;
		const unique = this.unique = Object.freeze(restrict.unique);
		const checks = [ ];
		restrict.hasOwnProperty('from') && checks.push(value => value < from && ('This value must be at least '+ from));
		restrict.hasOwnProperty('to') && checks.push(value => value > to && ('This value can be at most '+ to));
		match && checks.push(value => !match.exp.test(value) && match.message);
		type && checks.push(value => typeof value !== type && ('This value must be of type "'+ type +'" but is "'+ (typeof value) +'"'));
		isRegExp && checks.push(value => void RegExp(value));
		parent.type !== 'interval' && restrict.hasOwnProperty('unique') && (() => {
			checks.push((value, values, option) => this._unique.map(other => {
				if (other === option) {
					return values.filter(v => v === value).length > 1 && 'This value must be unique winthin this option';
				}
				return other && other.values.current.indexOf(value) !== -1 && 'This value must be unique, but it is already used in "'+ other.title +'"';
			}).find(x => x));
		})();
		this.checks = Object.freeze(checks);
		return Object.freeze(this);
	}
	validate(value, values, option) {
		const message = this._parent.type === 'interval'
		? this.checks.map(check => typeof value === 'object' && (check(value.from) || check(value.to))).find(x => x)
		: this.checks.map(check => check(value, values, option)).find(x => x);
		if (message) { throw new Error(message); }
	}
	validateAll(values, option, offset = 0) {
		values.forEach((value, index) => { try {
			this.validate(value, values, option);
		} catch (error) {
			try { error.index = index + offset; } catch (e) { }
			throw error;
		} });
	}
	get _unique() {
		let options = _uniqueCache.get(this);
		if (options) { return options; }
		const paths = (typeof this.unique === 'string' ? [ this.unique, ] : this.unique || [ ]).map(path => path.split(/[\/\\]/));
		const result = new Set;
		paths.forEach(path => walk(this._parent, path));
		options = Object.freeze(Array.from(result));
		_uniqueCache.set(this, options);
		return options;

		function walk(option, path) {
			if (!path.length) { return result.add(option); }
			const segment = path.shift();
			switch (segment) {
				case '.': {
					walk(option, path);
				} break;
				case '..': {
					walk(option.parent, path);
				} break;
				case '*': {
					option.children.forEach(child => walk(child, path));
				} break;
				default: {
					walk(option.children[segment], path);
				} break;
			}
		}
	}
}

function callAll(callbacks, value, values, old, path) {
	callbacks && callbacks.forEach(listener => { try {
		listener(value, values, old);
	} catch (error) { console.error('Options change listener for "'+ path +'" threw', error); } });
}

function crawlDown(option, callback) {
	callback(option);
	option.children.forEach(option => crawlDown(option, callback));
}

function climbUp(option, callback) {
	callback(option);
	option.parent && climbUp(option.parent, callback);
}

function inContext(ctx, callback) {
	try {
		context = ctx;
		return callback();
	} finally {
		context = null;
	}
}

return class OptionsRoot {
	constructor({ model, prefix, storage, addChangeListener, removeChangeListener, }) {
		const options = this.options = new Map;
		this.prefix = prefix; this.storage = storage;
		inContext(this, () => this._shadow = new Option({ children: Array.isArray(model) ? model : [ model, ], }, null));
		this.children = this._shadow.children;
		this.onChange = this.onChange.bind(this);
		this._removeChangeListener = removeChangeListener;

		return storage.get(Array.from(options.keys()).map(path => prefix + path))
		.then(data => inContext(this, () => {
			const { hasOwnProperty, } = Object.prototype;
			options.forEach(option => Values.set(option, new ValueList(
				option,
				hasOwnProperty.call(data, prefix + option.path) ? data[prefix + option.path] : option.defaults
			)));
			addChangeListener && addChangeListener.call(this, this.onChange);

			return this;
		}));
	}

	onChange(key, values) {
		if (!key.startsWith(this.prefix) || this.destroyed) { return; }
		const path = key.slice(this.prefix.length);
		const option = this.options.get(path);
		!values && (values = option.defaults);
		const list = option.values;
		const old = Values.get(list);
		Values.set(list, values);

		const is = values.find(x => x);
		const was = old.find(x => x);

		callAll(OnChange.get(option), values[0], list, old, path);
		climbUp(option, option => callAll(OnAnyChange.get(option), values[0], list, old, path));
		is && !was && callAll(OnTrue.get(option), values[0], list, old, path);
		!is && was && callAll(OnFalse.get(option), values[0], list, old, path);
	}

	resetAll() {
		return this.storage.remove(Array.from(this.options.keys()));
	}

	onAnyChange() {
		return this._shadow.onAnyChange(...arguments);
	}

	destroy() {
		this.destroyed = true;
		crawlDown(this._shadow, option => callbackMaps.forEach(map => map.delete(option)));
		this._removeChangeListener && this._removeChangeListener(this.onChange);
	}
};

});
<|endoftext|>


<commit_before>'use strict';

const POLL_INTERVAL = 540; // 9min caching of netatmo api results - use config["ttl"] to override

var NodeCache = require("node-cache");

class NetatmoDevice {
	constructor(log, api, config) {
		this.api = api;
		this.log = log;
		var ttl = typeof config.ttl !== 'undefined' ? config.ttl : POLL_INTERVAL;
		ttl = Math.max(300,ttl);

        this.log.debug("Creating cache with ttl of "+ttl+"s");
		this.cache = new NodeCache({
			stdTTL: ttl
		});
		this.config = config;

		this.deviceData = null;
		this.accessories = [];

		this.runCheckInterval = setInterval(function() {
			this.log.debug("Executing Timed Refresh");
			this.refreshDeviceData(function(err, data) {
				if (this.accessories) {
					this.accessories.forEach(function(accessory) {
						accessory.notifyUpdate(data);
					}.bind(this));
				}
			}.bind(this), true);
		}.bind(this), ttl * 1000);
	}

	buildAccessoriesForDevices(callback) {
		var accessories = [];
		this.refreshDeviceData(function(err, data) {
			if (!err) {
				this.buildAccessories(callback);
			} else {
				callback(err, accessories);
			}
		}.bind(this),false);
	}

	refreshDeviceData(callback, force) {
		this.log.debug("Refreshing data for netatmo " + this.deviceType);
		this.cache.get((this.deviceType), function(err, data) {
			if (!err) {
				if (data == undefined || data === null || force === true) {
					this.log("Loading new data from API for: " + this.deviceType);
					this.loadDeviceData(function(err, data) {
						callback(err, data);
					}.bind(this));
				} else {
                    this.log.debug("Data from cache for: " + this.deviceType);
					this.deviceData = data;
					callback(null, data);
				}
			} else {
				callback(err, this.deviceData);
			}
		}.bind(this));
	}

	loadDeviceData(callback) {
		this.deviceData = null;
		callback("The abstract method loadDeviceData should be overridden", null);
	}

	buildAccessories(callback) {
		Object.keys(this.deviceData).forEach(function(key) {

			// key is id! Should go into blacklist / whitelist !
			var keep = true;

			if (this.config.whitelist && this.config.whitelist.length > 0) {
				keep = (this.config.whitelist.indexOf(key) > -1);
			}
			if (this.config.blacklist && this.config.blacklist.length > 0) {
				keep = keep && (this.config.blacklist.indexOf(key) < 0);
			}

			if (keep) {
				var accessory = this.buildAccessory(this.deviceData[key]);
				if(accessory) {
					this.log.debug("Did build accessory " + accessory.name);
					this.accessories.push(accessory);
				} else {
					this.log.debug("Skipped building accessory " + accessory.name);
				}
			}

		}.bind(this));
		callback(null, this.accessories);
	}

}

module.exports = NetatmoDevice;
<commit_msg>only timer-refresh and accessory build trigger real api-call<commit_after>'use strict';

const POLL_INTERVAL = 540; // 9min caching of netatmo api results - use config["ttl"] to override

var NodeCache = require("node-cache");

class NetatmoDevice {
	constructor(log, api, config) {
		this.api = api;
		this.log = log;
		var ttl = typeof config.ttl !== 'undefined' ? config.ttl : POLL_INTERVAL;
		ttl = Math.max(300,ttl);

        this.log.debug("Creating cache with ttl of "+ttl+"s");
		this.cache = new NodeCache({
			stdTTL: ttl
		});
		this.config = config;

		this.deviceData = null;
		this.accessories = [];

		this.runCheckInterval = setInterval(function() {
			this.log.debug("Executing Timed Refresh");
			this.refreshDeviceData(function(err, data) {
				if (this.accessories) {
					this.accessories.forEach(function(accessory) {
						accessory.notifyUpdate(data);
					}.bind(this));
				}
			}.bind(this), true);
		}.bind(this), ttl * 1000);
	}

	buildAccessoriesForDevices(callback) {
		var accessories = [];
		this.refreshDeviceData(function(err, data) {
			if (!err) {
				this.buildAccessories(callback);
			} else {
				callback(err, accessories);
			}
		}.bind(this),true);
	}

	refreshDeviceData(callback, force) {
		this.log.debug("Refreshing data for netatmo " + this.deviceType);
		this.cache.get((this.deviceType), function(err, data) {
			if (!err) {
				if (data == undefined || data === null || force === true) {
					this.log("Loading new data from API for: " + this.deviceType);
					if(force) {
						this.log.debug("Reloading forced for: " + this.deviceType);
						this.loadDeviceData(function(err, data) {
							callback(err, data);
						}.bind(this));
					} else {
						this.log.debug("No force, return old data for: " + this.deviceType);
						callback(null, this.deviceData);
					}
				} else {
                    this.log.debug("Data from cache for: " + this.deviceType);
					this.deviceData = data;
					callback(null, data);
				}
			} else {
				callback(err, this.deviceData);
			}
		}.bind(this));
	}

	loadDeviceData(callback) {
		this.deviceData = null;
		callback("The abstract method loadDeviceData should be overridden", null);
	}

	buildAccessories(callback) {
		Object.keys(this.deviceData).forEach(function(key) {

			// key is id! Should go into blacklist / whitelist !
			var keep = true;

			if (this.config.whitelist && this.config.whitelist.length > 0) {
				keep = (this.config.whitelist.indexOf(key) > -1);
			}
			if (this.config.blacklist && this.config.blacklist.length > 0) {
				keep = keep && (this.config.blacklist.indexOf(key) < 0);
			}

			if (keep) {
				var accessory = this.buildAccessory(this.deviceData[key]);
				if(accessory) {
					this.log.debug("Did build accessory " + accessory.name);
					this.accessories.push(accessory);
				} else {
					this.log.debug("Skipped building accessory " + accessory.name);
				}
			}

		}.bind(this));
		callback(null, this.accessories);
	}

}

module.exports = NetatmoDevice;
<|endoftext|>


<commit_before>import * as macro from 'vtk.js/Sources/macro';
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';

const { vtkErrorMacro, TYPED_ARRAYS } = macro;

// ----------------------------------------------------------------------------
// vtkCutter methods
// ----------------------------------------------------------------------------

function vtkCutter(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkCutter');

  // Capture "parentClass" api for internal use
  const superClass = { ...publicAPI };

  publicAPI.getMTime = () => {
    let mTime = superClass.getMTime();
    if (!model.cutFunction) {
      return mTime;
    }

    mTime = Math.max(mTime, model.cutFunction.getMTime());
    return mTime;
  };

  function dataSetCutter(input, output) {
    const numCells = input.getNumberOfCells();
    const points = input.getPoints();
    const pointsData = points.getData();
    const numPts = points.getNumberOfPoints();
    const newPointsData = [];
    const newLinesData = [];
    const newPolysData = [];

    if (!model.cutScalars || model.cutScalars.length < numPts) {
      model.cutScalars = new Float32Array(numPts);
    }

    // Loop over all points evaluating scalar function at each point
    let inOffset = 0;
    let outOffset = 0;
    while (inOffset < pointsData.length) {
      model.cutScalars[outOffset++] = model.cutFunction.evaluateFunction(
        pointsData[inOffset++],
        pointsData[inOffset++],
        pointsData[inOffset++]
      );
    }

    const dataCell = input.getPolys().getData();
    const crossedEdges = [];
    const x1 = new Array(3);
    const x2 = new Array(3);
    // Loop over all cells; get scalar values for all cell points
    // and process each cell.
    /* eslint-disable no-continue */
    for (let cellId = 0; cellId < numCells; cellId++) {
      const nbPointsInCell = dataCell[0];
      // Check that cells have at least 3 points
      if (nbPointsInCell <= 2) {
        continue;
      }

      // Get associated scalar of points that constitute the current cell
      const cellPointsScalars = [];
      const valuesInCell = nbPointsInCell + 1; // first value is size
      let pointIndex;
      for (
        let i = valuesInCell * cellId + 1;
        i < valuesInCell * (cellId + 1);
        i++
      ) {
        pointIndex = dataCell[i];
        cellPointsScalars.push(model.cutScalars[pointIndex]);
      }

      // Check if all cell points are on same side (same side == cell not crossed by cut function)
      // TODO: won't work if one point scalar is = 0 ?
      const sideFirstPoint = cellPointsScalars[0] > 0;
      let allPointsSameSide = true;
      for (let i = 1; i < cellPointsScalars.length; i++) {
        const sideCurrentPoint = cellPointsScalars[i] > 0;
        if (sideCurrentPoint !== sideFirstPoint) {
          allPointsSameSide = false;
          break;
        }
      }

      // Go to next cell if cell is not crossed by cut function
      if (allPointsSameSide) {
        continue;
      }

      // Get id of points that constitute the current cell
      const cellPointsID = [];
      for (
        let i = valuesInCell * cellId + 1;
        i < valuesInCell * (cellId + 1);
        i++
      ) {
        cellPointsID.push(dataCell[i]);
      }

      // Find and compute edges which intersect cells
      const intersectedEdgesList = [];
      for (let i = 0; i < cellPointsID.length; i++) {
        const idNext = i + 1 === cellPointsID.length ? 0 : i + 1;

        // Go to next edge if edge is not crossed
        // TODO: in most come cases, (numberOfPointsInCell - 1) or 0 edges of the cell
        // will be crossed, but if it crosses right at a point, it could be intersecting
        // with (numberOfPoints) or 1 edge(s). Do we account for that?
        const signPoint0 = cellPointsScalars[i] > 0;
        const signPoint1 = cellPointsScalars[idNext] > 0;
        if (signPoint1 === signPoint0) {
          continue;
        }

        // Compute preferred interpolation direction
        let e1 = i;
        let e2 = idNext;
        let deltaScalar = cellPointsScalars[e2] - cellPointsScalars[e1];
        if (deltaScalar <= 0) {
          e1 = idNext;
          e2 = i;
          deltaScalar *= -1;
        }

        // linear interpolation
        let t = 0.0;
        if (deltaScalar !== 0.0) {
          t = (model.cutValue - cellPointsScalars[e1]) / deltaScalar;
        }

        // points position
        const pointID1 = cellPointsID[e1];
        const pointID2 = cellPointsID[e2];
        x1[0] = pointsData[pointID1 * 3];
        x1[1] = pointsData[pointID1 * 3 + 1];
        x1[2] = pointsData[pointID1 * 3 + 2];
        x2[0] = pointsData[pointID2 * 3];
        x2[1] = pointsData[pointID2 * 3 + 1];
        x2[2] = pointsData[pointID2 * 3 + 2];

        // Compute the intersected point on edge
        const computedIntersectedPoint = [
          x1[0] + t * (x2[0] - x1[0]),
          x1[1] + t * (x2[1] - x1[1]),
          x1[2] + t * (x2[2] - x1[2]),
        ];

        // Keep track of it
        intersectedEdgesList.push({
          pointEdge1: pointID1, // id of one point of the edge
          pointEdge2: pointID2, // id of one point of the edge
          intersectedPoint: computedIntersectedPoint, // 3D coordinate of points that intersected edge
          newPointID: -1, // id of the intersected point when it will be added into vtkPoints
        });
      }

      // Add points into newPointList
      for (let i = 0; i < intersectedEdgesList.length; i++) {
        const intersectedEdge = intersectedEdgesList[i];
        let alreadyAdded = false;
        // Check if point/edge already added
        for (let j = 0; j < crossedEdges.length; j++) {
          const crossedEdge = crossedEdges[j];
          const sameEdge =
            intersectedEdge.pointEdge1 === crossedEdge.pointEdge1 &&
            intersectedEdge.pointEdge2 === crossedEdge.pointEdge2;
          const samePoint =
            intersectedEdge.intersectedPoint[0] ===
              crossedEdge.intersectedPoint[0] &&
            intersectedEdge.intersectedPoint[1] ===
              crossedEdge.intersectedPoint[1] &&
            intersectedEdge.intersectedPoint[2] ===
              crossedEdge.intersectedPoint[2];
          if (sameEdge || samePoint) {
            alreadyAdded = true;
            intersectedEdgesList[i].newPointID = crossedEdges[j].newPointID;
            break;
          }
        }
        if (!alreadyAdded) {
          newPointsData.push(intersectedEdge.intersectedPoint[0]);
          newPointsData.push(intersectedEdge.intersectedPoint[1]);
          newPointsData.push(intersectedEdge.intersectedPoint[2]);
          intersectedEdgesList[i].newPointID = newPointsData.length / 3 - 1;
          crossedEdges.push(intersectedEdgesList[i]);
        }
      }

      // Store cells
      const cellSize = intersectedEdgesList.length;
      if (cellSize === 2) {
        newLinesData.push(
          cellSize,
          intersectedEdgesList[0].newPointID,
          intersectedEdgesList[1].newPointID
        );
      } else if (cellSize > 2) {
        newPolysData.push(cellSize);
        intersectedEdgesList.forEach((edge) => {
          newPolysData.push(edge.newPointID);
        });
      }
    }

    // Set points
    const outputPoints = output.getPoints();
    outputPoints.setData(
      TYPED_ARRAYS[points.getDataType()].from(newPointsData),
      3
    );

    // Set lines
    if (newLinesData.length !== 0) {
      output.getLines().setData(Uint16Array.from(newLinesData));
    }

    // Set polys
    if (newPolysData.length !== 0) {
      output.getPolys().setData(Uint16Array.from(newPolysData));
    }
  }

  // expose requestData
  publicAPI.requestData = (inData, outData) => {
    // implement requestData
    const input = inData[0];

    if (!input) {
      vtkErrorMacro('Invalid or missing input');
      return;
    }

    if (!model.cutFunction) {
      vtkErrorMacro('Missing cut function');
      return;
    }

    const output = vtkPolyData.newInstance();

    dataSetCutter(input, output);

    outData[0] = output;
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  cutFunction: null, // support method with evaluateFunction method
  cutScalars: null,
  cutValue: 0.0,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Make this a VTK object
  macro.obj(publicAPI, model);

  // Also make it an algorithm with one input and one output
  macro.algo(publicAPI, model, 1, 1);

  // Set implicit function use to cut the input data (is vtkPlane)
  macro.setGet(publicAPI, model, ['cutFunction', 'cutValue']);

  // Object specific methods
  vtkCutter(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkCutter');

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<commit_msg>fix(Cutter): Incorrect cell array traversal<commit_after>import * as macro from 'vtk.js/Sources/macro';
import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';

const { vtkErrorMacro, TYPED_ARRAYS } = macro;

// ----------------------------------------------------------------------------
// vtkCutter methods
// ----------------------------------------------------------------------------

function vtkCutter(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkCutter');

  // Capture "parentClass" api for internal use
  const superClass = { ...publicAPI };

  publicAPI.getMTime = () => {
    let mTime = superClass.getMTime();
    if (!model.cutFunction) {
      return mTime;
    }

    mTime = Math.max(mTime, model.cutFunction.getMTime());
    return mTime;
  };

  function dataSetCutter(input, output) {
    const numCells = input.getNumberOfCells();
    const points = input.getPoints();
    const pointsData = points.getData();
    const numPts = points.getNumberOfPoints();
    const newPointsData = [];
    const newLinesData = [];
    const newPolysData = [];

    if (!model.cutScalars || model.cutScalars.length < numPts) {
      model.cutScalars = new Float32Array(numPts);
    }

    // Loop over all points evaluating scalar function at each point
    let inOffset = 0;
    let outOffset = 0;
    while (inOffset < pointsData.length) {
      model.cutScalars[outOffset++] = model.cutFunction.evaluateFunction(
        pointsData[inOffset++],
        pointsData[inOffset++],
        pointsData[inOffset++]
      );
    }

    const dataCell = input.getPolys().getData();
    const crossedEdges = [];
    const x1 = new Array(3);
    const x2 = new Array(3);
    // Loop over all cells; get scalar values for all cell points
    // and process each cell.
    /* eslint-disable no-continue */
    let cellOffset = 0;
    let prevCellSize = -1;
    for (let cellId = 0; cellId < numCells; cellId++) {
      cellOffset += prevCellSize + 1; // account for length of cell

      const nbPointsInCell = dataCell[cellOffset];
      prevCellSize = nbPointsInCell;

      // Check that cells have at least 3 points
      if (nbPointsInCell <= 2) {
        continue;
      }
      const nextCellOffset = cellOffset + 1 + nbPointsInCell;

      // Get associated scalar of points that constitute the current cell
      const cellPointsScalars = [];
      let pointIndex;
      for (let i = cellOffset + 1; i < nextCellOffset; i++) {
        pointIndex = dataCell[i];
        cellPointsScalars.push(model.cutScalars[pointIndex]);
      }

      // Check if all cell points are on same side (same side == cell not crossed by cut function)
      // TODO: won't work if one point scalar is = 0 ?
      const sideFirstPoint = cellPointsScalars[0] > 0;
      let allPointsSameSide = true;
      for (let i = 1; i < cellPointsScalars.length; i++) {
        const sideCurrentPoint = cellPointsScalars[i] > 0;
        if (sideCurrentPoint !== sideFirstPoint) {
          allPointsSameSide = false;
          break;
        }
      }

      // Go to next cell if cell is not crossed by cut function
      if (allPointsSameSide) {
        continue;
      }

      // Get id of points that constitute the current cell
      const cellPointsID = [];
      for (let i = cellOffset + 1; i < nextCellOffset; i++) {
        cellPointsID.push(dataCell[i]);
      }

      // Find and compute edges which intersect cells
      const intersectedEdgesList = [];
      for (let i = 0; i < cellPointsID.length; i++) {
        const idNext = i + 1 === cellPointsID.length ? 0 : i + 1;

        // Go to next edge if edge is not crossed
        // TODO: in most come cases, (numberOfPointsInCell - 1) or 0 edges of the cell
        // will be crossed, but if it crosses right at a point, it could be intersecting
        // with (numberOfPoints) or 1 edge(s). Do we account for that?
        const signPoint0 = cellPointsScalars[i] > 0;
        const signPoint1 = cellPointsScalars[idNext] > 0;
        if (signPoint1 === signPoint0) {
          continue;
        }

        // Compute preferred interpolation direction
        let e1 = i;
        let e2 = idNext;
        let deltaScalar = cellPointsScalars[e2] - cellPointsScalars[e1];
        if (deltaScalar <= 0) {
          e1 = idNext;
          e2 = i;
          deltaScalar *= -1;
        }

        // linear interpolation
        let t = 0.0;
        if (deltaScalar !== 0.0) {
          t = (model.cutValue - cellPointsScalars[e1]) / deltaScalar;
        }

        // points position
        const pointID1 = cellPointsID[e1];
        const pointID2 = cellPointsID[e2];
        x1[0] = pointsData[pointID1 * 3];
        x1[1] = pointsData[pointID1 * 3 + 1];
        x1[2] = pointsData[pointID1 * 3 + 2];
        x2[0] = pointsData[pointID2 * 3];
        x2[1] = pointsData[pointID2 * 3 + 1];
        x2[2] = pointsData[pointID2 * 3 + 2];

        // Compute the intersected point on edge
        const computedIntersectedPoint = [
          x1[0] + t * (x2[0] - x1[0]),
          x1[1] + t * (x2[1] - x1[1]),
          x1[2] + t * (x2[2] - x1[2]),
        ];

        // Keep track of it
        intersectedEdgesList.push({
          pointEdge1: pointID1, // id of one point of the edge
          pointEdge2: pointID2, // id of one point of the edge
          intersectedPoint: computedIntersectedPoint, // 3D coordinate of points that intersected edge
          newPointID: -1, // id of the intersected point when it will be added into vtkPoints
        });
      }

      // Add points into newPointList
      for (let i = 0; i < intersectedEdgesList.length; i++) {
        const intersectedEdge = intersectedEdgesList[i];
        let alreadyAdded = false;
        // Check if point/edge already added
        for (let j = 0; j < crossedEdges.length; j++) {
          const crossedEdge = crossedEdges[j];
          const sameEdge =
            intersectedEdge.pointEdge1 === crossedEdge.pointEdge1 &&
            intersectedEdge.pointEdge2 === crossedEdge.pointEdge2;
          const samePoint =
            intersectedEdge.intersectedPoint[0] ===
              crossedEdge.intersectedPoint[0] &&
            intersectedEdge.intersectedPoint[1] ===
              crossedEdge.intersectedPoint[1] &&
            intersectedEdge.intersectedPoint[2] ===
              crossedEdge.intersectedPoint[2];
          if (sameEdge || samePoint) {
            alreadyAdded = true;
            intersectedEdgesList[i].newPointID = crossedEdges[j].newPointID;
            break;
          }
        }
        if (!alreadyAdded) {
          newPointsData.push(intersectedEdge.intersectedPoint[0]);
          newPointsData.push(intersectedEdge.intersectedPoint[1]);
          newPointsData.push(intersectedEdge.intersectedPoint[2]);
          intersectedEdgesList[i].newPointID = newPointsData.length / 3 - 1;
          crossedEdges.push(intersectedEdgesList[i]);
        }
      }

      // Store cells
      const cellSize = intersectedEdgesList.length;
      if (cellSize === 2) {
        newLinesData.push(
          cellSize,
          intersectedEdgesList[0].newPointID,
          intersectedEdgesList[1].newPointID
        );
      } else if (cellSize > 2) {
        newPolysData.push(cellSize);
        intersectedEdgesList.forEach((edge) => {
          newPolysData.push(edge.newPointID);
        });
      }
    }

    // Set points
    const outputPoints = output.getPoints();
    outputPoints.setData(
      TYPED_ARRAYS[points.getDataType()].from(newPointsData),
      3
    );

    // Set lines
    if (newLinesData.length !== 0) {
      output.getLines().setData(Uint16Array.from(newLinesData));
    }

    // Set polys
    if (newPolysData.length !== 0) {
      output.getPolys().setData(Uint16Array.from(newPolysData));
    }
  }

  // expose requestData
  publicAPI.requestData = (inData, outData) => {
    // implement requestData
    const input = inData[0];

    if (!input) {
      vtkErrorMacro('Invalid or missing input');
      return;
    }

    if (!model.cutFunction) {
      vtkErrorMacro('Missing cut function');
      return;
    }

    const output = vtkPolyData.newInstance();

    dataSetCutter(input, output);

    outData[0] = output;
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  cutFunction: null, // support method with evaluateFunction method
  cutScalars: null,
  cutValue: 0.0,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Make this a VTK object
  macro.obj(publicAPI, model);

  // Also make it an algorithm with one input and one output
  macro.algo(publicAPI, model, 1, 1);

  // Set implicit function use to cut the input data (is vtkPlane)
  macro.setGet(publicAPI, model, ['cutFunction', 'cutValue']);

  // Object specific methods
  vtkCutter(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = macro.newInstance(extend, 'vtkCutter');

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<|endoftext|>


<commit_before>var sys  = require('sys');
var fs   = require('fs');
var path = require('path');
var net  = require('net');
var ReplicaMember = require('./replica_member.js');
var Client        = require('./client.js');
var Database      = require('./database.js').Database;
var Group         = require('./group.js');

function Server(dbpath) {
	var self = this;
	this.dbpath = dbpath;
	this.replicaMembers = {};
	this.replicaMemberCount = 0;
	this.lastReplicaMemberId = 0;
	this.clients = {};
	this.clientCount = 0;
	this.lastClientId = 0;
	this.database = new Database(dbpath);
}

Server.prototype.listen = function(port, callback) {
	var self = this;
	this.database.reload(function(err) {
		if (err) {
			callback(err);
		} else {
			self.server = net.createServer(function(socket) {
				self._onNewClientSocket(socket);
			});
			self.server.listen(port);
			callback();
		}
	});
}

/* New clients first go through a handshake process. If the handshake is
 * successful they will be identified as either a normal client or a replica
 * member client and added to the corresponding data structures.
 */
Server.prototype._onNewClientSocket = function(socket) {
	var self = this;
	var handshakeState = {
		socket: socket,
		buffer: ''
	};
	var handshakeMessage = {
		protocolMajor: 1,
		protocolMinor: 0,
		serverName: "OptapDB/1.0"
	};
	socket.setNoDelay();
	socket.setKeepAlive(true);
	writeMessage(socket, handshakeMessage);
	socket.on('data', function(data) {
		self._onClientHandshake(handshakeState, data);
	});
	socket.on('end', function() {
		socket.destroy();
	})
}

Server.prototype._onClientHandshake = function(state, data) {
	var i, reply;
	var found = false;

	// Consume everything until newline.
	for (i = 0; i < data.length && !found; i++) {
		if (data[i] == 10) {
			found = true;
		}
	}
	state.buffer += data.toString('utf8', 0, i);

	if (found) {
		/* Handshake complete: we got a complete line,
		 * which should contain JSON. Parse and process it.
		 */
		try {
			reply = parseJsonObject(state.buffer);
		} catch (e) {
			disconnectWithError(state.socket,
				'Command cannot be parsed: ' + e.message);
			return;
		}

		var self = this;
		var remainingData = data.slice(i);
		state.socket.removeAllListeners('data');
		state.socket.removeAllListeners('end');
		logInput(state.buffer);

		/* Now identify client and add it to corresponding data structures. */

		if (reply.identity == 'replica-member') {
			var replicaMember = new ReplicaMember.ReplicaMember(state.socket,
				this.lastReplicaMemberId);
			this.replicaMembers[this.lastReplicaMemberId] = replicaMember;
			this.replicaMemberCount++;
			this.lastReplicaMemberId++;

			reply = { status: 'ok' };
			writeMessage(state.socket, reply);
			state.socket.on('data', function(data) {
				self._onReplicaMemberData(replicaMember, data);
			});
			state.socket.on('close', function() {
				delete self.replicaMembers[replicaMember.id];
				self.replicaMemberCount--;
			});
			if (remainingData.length > 0) {
				this._onReplicaMemberData(replicaMember, remainingData);
			}
		} else {
			var client = new Client.Client(state.socket,
				this.lastClientId);
			this.clients[this.lastClientId] = client;
			this.clientCount++;
			this.lastClientId++;

			reply = { status: 'ok' };
			writeMessage(state.socket, reply);
			state.socket.on('data', function(data) {
				self._onClientData(client, data);
			});
			state.socket.on('close', function() {
				delete self.clients[client.id];
				self.clientCount--;
			});
			if (remainingData.length > 0) {
				this._onClientData(client, remainingData);
			}
		}
	}
}

Server.prototype._onReplicaMemberData = function(replicaMember, data) {
	sys.print("new replica member\n");
}

Server.prototype._onClientData = function(client, data) {
	var pos = 0, found = false;
	var i, command, result;

	while (pos < data.length && client.connected()) {
		switch (client.state) {
		case Client.RECEIVING_COMMAND:
			found = false;
			for (i = pos; i < data.length && !found; i++) {
				if (data[i] == 10) {
					found = true;
				}
			}
			client.commandBuffer += data.toString('utf8', pos, i);
			pos = i;
			if (found) {
				try {
					logInput(client.commandBuffer);
					command = parseJsonObject(client.commandBuffer);
				} catch (e) {
					disconnectClientWithError(client,
						"Cannot parse command " +
						JSON.stringify(client.commandBuffer) +
						": " + e.message);
					return;
				}

				if (getType(command.command) != 'string') {
					disconnectClientWithError(client,
						"Command must have a 'command' string field");
					return;
				}

				client.commandBuffer = '';
				if (this._onClientCommand(client, command, data.slice(pos))) {
					/* _onClientCommand assumes control. */
					logInput("onClientClient assumes control");
					return;
				}
			}
			break;

		case Client.RECEIVING_ADD_DATA:
			this._onClientAddData(client, data.slice(pos));
			/* We stop processing here. _onClientAddData() will take
			 * care of changing state as well as processing the
			 * remaining socket data.
			 */
			return;

		default:
			throw 'BUG: unknown client state ' + client.state;
		}
	}
}

Server.prototype._onClientCommand = function(client, command, remainingSocketData) {
	if (command.command == 'add') {
		if (getType(command.group) != 'string') {
			disconnectClientWithError(client, "Expecting a 'group' string field");
			return false;
		} else if (!Group.validateGroupName(command.group)) {
			disconnectClientWithError(client, "Invalid group name");
			return false;
		} else if (getType(command.timestamp) != 'number') {
			disconnectClientWithError(client, "Expecting a 'timestamp' number field");
			return false;
		} else if (getType(command.size) != 'number') {
			disconnectClientWithError(client, "Expecting a 'size' number field");
			return false;
		} else if (getType(command.opid) != 'number') {
			disconnectClientWithError(client, "Expecting an 'opid' number field");
			return false;
		} else if (command.size > 1024 * 1024) {
			disconnectClientWithError(client, "Size may not be larger than 1 MB");
			return false;
		} else if (client.results[command.opid]) {
			disconnectClientWithError(client, "The given opid is already given");
			return false;
		}
		client.currentCommand = command;
		client.currentCommand.remaining = command.size;
		client.state = Client.RECEIVING_ADD_DATA;
		return false;

	} else if (command.command == 'results') {
		if (command.discard) {
			client.results = {};
			return false;
		}

		function writeResults() {
			writeMessage(client.socket, { status: 'ok', results: client.results });
			client.results = {};
		}

		if (client.operationsInProgress == 0) {
			writeResults();
			return false;
		} else {
			/* Block until all background operations are ready. */
			var self = this;
			client.socket.pause();
			client.once('operationsDrained', function() {
				writeResults();
				client.socket.resume();
				if (remainingSocketData.length > 0) {
					self._onClientData(client, remainingSocketData);
				}
			});
			return true;
		}

	} else if (command.command == 'get') {
		if (getType(command.group) != 'string') {
			disconnectClientWithError(client, "Expecting a 'group' string field");
			return false;
		} else if (getType(command.timestamp) != 'number') {
			disconnectClientWithError(client, "Expecting a 'timestamp' number field");
			return false;
		} else if (getType(command.key) != 'string') {
			disconnectClientWithError(client, "Expecting a 'key' string field");
			return false;
		}
		return false;

	} else if (command.command == 'delete') {
		return false;

	} else if (command.command == 'ping') {
		writeMessage(client.socket, { status: 'ok' });
		return false;

	} else {
		disconnectClientWithError(client, "Unknown command");
		return false;
	}
}

Server.prototype._onClientAddData = function(client, data) {
	var self = this;
	var consumed = 0;
	var done, slice, filename;

	consumed = min(client.currentCommand.remaining, data.length);
	client.currentCommand.remaining -= consumed;
	done = client.currentCommand.remaining == 0;
	slice = data.slice(0, consumed);
	client.addBuffers.push(slice);
	client.addChecksum.update(slice);

	if (done) {
		client.incOperations();
		opid = client.currentCommand.opid;

		function added(err, offset) {
			if (!client.connected()) {
				console.err("Client no longer connected");
				return;
			} else if (err) {
				disconnectClientWithError(client, "I/O error: " + err.message);
				return;
			}

			client.results[opid] = { status: 'ok', offset: offset };
			client.decOperations();
			client.socket.resume();
			client.state = Client.RECEIVING_COMMAND;

			/* There was remaining socket data that isn't
			 * part of the 'add' command body, so process
			 * that now.
			 */
			if (consumed != data.length) {
				self._onClientData(client, data.slice(consumed));
			}
		}

		client.addChecksum.finalize();
		this.database.add(client.currentCommand.group,
			client.currentCommand.timestamp,
			client.addBuffers,
			client.addChecksum.toBuffer(),
			added);
		client.currentCommand = undefined;
		client.addBuffers = [];
		client.addChecksum.reset();
		/* Data hasn't been written to disk yet so stop
		 * accepting client data until we're done.
		 */
		client.socket.pause();
	}
	/* else: we've consumed all data */
}

function logInput(data) {
	//console.log("<<", data);
}

function logOutput(data) {
	//console.log(">>", data);
}

function writeMessage(socket, object, callback) {
	var data = JSON.stringify(object);
	data += "\n";
	socket.write(data, callback);
	logOutput(data);
}

function disconnectClientWithError(client, message) {
	disconnectWithError(client.socket, message);
	client.state = Client.DISCONNECTED;
}

function disconnectWithError(socket, message) {
	var reply = {
		status: 'error',
		message: message
	};
	writeMessage(socket, reply, function() {
		socket.destroy();
	});
	socket.setTimeout(20000, function() {
		socket.destroy();
	});
}

function min(a, b) {
	if (a < b) {
		return a;
	} else {
		return b;
	}
}

function parseJsonObject(str) {
	var object = JSON.parse(str);
	if (getType(object) != 'object') {
		throw new SyntaxError("Expected an object value, but got a " + getType(object));
	}
	return object;
}

function getType(value) {
	var typeName = typeof(value);
	if (typeName == 'object') {
		if (value) {
			if (value instanceof Array) {
				return 'array';
			} else {
				return typeName;
			}
		} else {
			return 'null';
		}
	} else {
		return typeName;
	}
}

exports.Server = Server;
new Server("db").listen(3000, function(err) {
	if (err) {
		sys.print(err, "\n");
	}
});
<commit_msg>Database.add() may not call the callback in the next tick so take this into account.<commit_after>var sys  = require('sys');
var fs   = require('fs');
var path = require('path');
var net  = require('net');
var ReplicaMember = require('./replica_member.js');
var Client        = require('./client.js');
var Database      = require('./database.js').Database;
var Group         = require('./group.js');

function Server(dbpath) {
	var self = this;
	this.dbpath = dbpath;
	this.replicaMembers = {};
	this.replicaMemberCount = 0;
	this.lastReplicaMemberId = 0;
	this.clients = {};
	this.clientCount = 0;
	this.lastClientId = 0;
	this.database = new Database(dbpath);
}

Server.prototype.listen = function(port, callback) {
	var self = this;
	this.database.reload(function(err) {
		if (err) {
			callback(err);
		} else {
			self.server = net.createServer(function(socket) {
				self._onNewClientSocket(socket);
			});
			self.server.listen(port);
			callback();
		}
	});
}

/* New clients first go through a handshake process. If the handshake is
 * successful they will be identified as either a normal client or a replica
 * member client and added to the corresponding data structures.
 */
Server.prototype._onNewClientSocket = function(socket) {
	var self = this;
	var handshakeState = {
		socket: socket,
		buffer: ''
	};
	var handshakeMessage = {
		protocolMajor: 1,
		protocolMinor: 0,
		serverName: "OptapDB/1.0"
	};
	socket.setNoDelay();
	socket.setKeepAlive(true);
	writeMessage(socket, handshakeMessage);
	socket.on('data', function(data) {
		self._onClientHandshake(handshakeState, data);
	});
	socket.on('end', function() {
		socket.destroy();
	})
}

Server.prototype._onClientHandshake = function(state, data) {
	var i, reply;
	var found = false;

	// Consume everything until newline.
	for (i = 0; i < data.length && !found; i++) {
		if (data[i] == 10) {
			found = true;
		}
	}
	state.buffer += data.toString('utf8', 0, i);

	if (found) {
		/* Handshake complete: we got a complete line,
		 * which should contain JSON. Parse and process it.
		 */
		try {
			reply = parseJsonObject(state.buffer);
		} catch (e) {
			disconnectWithError(state.socket,
				'Command cannot be parsed: ' + e.message);
			return;
		}

		var self = this;
		var remainingData = data.slice(i);
		state.socket.removeAllListeners('data');
		state.socket.removeAllListeners('end');
		logInput(state.buffer);

		/* Now identify client and add it to corresponding data structures. */

		if (reply.identity == 'replica-member') {
			var replicaMember = new ReplicaMember.ReplicaMember(state.socket,
				this.lastReplicaMemberId);
			this.replicaMembers[this.lastReplicaMemberId] = replicaMember;
			this.replicaMemberCount++;
			this.lastReplicaMemberId++;

			reply = { status: 'ok' };
			writeMessage(state.socket, reply);
			state.socket.on('data', function(data) {
				self._onReplicaMemberData(replicaMember, data);
			});
			state.socket.on('close', function() {
				delete self.replicaMembers[replicaMember.id];
				self.replicaMemberCount--;
			});
			if (remainingData.length > 0) {
				this._onReplicaMemberData(replicaMember, remainingData);
			}
		} else {
			var client = new Client.Client(state.socket,
				this.lastClientId);
			this.clients[this.lastClientId] = client;
			this.clientCount++;
			this.lastClientId++;

			reply = { status: 'ok' };
			writeMessage(state.socket, reply);
			state.socket.on('data', function(data) {
				self._onClientData(client, data);
			});
			state.socket.on('close', function() {
				delete self.clients[client.id];
				self.clientCount--;
			});
			if (remainingData.length > 0) {
				this._onClientData(client, remainingData);
			}
		}
	}
}

Server.prototype._onReplicaMemberData = function(replicaMember, data) {
	sys.print("new replica member\n");
}

Server.prototype._onClientData = function(client, data) {
	var pos = 0, found = false;
	var i, command, result;

	while (pos < data.length && client.connected()) {
		switch (client.state) {
		case Client.RECEIVING_COMMAND:
			found = false;
			for (i = pos; i < data.length && !found; i++) {
				if (data[i] == 10) {
					found = true;
				}
			}
			client.commandBuffer += data.toString('utf8', pos, i);
			pos = i;
			if (found) {
				try {
					logInput(client.commandBuffer);
					command = parseJsonObject(client.commandBuffer);
				} catch (e) {
					disconnectClientWithError(client,
						"Cannot parse command " +
						JSON.stringify(client.commandBuffer) +
						": " + e.message);
					return;
				}

				if (getType(command.command) != 'string') {
					disconnectClientWithError(client,
						"Command must have a 'command' string field");
					return;
				}

				client.commandBuffer = '';
				if (this._onClientCommand(client, command, data.slice(pos))) {
					/* _onClientCommand assumes control. */
					logInput("onClientClient assumes control");
					return;
				}
			}
			break;

		case Client.RECEIVING_ADD_DATA:
			this._onClientAddData(client, data.slice(pos));
			/* We stop processing here. _onClientAddData() will take
			 * care of changing state as well as processing the
			 * remaining socket data.
			 */
			return;

		default:
			throw 'BUG: unknown client state ' + client.state;
		}
	}
}

Server.prototype._onClientCommand = function(client, command, remainingSocketData) {
	if (command.command == 'add') {
		if (getType(command.group) != 'string') {
			disconnectClientWithError(client, "Expecting a 'group' string field");
			return false;
		} else if (!Group.validateGroupName(command.group)) {
			disconnectClientWithError(client, "Invalid group name");
			return false;
		} else if (getType(command.timestamp) != 'number') {
			disconnectClientWithError(client, "Expecting a 'timestamp' number field");
			return false;
		} else if (getType(command.size) != 'number') {
			disconnectClientWithError(client, "Expecting a 'size' number field");
			return false;
		} else if (getType(command.opid) != 'number') {
			disconnectClientWithError(client, "Expecting an 'opid' number field");
			return false;
		} else if (command.size > 1024 * 1024) {
			disconnectClientWithError(client, "Size may not be larger than 1 MB");
			return false;
		} else if (client.results[command.opid]) {
			disconnectClientWithError(client, "The given opid is already given");
			return false;
		}
		client.currentCommand = command;
		client.currentCommand.remaining = command.size;
		client.state = Client.RECEIVING_ADD_DATA;
		return false;

	} else if (command.command == 'results') {
		if (command.discard) {
			client.results = {};
			return false;
		}

		function writeResults() {
			writeMessage(client.socket, { status: 'ok', results: client.results });
			client.results = {};
		}

		if (client.operationsInProgress == 0) {
			writeResults();
			return false;
		} else {
			/* Block until all background operations are ready. */
			var self = this;
			client.socket.pause();
			client.once('operationsDrained', function() {
				writeResults();
				client.socket.resume();
				if (remainingSocketData.length > 0) {
					self._onClientData(client, remainingSocketData);
				}
			});
			return true;
		}

	} else if (command.command == 'get') {
		if (getType(command.group) != 'string') {
			disconnectClientWithError(client, "Expecting a 'group' string field");
			return false;
		} else if (getType(command.timestamp) != 'number') {
			disconnectClientWithError(client, "Expecting a 'timestamp' number field");
			return false;
		} else if (getType(command.key) != 'string') {
			disconnectClientWithError(client, "Expecting a 'key' string field");
			return false;
		}
		return false;

	} else if (command.command == 'delete') {
		return false;

	} else if (command.command == 'ping') {
		writeMessage(client.socket, { status: 'ok' });
		return false;

	} else {
		disconnectClientWithError(client, "Unknown command");
		return false;
	}
}

Server.prototype._onClientAddData = function(client, data) {
	var self = this;
	var consumed = 0;
	var done, slice, filename;

	consumed = min(client.currentCommand.remaining, data.length);
	client.currentCommand.remaining -= consumed;
	done = client.currentCommand.remaining == 0;
	slice = data.slice(0, consumed);
	client.addBuffers.push(slice);
	client.addChecksum.update(slice);

	if (done) {
		client.incOperations();
		opid = client.currentCommand.opid;

		function added(err, offset) {
			if (!client.connected()) {
				console.err("Client no longer connected");
				return;
			} else if (err) {
				disconnectClientWithError(client, "I/O error: " + err.message);
				return;
			}

			client.results[opid] = { status: 'ok', offset: offset };
			client.decOperations();
			client.socket.resume();
			client.state = Client.RECEIVING_COMMAND;

			/* There was remaining socket data that isn't
			 * part of the 'add' command body, so process
			 * that now.
			 */
			if (consumed != data.length) {
				self._onClientData(client, data.slice(consumed));
			}
		}

		var group = client.currentCommand.group;
		var timestamp = client.currentCommand.timestamp;
		var buffers = client.addBuffers;
		var checksumBuffer = client.addChecksum.toBuffer();

		client.addChecksum.finalize();
		client.currentCommand = undefined;
		client.addBuffers = [];
		client.addChecksum.reset();
		/* Data hasn't been written to disk yet so stop
		 * accepting client data until we're done.
		 */
		client.socket.pause();

		this.database.add(group, timestamp, buffers, checksumBuffer, added);
	}
	/* else: we've consumed all data */
}

function logInput(data) {
	//console.log("<<", data);
}

function logOutput(data) {
	//console.log(">>", data);
}

function writeMessage(socket, object, callback) {
	var data = JSON.stringify(object);
	data += "\n";
	socket.write(data, callback);
	logOutput(data);
}

function disconnectClientWithError(client, message) {
	disconnectWithError(client.socket, message);
	client.state = Client.DISCONNECTED;
}

function disconnectWithError(socket, message) {
	var reply = {
		status: 'error',
		message: message
	};
	writeMessage(socket, reply, function() {
		socket.destroy();
	});
	socket.setTimeout(20000, function() {
		socket.destroy();
	});
}

function min(a, b) {
	if (a < b) {
		return a;
	} else {
		return b;
	}
}

function parseJsonObject(str) {
	var object = JSON.parse(str);
	if (getType(object) != 'object') {
		throw new SyntaxError("Expected an object value, but got a " + getType(object));
	}
	return object;
}

function getType(value) {
	var typeName = typeof(value);
	if (typeName == 'object') {
		if (value) {
			if (value instanceof Array) {
				return 'array';
			} else {
				return typeName;
			}
		} else {
			return 'null';
		}
	} else {
		return typeName;
	}
}

exports.Server = Server;
new Server("db").listen(3000, function(err) {
	if (err) {
		sys.print(err, "\n");
	}
});
<|endoftext|>


<commit_before>'use strict';
(function($) {

    $.fn.setLocale = function() {

        var locale = $.fn.getParameter("lang");

        if (locale != undefined) {
            if (locale === "" || locale === "en") {
                localStorage.removeItem("locale");
                $(".nav>.dropdown>.dropdown-menu>li[name='en']").remove();
            } else {
                localStorage.setItem("locale", locale);
                $(".nav>.dropdown>.dropdown-toggle").html($(".nav>.dropdown>.dropdown-menu>li[name='" + locale + "']>a").html() + '');
                $(".nav>.dropdown>.dropdown-menu>li[name='" + locale + "']").remove();
            }
        } else {
            locale = localStorage.getItem("locale");
            if (locale == undefined) {
                locale = 'en';
            }
            $(".nav>.dropdown>.dropdown-toggle").html($(".nav>.dropdown>.dropdown-menu>li[name='" + locale + "']>a").html() + '');
            $(".nav>.dropdown>.dropdown-menu>li[name='" + locale + "']").remove();
        }

        if (localStorage.getItem("locale") && localStorage.getItem("locale") != 'en') {
            $.ajaxSetup({
                contentType: 'application/json',
                headers: {
                    'Accept-Language': localStorage.getItem("locale")
                }
            });
        } else {
            $.ajaxSetup({
                contentType: 'application/json'
            });
        }

    }

    $.fn.getParameter = function(name) {
        if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search))
            return decodeURIComponent(name[1]);
    }

    $.fn.setLocale();

    $.fn.setParameter = function(key, value) {
        key = encodeURI(key);
        value = encodeURI(value);

        var kvp = document.location.search.substr(1).split('&');

        var i = kvp.length;
        var x;
        while (i--) {
            x = kvp[i].split('=');

            if (x[0] == key) {
                x[1] = value;
                kvp[i] = x.join('=');
                break;
            }
        }

        if (i < 0) {
            kvp[kvp.length] = [key, value].join('=');
        }

        //this will reload the page, it's likely better to store this until finished
        document.location.search = kvp.join('&');
    }

    $(".nav>.dropdown>.dropdown-menu>li").on('click', function() {
        if ($(this).attr('name') === "en") {
            $.fn.setParameter("lang", "")
        } else {
            $.fn.setParameter("lang", $(this).attr('name'))
        }

    });

    var search_schema = localStorage.getItem("search_schema");

    if (search_schema == undefined) {
        search_schema = 'asset';
    }

    var $li = $(".search-panel>.dropdown-menu>li[name='" + search_schema + "']");

    $("#search_param").attr("placeholder", "Search " + search_schema);
    $("#search_concept>.glyphicon").first().attr("class", $li.find(".glyphicon").first().attr('class'));
    $(".search-panel>.dropdown-menu>li").show();
    $li.hide();

    $(".search-panel>.dropdown-menu>li").on('click', function() {
        var search_schema = $(this).attr('name');
        localStorage.setItem("search_schema", search_schema);
        $("#search_param").attr("placeholder", "Search " + search_schema);
        $("#search_concept>.glyphicon").first().attr("class", $(this).find(".glyphicon").first().attr('class'));
        $(".search-panel>.dropdown-menu>li").show();
        $(this).hide();
    });

    var $input = $("#search_param");
    $input.typeahead({
        source: function(value, callback) {
            $.getJSON("/api/data/movie", {
                q: value
            }, function(data) {
                callback(data.content || [])
            })
        },
        displayText:function(item) {
          return item.title;
        },
        autoSelect: true
    });
    $input.change(function() {
        var current = $input.typeahead("getActive");
        if (current) {
            // Some item from your model is active!
            if (current.name == $input.val()) {
                // This means the exact match is found. Use toLowerCase() if you want case insensitive match.
            } else {
                // This means it is only a partial match, you can either add a new item
                // or take the active if you don't want new items
            }
        } else {
            // Nothing is active so it is a new value (or maybe empty value)
        }
    });

}(jQuery));
<commit_msg>Custom TEmplate for Typeahead<commit_after>'use strict';
(function($) {

    $.fn.setLocale = function() {

        var locale = $.fn.getParameter("lang");

        if (locale != undefined) {
            if (locale === "" || locale === "en") {
                localStorage.removeItem("locale");
                $(".nav>.dropdown>.dropdown-menu>li[name='en']").remove();
            } else {
                localStorage.setItem("locale", locale);
                $(".nav>.dropdown>.dropdown-toggle").html($(".nav>.dropdown>.dropdown-menu>li[name='" + locale + "']>a").html() + '');
                $(".nav>.dropdown>.dropdown-menu>li[name='" + locale + "']").remove();
            }
        } else {
            locale = localStorage.getItem("locale");
            if (locale == undefined) {
                locale = 'en';
            }
            $(".nav>.dropdown>.dropdown-toggle").html($(".nav>.dropdown>.dropdown-menu>li[name='" + locale + "']>a").html() + '');
            $(".nav>.dropdown>.dropdown-menu>li[name='" + locale + "']").remove();
        }

        if (localStorage.getItem("locale") && localStorage.getItem("locale") != 'en') {
            $.ajaxSetup({
                contentType: 'application/json',
                headers: {
                    'Accept-Language': localStorage.getItem("locale")
                }
            });
        } else {
            $.ajaxSetup({
                contentType: 'application/json'
            });
        }

    }

    $.fn.getParameter = function(name) {
        if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search))
            return decodeURIComponent(name[1]);
    }

    $.fn.setLocale();

    $.fn.setParameter = function(key, value) {
        key = encodeURI(key);
        value = encodeURI(value);

        var kvp = document.location.search.substr(1).split('&');

        var i = kvp.length;
        var x;
        while (i--) {
            x = kvp[i].split('=');

            if (x[0] == key) {
                x[1] = value;
                kvp[i] = x.join('=');
                break;
            }
        }

        if (i < 0) {
            kvp[kvp.length] = [key, value].join('=');
        }

        //this will reload the page, it's likely better to store this until finished
        document.location.search = kvp.join('&');
    }

    $(".nav>.dropdown>.dropdown-menu>li").on('click', function() {
        if ($(this).attr('name') === "en") {
            $.fn.setParameter("lang", "")
        } else {
            $.fn.setParameter("lang", $(this).attr('name'))
        }

    });

    var search_schema = localStorage.getItem("search_schema");

    if (search_schema == undefined) {
        search_schema = 'asset';
    }

    var $li = $(".search-panel>.dropdown-menu>li[name='" + search_schema + "']");

    $("#search_param").attr("placeholder", "Search " + search_schema);
    $("#search_concept>.glyphicon").first().attr("class", $li.find(".glyphicon").first().attr('class'));
    $(".search-panel>.dropdown-menu>li").show();
    $li.hide();

    $(".search-panel>.dropdown-menu>li").on('click', function() {
        var search_schema = $(this).attr('name');
        localStorage.setItem("search_schema", search_schema);
        $("#search_param").attr("placeholder", "Search " + search_schema);
        $("#search_concept>.glyphicon").first().attr("class", $(this).find(".glyphicon").first().attr('class'));
        $(".search-panel>.dropdown-menu>li").show();
        $(this).hide();
    });

    var $input = $("#search_param");
    $input.typeahead({
        source: function(value, callback) {
            $.getJSON("/api/data/" + localStorage.getItem("search_schema"), {
                q: value,
                size:5

            }, function(data) {
                callback(data.content || [])
            })
        },
        displayText: function(item) {
            return item.title;
        },
        highlighter: function(text,item) {
            var html = $('

');
            return html.append(document.createTextNode(item.title)).html();
        },
        autoSelect: true
    });
    $input.change(function() {
        var current = $input.typeahead("getActive");
        if (current) {
            // Some item from your model is active!
            if (current.name == $input.val()) {
                // This means the exact match is found. Use toLowerCase() if you want case insensitive match.
            } else {
                // This means it is only a partial match, you can either add a new item
                // or take the active if you don't want new items
            }
        } else {
            // Nothing is active so it is a new value (or maybe empty value)
        }
    });

}(jQuery));
<|endoftext|>


<commit_before>/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

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

var deep = require('deep-get-set');

module.exports = function(app) {
  function createPipedProvider(providerConfig, nmea0183Listener) {
    var result = {
      id: providerConfig.id,
      pipeElements: providerConfig.pipeElements.map(createPipeElement)
    };

    result.pipeElements.forEach(function(pipeElement) {
      if (typeof pipeElement.on === 'function') {
        pipeElement.on('nmea0183', nmea0183Listener);
      }
    });

    for (var i = result.pipeElements.length - 2; i >= 0; i--) {
      result.pipeElements[i].pipe(result.pipeElements[i + 1]);
    }

    result.pipeElements[result.pipeElements.length - 1].on('data', function(data) {
      if (data.updates) {
        if (typeof data.context === 'undefined') {
          data.context = 'vessels.' + app.selfId;
        }
        data.updates.forEach(function(update) {
          update.source.label = providerConfig.id;
          if (!update.timestamp) {
            update.timestamp = (new Date()).toISOString();
          }
        })
        app.signalk.addDelta(data);
      }
    });
    return result;
  }

  function createPipeElement(elementConfig) {
    var options = elementConfig.options || {};
    options.app = app;
    if (elementConfig.optionMappings) {
      elementConfig.optionMappings.forEach(function(mapping) {
        if (deep(app, mapping.fromAppProperty)) {
          options[mapping.toOption] = deep(app, mapping.fromAppProperty);
        }
      });
    }
    try {
      return new(require(elementConfig.type))(options);
    } catch (e) {
      return new(require(__dirname + '/../' + elementConfig.type))(options);
    }
  }

  function startProviders() {
    var nmea0183Listener = function(sentence) {
      app.signalk.emit('nmea0183', sentence);
    }
    if (app.config.settings.pipedProviders) {
      return app.config.settings.pipedProviders.map(function(providerConfig) {
        return createPipedProvider(providerConfig, nmea0183Listener);
      });

    } else {
      console.error("No pipedProviders in the settings file");
      return [];
    }
  }

  return api = {
    start: startProviders
  }
};
<commit_msg>Emit an event when piped providers have been started (#130)<commit_after>/*
 * Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0

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

var deep = require('deep-get-set');

module.exports = function(app) {
  function createPipedProvider(providerConfig, nmea0183Listener) {
    var result = {
      id: providerConfig.id,
      pipeElements: providerConfig.pipeElements.map(createPipeElement)
    };

    result.pipeElements.forEach(function(pipeElement) {
      if (typeof pipeElement.on === 'function') {
        pipeElement.on('nmea0183', nmea0183Listener);
      }
    });

    for (var i = result.pipeElements.length - 2; i >= 0; i--) {
      result.pipeElements[i].pipe(result.pipeElements[i + 1]);
    }

    result.pipeElements[result.pipeElements.length - 1].on('data', function(data) {
      if (data.updates) {
        if (typeof data.context === 'undefined') {
          data.context = 'vessels.' + app.selfId;
        }
        data.updates.forEach(function(update) {
          update.source.label = providerConfig.id;
          if (!update.timestamp) {
            update.timestamp = (new Date()).toISOString();
          }
        })
        app.signalk.addDelta(data);
      }
    });
    app.emit("pipedProvidersStarted", providerConfig)
    return result;
  }

  function createPipeElement(elementConfig) {
    var options = elementConfig.options || {};
    options.app = app;
    if (elementConfig.optionMappings) {
      elementConfig.optionMappings.forEach(function(mapping) {
        if (deep(app, mapping.fromAppProperty)) {
          options[mapping.toOption] = deep(app, mapping.fromAppProperty);
        }
      });
    }
    try {
      return new(require(elementConfig.type))(options);
    } catch (e) {
      return new(require(__dirname + '/../' + elementConfig.type))(options);
    }
  }

  function startProviders() {
    var nmea0183Listener = function(sentence) {
      app.signalk.emit('nmea0183', sentence);
    }
    if (app.config.settings.pipedProviders) {
      return app.config.settings.pipedProviders.map(function(providerConfig) {
        return createPipedProvider(providerConfig, nmea0183Listener);
      });

    } else {
      console.error("No pipedProviders in the settings file");
      return [];
    }
  }

  return api = {
    start: startProviders
  }
};
<|endoftext|>


<commit_before>let _ = require('underscore'),
    path = require('path')

let constants = require('./constants'),
    EP = constants.EP

let Plugin = require('./plugins').Plugin

exports._Plugin = Plugin
exports.PluginManager = class PluginManager {

  constructor (registry, cwd) {
    this._registry = registry
    this._rootDir = cwd || process.cwd()
    this._pluginsByLocation = {}
    this._pluginsByName = {}
    this._pluginIsLoading = {}

    registry.register(EP.PLUGIN_LOADER, require('./plugin-loader'))

    this._id = 'pluginManager'
    this.description = 'The plugin manager running this show. You probably don\'t want this.'
    registry.register(EP.SINGLETONS, this)

    registry._id = 'extensionRegistry'
    registry.description = 'The extension registry gluing everything together. You probably don\'t want this.'
    registry.register(EP.SINGLETONS, registry)

    this._loaders = registry.getExtensionPoint(EP.PLUGIN_LOADER)
  }

  get plugins () {
    return _.values(this._pluginsByLocation)
  }

  findPlugin (location) {
    return this._pluginsByLocation[location]
  }

  dispose () {
    let registry = this._registry
    register.unregister(EP.SINGLETONS, registry)
    register.unregister(EP.SINGLETONS, this)
    registry.unregister(EP.PLUGIN_LOADER, require('./plugin-loader'))
  }

  get loader () {
    let all = this._loaders.array
    return all[all.length - 1];
  }

  load (ids, rootDir) {
    if (!_.isArray(ids)) {
      ids = _.toArray(arguments)
    }

    rootDir = rootDir || this._rootDir
    let loader = this.loader

    let createPlugin = (pathOrId) => {
      let base = loader.resolvePlugin(rootDir, pathOrId),
          plugin = this._pluginsByLocation[base]

      if (plugin) {
        return
      }

      if (this._pluginIsLoading[base]) {
        // we should return a promise here, to allow multiple promise to
        return;
      }

      this._pluginIsLoading[base] = true
      return new Plugin(base)
    }

    let loadManifest = (plugins) => {
      return plugins.map((p) => {
        return p.initializeFromDisk(loader)
          .catch((e) => {
            delete this._pluginIsLoading[p.location]
            console.log(e.message)
          })
      });
    }

    let pluginsOnly = (plugins) => {
      return plugins.filter((p) => {
        let isPlugin = !!p._manifest
        if (!isPlugin) {
          delete this._pluginIsLoading[p.location]
        }
        return isPlugin
      })
    }

    let registry = this._registry
    let askForContributions = (plugins) => {
      plugins.forEach((p) => {
        p.transformManifest(registry)
        p.provisionAPI(registry)
      })
      return plugins
    }

    let contributeSingletons = (plugins) => {
      plugins.forEach((p) => {
        p.populateSingletons(registry)
      })
      return plugins
    }

    let contributeExtensions = (plugins) => {
      plugins.forEach((p) => {
        p.distributeExtensions(registry)
      })
      return plugins
    }

    let returnPromise = (plugins) => {
      return plugins.map((p) => {
        let base = p.location
        try {
          p.start()
        } catch (e) {
          console.error(e.stack)
          return
        } finally {
          delete this._pluginIsLoading[base]
        }
        this._pluginsByLocation[base] = p
        this._pluginsByName[p.name] = p
        return p.api
      })
    }

    let pluginsToLoad = ids.map(createPlugin).filter((x) => x)

    return Promise.all(loadManifest(pluginsToLoad))
      .then(pluginsOnly)
      .then(askForContributions)
      .then(contributeSingletons)
      .then(contributeExtensions)
      .then(returnPromise)
      .catch((e) => {

      })
  }

  unload (nameOrLocation) {
    if (_.isArray(nameOrLocation)) {
      nameOrLocation.forEach((p) => {
        this.unload(p)
      })
      return
    }
    let plugin = this._pluginsByLocation[nameOrLocation] || this._pluginsByName[nameOrLocation]
    if (!plugin) {
      return Promise.reject(new Error('Plugin ' + nameOrLocation + ' not loaded'))
    }

    let p = plugin.unload(this._registry)

    delete this._pluginsByLocation[plugin.location]
    delete this._pluginsByName[plugin.name]

    return p
  }
}
<commit_msg>Add a restartSelf as a permission.<commit_after>let _ = require('underscore'),
    path = require('path')

let constants = require('./constants'),
    EP = constants.EP

let Plugin = require('./plugins').Plugin

exports._Plugin = Plugin
exports.PluginManager = class PluginManager {

  constructor (registry, cwd) {
    this._registry = registry
    this._rootDir = cwd || process.cwd()
    this._pluginsByLocation = {}
    this._pluginsByName = {}
    this._pluginIsLoading = {}

    registry.register(EP.PLUGIN_LOADER, require('./plugin-loader'))

    this._id = 'pluginManager'
    this.description = 'The plugin manager running this show. You probably don\'t want this.'
    registry.register(EP.SINGLETONS, this)

    let pluginManager = this
    let restart = function () {
      let plugin = pluginManager._pluginsByName[this.name]
      return pluginManager.unload(plugin.location)
        .then(() => {
          return pluginManager.load(plugin.location)
        })
    }
    restart._id = 'restartSelf'
    restart.description = 'Restart the plugin. You cannot use this to restart other plugins.'
    registry.register(EP.SINGLETONS, restart)

    registry._id = 'extensionRegistry'
    registry.description = 'The extension registry gluing everything together. You probably don\'t want this.'
    registry.register(EP.SINGLETONS, registry)

    this._loaders = registry.getExtensionPoint(EP.PLUGIN_LOADER)
  }

  get plugins () {
    return _.values(this._pluginsByLocation)
  }

  findPlugin (location) {
    return this._pluginsByLocation[location]
  }

  dispose () {
    let registry = this._registry
    register.unregister(EP.SINGLETONS, registry)
    register.unregister(EP.SINGLETONS, this)
    registry.unregister(EP.PLUGIN_LOADER, require('./plugin-loader'))
  }

  get loader () {
    let all = this._loaders.array
    return all[all.length - 1];
  }

  load (ids, rootDir) {
    if (!_.isArray(ids)) {
      ids = _.toArray(arguments)
    }

    rootDir = rootDir || this._rootDir
    let loader = this.loader

    let createPlugin = (pathOrId) => {
      let base = loader.resolvePlugin(rootDir, pathOrId),
          plugin = this._pluginsByLocation[base]

      if (plugin) {
        return
      }

      if (this._pluginIsLoading[base]) {
        // we should return a promise here, to allow multiple promise to
        return;
      }

      this._pluginIsLoading[base] = true
      return new Plugin(base)
    }

    let loadManifest = (plugins) => {
      return plugins.map((p) => {
        return p.initializeFromDisk(loader)
          .catch((e) => {
            delete this._pluginIsLoading[p.location]
            console.log(e.message)
          })
      });
    }

    let pluginsOnly = (plugins) => {
      return plugins.filter((p) => {
        let isPlugin = !!p._manifest
        if (!isPlugin) {
          delete this._pluginIsLoading[p.location]
        }
        return isPlugin
      })
    }

    let registry = this._registry
    let askForContributions = (plugins) => {
      plugins.forEach((p) => {
        p.transformManifest(registry)
        p.provisionAPI(registry)
      })
      return plugins
    }

    let contributeSingletons = (plugins) => {
      plugins.forEach((p) => {
        p.populateSingletons(registry)
      })
      return plugins
    }

    let contributeExtensions = (plugins) => {
      plugins.forEach((p) => {
        p.distributeExtensions(registry)
      })
      return plugins
    }

    let returnPromise = (plugins) => {
      return plugins.map((p) => {
        let base = p.location
        try {
          p.start()
        } catch (e) {
          console.error(e.stack)
          return
        } finally {
          delete this._pluginIsLoading[base]
        }
        this._pluginsByLocation[base] = p
        this._pluginsByName[p.name] = p
        return p.api
      })
    }

    let pluginsToLoad = ids.map(createPlugin).filter((x) => x)

    return Promise.all(loadManifest(pluginsToLoad))
      .then(pluginsOnly)
      .then(askForContributions)
      .then(contributeSingletons)
      .then(contributeExtensions)
      .then(returnPromise)
      .catch((e) => {

      })
  }

  unload (nameOrLocation) {
    if (_.isArray(nameOrLocation)) {
      nameOrLocation.forEach((p) => {
        this.unload(p)
      })
      return
    }
    let plugin = this._pluginsByLocation[nameOrLocation] || this._pluginsByName[nameOrLocation]
    if (!plugin) {
      return Promise.reject(new Error('Plugin ' + nameOrLocation + ' not loaded'))
    }

    let p = plugin.unload(this._registry)

    delete this._pluginsByLocation[plugin.location]
    delete this._pluginsByName[plugin.name]

    return p
  }
}
<|endoftext|>