diff options
| author | Kristian Lyngstol <kly@kly@.no> | 2016-03-21 20:11:55 +0100 | 
|---|---|---|
| committer | Kristian Lyngstol <kly@kly@.no> | 2016-03-21 20:11:55 +0100 | 
| commit | 727e4ab31aa6d1a754711d4cd29dbcefae2e952a (patch) | |
| tree | d1b4f17addedf5fe9695d6018efd880d13f0c5c1 /web/nms-public.gathering.org/js/nms-data.js | |
| parent | 7a6cf59e67d569a412f7670d9f04b2378c546dbc (diff) | |
NMS: NMS Public
Diffstat (limited to 'web/nms-public.gathering.org/js/nms-data.js')
| -rw-r--r-- | web/nms-public.gathering.org/js/nms-data.js | 262 | 
1 files changed, 262 insertions, 0 deletions
diff --git a/web/nms-public.gathering.org/js/nms-data.js b/web/nms-public.gathering.org/js/nms-data.js new file mode 100644 index 0000000..6432a8d --- /dev/null +++ b/web/nms-public.gathering.org/js/nms-data.js @@ -0,0 +1,262 @@ +"use strict"; + +/* + * This file/module/whatever is an attempt to gather all data collection in + * one place. + * + * The basic idea is to have all periodic data updates unified here, with + * stats, tracking of "ajax overflows" and general-purpose error handling + * and callbacks and whatnot, instead of all the custom stuff that we + * started out with. + * + * Sources are identified by a name, which is then available in + * nmsData[name] in full. A copy of the previous data set is kept in + * nmsData.old[name]. You can use getNow / setNow() to append a 'now=' + * string. + * + * nmsData[name] - actual data + * nmsData.old[name] - previous copy of data + * nmsData.registerSource() - add a source, will be polled periodicall + * nmsData.addHandler() + * nmsData.updateSource() - issue a one-off update, outside of whatever + * 			    periodic polling might take place + * nmsData.invalidate() - Invalidate browser-cache. + */ + + +var nmsData = nmsData || { +	old: {}, // Single copy of previous data. Automatically populated. +	stats: { +		identicalFetches:0, +		outstandingAjaxRequests:0, +		ajaxOverflow:0, +		pollClearsEmpty:0, +		pollClears:0, +		pollSets:0, +		newSource:0, +		oldSource:0 +	}, +	/* +	 * The last time stamp of any data received, regardless of source. +	 * +	 * Used as a fallback for blank now, but can also be used to check +	 * "freshness", I suppose. +	 */ +	_last: undefined, +	_now: undefined, + +	/* +	 * These are provided so we can introduce error checking when we +	 * have time. +	 *  +	 * now() represents the data, not the intent. That means that if +	 * you want to check if we are traveling in time you should not +	 * check nmsData.now. That will always return a value as long as +	 * we've had a single piece of data. +	 */ +	get now() { return this._now || this._last; }, +	set now(val) { +		if (val == undefined || !val) { +			nmsData._now = undefined; +		} else { +			// FIXME: Check if now is valid syntax. +			nmsData._now = val; +		} +	}, +	/* +	 * List of sources, name, handler, etc +	 */ +	_sources: {}, + +	/* +	 * Maximum number of AJAX requests in transit before we start +	 * skipping updates. +	 * +	 * A problem right now is that it will typically always hit the +	 * same thing since everything starts at the same time... +	 */ +	_ajaxThreshold: 10 +}; + + +nmsData._dropData = function (name) { +	delete this[name]; +	delete this.old[name]; +} + +nmsData.removeSource = function (name) { +	if (this._sources[name] == undefined) { +		this.stats.pollClearsEmpty++; +		return true; +	} +	if (this._sources[name]['handle']) { +		this.stats.pollClears++; +		clearInterval(this._sources[name]['handle']); +	} +	delete this._sources[name]; +} + +/* + * Register a source. + * + * name: "Local" name. Maps to nmsData[name] + * target: URL of the source + * + * This can be called multiple times to add multiple handlers. There's no + * guarantee that they will be run in order, but right now they do. + * + * Update frequency _might_ be adaptive eventually, but since we only + * execute callbacks on change and backend sends cache headers, the browser + * will not issue actual HTTP requests. + * + * FIXME: Should be unified with nmsTimers() somehow. + */ +nmsData.registerSource = function(name, target) { +	if (this._sources[name] == undefined) { +		this._sources[name] = { target: target, cbs: {}, fresh: true }; +		this._sources[name]['handle'] = setInterval(function(){nmsData.updateSource(name)}, 1000); +		this.stats.newSource++; +	} else { +		this.stats.oldSource++; +	} + +	this.stats.pollSets++; +} + +/* + * Add a handler (callback) for a source, using an id. + * + * This is idempotent: if the id is the same, it will just overwrite the + * old id, not add a copy. + */ +nmsData.addHandler = function(name, id, cb, cbdata) { +	try { +	var cbob = { +		id: id, +		name: name, +		cb: cb, +		fresh: true, +		cbdata: cbdata +	}; +	if (id == undefined) { +		return; +	} +	this._sources[name].cbs[id] = cbob; +	this.updateSource(name); +	} catch (e) { +	} +} + +/* + * Unregister all handlers with the "id" for all sources. + * + * Mainly used to avoid fini() functions in the map handlers. E.g.: just + * reuse "mapHandler" as id. + */ +nmsData.unregisterHandlerWildcard = function(id) { +	for (var v in nmsData._sources) { +		this.unregisterHandler(v, id); +	} +} + +nmsData.unregisterHandler = function(name, id) { +	delete this._sources[name].cbs[id]; +} + +/* + * Updates a source. + * + * Called on interval, but can also be used to update a source after a + * known action that updates the underlying data (e.g: update comments + * after a comment is posted). + */ +nmsData.updateSource = function(name) { +	/* +	 * See comment in nms.js nmsINIT(); +	 */ +	if (name == "ticker" ) { +		for (var i in nmsData._sources[name].cbs) { +			var tmp = nmsData._sources[name].cbs[i]; +			if (tmp.cb != undefined) { +				tmp.cb(tmp.cbdata); +			} +		} +		return; +	} +	this._genericUpdater(name, true); +} + +nmsData.invalidate = function(name) { +	this._genericUpdater(name, false); +} +/* + * Reset a source, deleting all data, including old. + * + * Useful if traveling in time, for example. + */ +nmsData.resetSource = function(name) { +	this[name] = {}; +	this.old[name] = {}; +	this.updateSource(name); +} + +/* + * Updates nmsData[name] and nmsData.old[name], issuing any callbacks where + * relevant. + * + * Do not use this directly. Use updateSource(). + * + */ +nmsData._genericUpdater = function(name, cacheok) { +	if (this.stats.outstandingAjaxRequests++ > this._ajaxThreshold) { +		this.stats.outstandingAjaxRequests--; +		this.stats.ajaxOverflow++; +		return; +	} +	var now = ""; +	if (this._now != undefined) +		now = "now=" + this._now; +	if (now != "") { +		if (this._sources[name].target.match("\\?")) +			now = "&" + now; +		else +			now = "?" + now; +	} +	var heads = {}; +	if (cacheok == false) { +		heads['Cache-Control'] = "max-age=0, no-cache, stale-while-revalidate=0"; +	} + +	$.ajax({ +		type: "GET", +		headers: heads, +		url: this._sources[name].target + now, +		dataType: "json", +		success: function (data, textStatus, jqXHR) { +			if (nmsData[name] == undefined ||  nmsData[name]['hash'] != data['hash']) { +				nmsData._last = data['time']; +				nmsData.old[name] = nmsData[name]; +				nmsData[name] = data; +				nmsMap.drawNow(); +				for (var i in nmsData._sources[name].cbs) { +					var tmp = nmsData._sources[name].cbs[i]; +					if (tmp.cb != undefined) { +						tmp.cb(tmp.cbdata); +					} +				} +			} else { +				for (var i in nmsData._sources[name].cbs) { +					var tmp = nmsData._sources[name].cbs[i]; +					if (tmp.cb != undefined && tmp.fresh) { +						nmsData._sources[name].cbs[i].fresh = false; +						tmp.cb(tmp.cbdata); +					} +				} +				nmsData.stats.identicalFetches++; +			} +		}, +		complete: function(jqXHR, textStatus) { +			nmsData.stats.outstandingAjaxRequests--; +		} +	}); +};  | 
