aboutsummaryrefslogtreecommitdiffstats
path: root/web/nms-public.gathering.org/js/nms-data.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/nms-public.gathering.org/js/nms-data.js')
-rw-r--r--web/nms-public.gathering.org/js/nms-data.js259
1 files changed, 259 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..14e5fed
--- /dev/null
+++ b/web/nms-public.gathering.org/js/nms-data.js
@@ -0,0 +1,259 @@
+"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--;
+ }
+ });
+};