1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
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--;
}
});
};
|