"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) {
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);
};
/*
* 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 tmp2 = nmsData._sources[name].cbs[i];
if (tmp2.cb != undefined) {
tmp2.cb(tmp2.cbdata);
}
}
} else {
for (var j in nmsData._sources[name].cbs) {
var tmp = nmsData._sources[name].cbs[j];
if (tmp.cb != undefined && tmp.fresh) {
nmsData._sources[name].cbs[j].fresh = false;
tmp.cb(tmp.cbdata);
}
}
nmsData.stats.identicalFetches++;
}
},
complete: function(jqXHR, textStatus) {
nmsData.stats.outstandingAjaxRequests--;
}
});
};