sbMetrics.js
Go to the documentation of this file.
1 
26 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
27 
28 const SONGBIRD_METRICS_CONTRACTID = "@songbirdnest.com/Songbird/Metrics;1";
29 const SONGBIRD_METRICS_CLASSNAME = "Songbird Metrics Service Interface";
30 const SONGBIRD_METRICS_CID = Components.ID("{1066527d-b135-4e0c-9ea4-f6109ae97d02}");
31 const SONGBIRD_METRICS_IID = Components.interfaces.sbIMetrics;
32 
33 const SONGBIRD_METRICS_GA_PROPERTYKEY = "app.metrics.ga.property";
34 const SONGBIRD_METRICS_GA_PROPERTYDEF = "UA-114360-23";
35 
36 const SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS = 1; // every day
37 
38 function Metrics() {
39  this.prefs = Components.classes["@mozilla.org/preferences-service;1"]
40  .getService(Components.interfaces.nsIPrefBranch);
41 }
42 
43 Metrics.prototype = {
47  QueryInterface: XPCOMUtils.generateQI([
49  Components.interfaces.nsIWebProgressListener,
50  Components.interfaces.nsISupportsWeakReference
51  ]),
52 
53  _getreq: null,
54  _dbquery: null,
55 
56  _eventMatchArray: [
57  [new RegExp(/^firstrun\.mediaimport\.(.+)$/g),
58  "install","first run media import"],
59  [new RegExp(/^app\.appstart$/g),
60  "usage","start"],
61  [new RegExp(/^mediacore\.play\.attempt\.(.+)$/g),
62  "usage","media playback"],
63  ],
64 
65  LOG: function(str) {
66  var consoleService = Components.classes['@mozilla.org/consoleservice;1']
67  .getService(Components.interfaces.nsIConsoleService);
68  consoleService.logStringMessage(str);
69  },
70 
71 
75  checkUploadMetrics: function()
76  {
77  if (!this._isEnabled()) return;
78 
79  var timeUp = this._isWaitPeriodUp();
80 
81  if (timeUp)
82  {
83  this.uploadMetrics();
84  }
85  },
86 
92  uploadMetrics: function()
93  {
94  var user_install_uuid = this._getPlayerUUID();
95 
96  var xulRuntime = Components.classes["@mozilla.org/xre/runtime;1"].getService(Components.interfaces.nsIXULRuntime);
97  var user_os = xulRuntime.OS;
98 
99 
100  var metrics = this._getTable();
101 
102  var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
103  // appInfo.name + " " + appInfo.version + " - " + appInfo.appBuildID;
104 
105  var abi = "Unknown";
106  // Not all builds have a known ABI
107  try {
108  abi = appInfo.XPCOMABI;
109 
110  // TODO: Throwing an exception every time is bad.. should probably detect os x
111 
112  // Mac universal build should report a different ABI than either macppc
113  // or mactel.
114  var macutils = Components.classes["@mozilla.org/xpcom/mac-utils;1"]
115  .getService(Components.interfaces.nsIMacUtils);
116  if (macutils.isUniversalBinary) abi = "Universal-gcc3";
117  }
118  catch (e) {}
119 
120  var platform = appInfo.OS + "_" + abi;
121 
122  var tzo = (new Date()).getTimezoneOffset();
123  var neg = (tzo < 0);
124  tzo = Math.abs(tzo);
125  var tzh = Math.floor(tzo / 60);
126  var tzm = tzo - (tzh*60);
127  // note: timezone is -XX:XX if the offset is positive, and +XX:00 if the offset is negative !
128  // this is because the offset has the reverse sign compared to the timezone, since
129  // the offset is what you should add to localtime to get UTC, so if you add -XX:XX, you're
130  // subtracting XX:XX, because the timezone is UTC+XX:XX
131  var tz = (neg ? "+" : "-") + this.formatDigits(tzh,2) + ":" + this.formatDigits(tzm,2);
132 
133  // build xml
134 
135  for (var i = 0; i < metrics.length; i++)
136  {
137  var key = metrics[i][0];
138  var val = metrics[i][1];
139  if ( val > 0 )
140  {
141  var dot = key.indexOf(".");
142  if (dot >= 0) {
143  var timestamp = key.substr(0, dot);
144  var cleanKey = key.substr(dot + 1);
145 
146  // TODO ???
147  }
148  }
149  }
150 
151  var ongetload = {
152  _that: null,
153  handleEvent: function( event ) { this._that.onGetLoad(); }
154  };
155  ongetload._that = this;
156 
157  var ongeterror = {
158  _that: null,
159  handleEvent: function( event ) { this._that.onGetError(); }
160  };
161  ongeterror._that = this;
162 
163  var getURL = "http://www.google-analytics.com/__ga.gif?";
164  // todo
165 
166 // this._getreq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
167 // this._getreq.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest).addEventListener("load", ongetload, false);
168 // this._getreq.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest).addEventListener("error", ongeterror, false);
169 // this._getreq.open('GET', getURL, true);
170 // this._getreq.send(null);
171  },
172 
173  formatDigits: function(str, n) {
174  str = str+'';
175  while (str.length < n) str = "0" + str;
176  return str;
177  },
178 
179  onGetLoad: function() {
180  this.LOG("GET metrics done: " + this._getreq.status + " - " + this._getreq.responseText);
181 
182  // GET successful, reset all metrics to 0
183  if (this._getreq.status == 200 && this._getreq.responseText == "OK")
184  {
185  this._emptyTable();
186  var pref = Components.classes["@mozilla.org/preferences-service;1"]
187  .getService(Components.interfaces.nsIPrefBranch);
188  var timenow = new Date();
189  var now = timenow.getTime();
190 
191  pref.setCharPref("app.metrics.last_upload", now);
192  pref.setCharPref("app.metrics.last_version", this._getCurrentVersion());
193  pref.setIntPref("app.metrics.last_update_count", this._getUpdateCount());
194 
195  this.LOG("metrics reset");
196  }
197  else
198  {
199  this.LOG("GET metrics failed: " + this._getreq.responseText);
200  }
201  },
202 
203  onGetError: function() {
204  this.LOG("GET metrics error");
205  },
206 
207 
208 
213  _isEnabled: function() {
214 
215  // Make sure we are allowed to send metrics
216  var enabled = 0;
217  try {
218  enabled = parseInt(this.prefs.getCharPref("app.metrics.enabled"));
219  }
220  catch (e) { }
221  //if (!enabled) dump("*** METRICS ARE DISABLED ***\n");
222 
223  return enabled;
224  },
225 
226 
231  _isWaitPeriodUp: function() {
232 
233  var timenow = new Date();
234  var now = timenow.getTime();
235  var last = 0;
236  try
237  {
238  last = parseInt(this.prefs.getCharPref("app.metrics.last_upload"));
239  }
240  catch (e)
241  {
242  // first start, pretend we just uploaded so we'll trigger the next upload in n days
243  this.prefs.setCharPref("app.metrics.last_upload", now);
244  last = now;
245  }
246 
247  var diff = now - last;
248 
249  return (diff > (1000 /*one second*/ * 60 /*one minute*/ * 60 /*one hour*/ * 24 /*one day*/ * SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS))
250  },
251 
252 
256  _hasVersionChanged: function() {
257 
258  var upgraded = false;
259 
260  var currentVersion = this._getCurrentVersion();
261  var lastVersion = null;
262 
263  try
264  {
265  lastVersion = this.prefs.getCharPref("app.metrics.last_version");
266  }
267  catch (e) { }
268 
269  if (currentVersion != lastVersion)
270  {
271  upgraded = true;
272  }
273 
274  return upgraded;
275  },
276 
280  _getCurrentVersion: function() {
281 
282  var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
283  return appInfo.name + " " + appInfo.version + " - " + appInfo.appBuildID;
284  },
285 
286 
290  _getUpdateCount: function() {
291  var updateManager = Components.classes["@mozilla.org/updates/update-manager;1"].getService(Components.interfaces.nsIUpdateManager);
292  return updateManager.updateCount;
293  },
294 
295 
299  _getPlayerUUID: function() {
300 
301  var uuid = "";
302 
303  try
304  {
305  uuid = this.prefs.getCharPref("app.player_uuid");
306  }
307  catch (e)
308  {
309  uuid = "";
310  }
311 
312  if (uuid == "")
313  {
314  var aUUIDGenerator = Components.classes["@mozilla.org/uuid-generator;1"].createInstance(Components.interfaces.nsIUUIDGenerator);
315  uuid = aUUIDGenerator.generateUUID();
316  this.prefs.setCharPref("app.player_uuid", uuid);
317  uuid = this.prefs.getCharPref("app.player_uuid"); // force string type
318  }
319 
320  return uuid;
321  },
322 
323  metricsInc: function( aCategory, aUniqueID, aExtraString ) {
324  this.metricsAdd( aCategory, aUniqueID, aExtraString, 1 );
325  },
326 
327  metricsAdd: function( aCategory, aUniqueID, aExtraString, aIntValue ) {
328  var gPrompt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
329  .getService(Components.interfaces.nsIPromptService);
330 
331  // Cook up the key string
332  var key = aCategory + "." + aUniqueID;
333  if (aExtraString != null && aExtraString != "") key = key + "." + aExtraString;
334 
335  var eventCode = null;
336  for(var iPattern = 0; iPattern < this._eventMatchArray.length; iPattern++ ) {
337  var match = this._eventMatchArray[iPattern][0].exec(key);
338  if(match && (match.length > 0) && (match[0] == key)) {
339  eventCode = "5({0}*{1}*{2})(1)";
340  eventCode = eventCode.replace("{0}", this._eventMatchArray[iPattern][1]);
341  eventCode = eventCode.replace("{1}", this._eventMatchArray[iPattern][2]);
342  eventCode = eventCode.replace("{2}", (match.length > 1) ? match[1] : "");
343  break;
344  }
345  }
346  if(!eventCode) {
347  return;
348  }
349 
350 // gPrompt.alert(null,key,eventCode);
351 
352  // timestamps are recorded as UTC !
353  var d = new Date();
355  var timestamp = (Math.floor(d.getTime() / 3600000) * 3600) + (d.getTimezoneOffset() * 60);
357  var gaCompliantUUID = this._getPlayerUUID().replace(/[{}-]/g,"");
358  // GA cookie
359  var cookie = [
360  this._gaHash("songbirdnest.com"),
361  this._gaHash(gaCompliantUUID),
362  timestamp,timestamp,timestamp,
363  1
364  ].join(".");
365  var rand = Math.floor(Math.random()*1000000000);
366 // var gaPropKey = this.prefs.getCharPref(SONGBIRD_METRICS_GA_PROPERTYKEY);
367  var gaPropKey = SONGBIRD_METRICS_GA_PROPERTYDEF;
368  var getParams = [
369  "utmwv=5.3.8",
370  "utmac=" + gaPropKey,
371  "utmcc=__utma%3D" + cookie,
372  "utmp=index.html",
373  "utmt=event",
374  "utme=" + eventCode,
375  "utmn=" + rand
376  ].join("&");
377 
378 // gPrompt.alert(null,key,getParams);
379 
380  var getURL = "http://www.google-analytics.com/__utm.gif?"
381  + getParams;
382 
383  this._getreq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
384  .createInstance(Components.interfaces.nsIXMLHttpRequest);
385  this._getreq.open('GET', getURL, false);
386  this._getreq.send(null);
387  },
388 
389  _initDB: function() {
390  if (!this._dbquery) {
391  this._dbquery = Components.classes["@songbirdnest.com/Songbird/DatabaseQuery;1"].
392  createInstance(Components.interfaces.sbIDatabaseQuery);
393  this._dbquery.setAsyncQuery(false);
394  this._dbquery.setDatabaseGUID("metrics");
395  this._dbquery.resetQuery();
396  this._dbquery.addQuery("CREATE TABLE IF NOT EXISTS metrics (keyname TEXT UNIQUE NOT NULL, keyvalue BIGINT DEFAULT 0)");
397  this._dbquery.execute();
398  }
399  },
400 
401  _getValue: function(key) {
402  var retval = 0;
403 
404  this._initDB();
405  this._dbquery.resetQuery();
406 
407  this._dbquery.addQuery("SELECT * FROM metrics WHERE keyname = \"" + key + "\"");
408  this._dbquery.execute();
409 
410  var dbresult = this._dbquery.getResultObject();
411  if (dbresult.getRowCount() > 0) {
412  retval = parseInt(dbresult.getRowCell(0, 1));
413  }
414 
415  return retval;
416  },
417 
418  _setValue: function(key, n) {
419  this._initDB();
420  this._dbquery.resetQuery();
421  this._dbquery.addQuery("INSERT OR REPLACE INTO metrics VALUES (\"" + key + "\", " + n + ")");
422  this._dbquery.execute();
423  },
424 
425  _getTable: function() {
426  var table = new Array();
427  this._initDB();
428  this._dbquery.resetQuery();
429  this._dbquery.addQuery("SELECT * FROM metrics");
430  this._dbquery.execute();
431 
432  var dbresult = this._dbquery.getResultObject();
433  var count = dbresult.getRowCount();
434 
435  for (var i=0;i<count;i++) {
436  var key = dbresult.getRowCell(i, 0);
437  var val = parseInt(dbresult.getRowCell(i, 1));
438  table.push([key, val]);
439  }
440 
441  return table;
442  },
443 
444  _emptyTable: function() {
445  this._initDB();
446  this._dbquery.resetQuery();
447  this._dbquery.addQuery("DELETE FROM metrics");
448  this._dbquery.execute();
449  },
450 
451  _gaHash : function(aString) {
452  var result = 1;
453  var partial = 0;
454  var charIndex = 0;
455  var charCode = 0;
456  if( aString ) {
457  result = 0;
458  for( charIndex = aString["length"]-1; charIndex>=0; charIndex--) {
459  charCode = aString.charCodeAt(charIndex);
460  result = (result<<6&268435455)+charCode+(charCode<<14);
461  partial = result&266338304;
462  result = (partial!=0) ? result^partial>>21 : result;
463  }
464  }
465  return result;
466  },
467 } // Metrics.prototype
468 
469 function NSGetModule(compMgr, fileSpec) {
470  return XPCOMUtils.generateModule([Metrics]);
471 }
472 
const SONGBIRD_METRICS_CID
Definition: sbMetrics.js:30
#define LOG(args)
var pref
Definition: openLocation.js:44
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
const SONGBIRD_METRICS_GA_PROPERTYKEY
Definition: sbMetrics.js:33
const SONGBIRD_METRICS_CONTRACTID
Definition: sbMetrics.js:28
var event
sidebarFactory createInstance
Definition: nsSidebar.js:351
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
iso8601Week date date date getTimezoneOffset()/-60))
var uuid
const SONGBIRD_METRICS_IID
Definition: sbMetrics.js:31
function d(s)
var count
Definition: test_bug7406.js:32
const SONGBIRD_METRICS_GA_PROPERTYDEF
Definition: sbMetrics.js:34
this _dialogInput val(dateText)
ExtensionSchemeMatcher prototype match
const SONGBIRD_METRICS_CLASSNAME
Definition: sbMetrics.js:29
return null
Definition: FeedWriter.js:1143
var prefs
Definition: FeedWriter.js:1169
var gPrompt
Definition: windowUtils.js:64
sbDeviceFirmwareAutoCheckForUpdate prototype classID
function now()
const SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS
Definition: sbMetrics.js:36
_getSelectedPageStyle s i
function Metrics()
Definition: sbMetrics.js:38