home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World 2007 December
/
PCWorld_2007-12_cd.bin
/
audio-video
/
songbird
/
Songbird_0.3_windows-i686.exe
/
components
/
sbMetrics.js
< prev
next >
Wrap
Text File
|
2007-10-27
|
14KB
|
452 lines
/**
//
// BEGIN SONGBIRD GPL
//
// This file is part of the Songbird web player.
//
// Copyright(c) 2005-2007 POTI, Inc.
// http://songbirdnest.com
//
// This file may be licensed under the terms of of the
// GNU General Public License Version 2 (the "GPL").
//
// Software distributed under the License is distributed
// on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
// express or implied. See the GPL for the specific language
// governing rights and limitations.
//
// You should have received a copy of the GPL along with this
// program. If not, go to http://www.gnu.org/licenses/gpl.html
// or write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// END SONGBIRD GPL
//
*/
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const SONGBIRD_METRICS_CONTRACTID = "@songbirdnest.com/Songbird/Metrics;1";
const SONGBIRD_METRICS_CLASSNAME = "Songbird Metrics Service Interface";
const SONGBIRD_METRICS_CID = Components.ID("{1066527d-b135-4e0c-9ea4-f6109ae97d02}");
const SONGBIRD_METRICS_IID = Components.interfaces.sbIMetrics;
const SONGBIRD_POSTMETRICS_PREFKEY = "songbird.url.metrics";
const SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS = 1; // every day
function Metrics() {
this.prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
}
Metrics.prototype = {
classDescription: SONGBIRD_METRICS_CLASSNAME,
classID: SONGBIRD_METRICS_CID,
contractID: SONGBIRD_METRICS_CONTRACTID,
QueryInterface: XPCOMUtils.generateQI([
SONGBIRD_METRICS_IID,
Components.interfaces.nsIWebProgressListener,
Components.interfaces.nsISupportsWeakReference
]),
_postreq: null,
_dbquery: null,
LOG: function(str) {
var consoleService = Components.classes['@mozilla.org/consoleservice;1']
.getService(Components.interfaces.nsIConsoleService);
consoleService.logStringMessage(str);
},
/**
* Check to see if metrics should be submitted.
*/
checkUploadMetrics: function()
{
if (!this._isEnabled()) return;
var timeUp = this._isWaitPeriodUp();
if (timeUp)
{
this.uploadMetrics();
}
},
/**
* Bundle all metrics info and send it to the server.
*
* TODO: Rethink version and OS strings
*/
uploadMetrics: function()
{
dump("*** UPLOADING METRICS ***");
var user_install_uuid = this._getPlayerUUID();
var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULRuntime);
var user_os = xulRuntime.OS;
var metrics = this._getTable();
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
// appInfo.name + " " + appInfo.version + " - " + appInfo.appBuildID;
var abi = "Unknown";
// Not all builds have a known ABI
try {
abi = appInfo.XPCOMABI;
// TODO: Throwing an exception every time is bad.. should probably detect os x
// Mac universal build should report a different ABI than either macppc
// or mactel.
var macutils = Components.classes["@mozilla.org/xpcom/mac-utils;1"]
.getService(Components.interfaces.nsIMacUtils);
if (macutils.isUniversalBinary) abi = "Universal-gcc3";
}
catch (e) {}
var platform = appInfo.OS + "_" + abi;
var tzo = (new Date()).getTimezoneOffset();
var neg = (tzo < 0);
tzo = Math.abs(tzo);
var tzh = Math.floor(tzo / 60);
var tzm = tzo - (tzh*60);
// note: timezone is -XX:XX if the offset is positive, and +XX:00 if the offset is negative !
// this is because the offset has the reverse sign compared to the timezone, since
// the offset is what you should add to localtime to get UTC, so if you add -XX:XX, you're
// subtracting XX:XX, because the timezone is UTC+XX:XX
var tz = (neg ? "+" : "-") + this.formatDigits(tzh,2) + ":" + this.formatDigits(tzm,2);
// build xml
var xml = "";
xml += '<metrics schema_version="2.0" guid="' + user_install_uuid
+ '" version="' + appInfo.version
+ '" build="' + appInfo.appBuildID
+ '" product="' + appInfo.name
+ '" platform="' + platform
+ '" os="' + user_os
+ '" timezone="' + tz
+ '">\n';
for (var i = 0; i < metrics.length; i++)
{
var key = metrics[i][0];
var val = metrics[i][1];
if ( val > 0 )
{
var dot = key.indexOf(".");
if (dot >= 0) {
var timestamp = key.substr(0, dot);
var cleanKey = key.substr(dot + 1);
var date = new Date();
date.setTime(timestamp);
var hourstart = date.getFullYear() + "-" +
this.formatDigits(date.getMonth()+1,2) + "-" +
this.formatDigits(date.getDate(),2) + " " +
this.formatDigits(date.getHours(),2) + ":" +
this.formatDigits(date.getMinutes(),2) + ":" +
this.formatDigits(date.getSeconds(),2);
xml += '\t<item hour_start="' + hourstart + '" key="' + encodeURIComponent(cleanKey) + '" value="' + val + '"/>\n';
}
}
}
xml += '</metrics>';
/*
// Happy little self-contained test display
var gPrompt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Components.interfaces.nsIPromptService);
gPrompt.alert( null, "METRICS XML", xml );
*/
// upload xml
var domparser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
.getService(Components.interfaces.nsIDOMParser);
var document = domparser.parseFromString(xml, "text/xml");
var onpostload = {
_that: null,
handleEvent: function( event ) { this._that.onPostLoad(); }
};
onpostload._that = this;
var onposterror = {
_that: null,
handleEvent: function( event ) { this._that.onPostError(); }
};
onposterror._that = this;
var postURL = this.prefs.getCharPref(SONGBIRD_POSTMETRICS_PREFKEY);
this._postreq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest);
this._postreq.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest).addEventListener("load", onpostload, false);
this._postreq.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest).addEventListener("error", onposterror, false);
this._postreq.open('POST', postURL, true);
this._postreq.send(document);
},
formatDigits: function(str, n) {
str = str+'';
while (str.length < n) str = "0" + str;
return str;
},
onPostLoad: function() {
this.LOG("POST metrics done: " + this._postreq.status + " - " + this._postreq.responseText);
// POST successful, reset all metrics to 0
if (this._postreq.status == 200 && this._postreq.responseText == "OK")
{
this._emptyTable();
var pref = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
var timenow = new Date();
var now = timenow.getTime();
pref.setCharPref("app.metrics.last_upload", now);
pref.setCharPref("app.metrics.last_version", this._getCurrentVersion());
pref.setIntPref("app.metrics.last_update_count", this._getUpdateCount());
this.LOG("metrics reset");
}
else
{
this.LOG("POST metrics failed: " + this._postreq.responseText);
}
},
onPostError: function() {
this.LOG("POST metrics error");
},
/**
* Return true unless metrics have been
* explicitly disabled.
*/
_isEnabled: function() {
// Make sure we are allowed to send metrics
var enabled = 0;
try {
enabled = parseInt(this.prefs.getCharPref("app.metrics.enabled"));
}
catch (e) { }
//if (!enabled) dump("*** METRICS ARE DISABLED ***\n");
return enabled;
},
/**
* Return true if SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS days have passed
* since last submission
*/
_isWaitPeriodUp: function() {
var timenow = new Date();
var now = timenow.getTime();
var last = 0;
try
{
last = parseInt(this.prefs.getCharPref("app.metrics.last_upload"));
}
catch (e)
{
// first start, pretend we just uploaded so we'll trigger the next upload in n days
this.prefs.setCharPref("app.metrics.last_upload", now);
last = now;
}
var diff = now - last;
return (diff > (1000 /*one second*/ * 60 /*one minute*/ * 60 /*one hour*/ * 24 /*one day*/ * SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS))
},
/**
* Has the version changed since last metrics submission
*/
_hasVersionChanged: function() {
var upgraded = false;
var currentVersion = this._getCurrentVersion();
var lastVersion = null;
try
{
lastVersion = this.prefs.getCharPref("app.metrics.last_version");
}
catch (e) { }
if (currentVersion != lastVersion)
{
upgraded = true;
}
return upgraded;
},
/**
* TODO: REPLACE WITH SOMETHING OFFICIAL
*/
_getCurrentVersion: function() {
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
return appInfo.name + " " + appInfo.version + " - " + appInfo.appBuildID;
},
/**
* Find out how many updates have been applied through the update manager
*/
_getUpdateCount: function() {
var updateManager = Components.classes["@mozilla.org/updates/update-manager;1"].getService(Components.interfaces.nsIUpdateManager);
return updateManager.updateCount;
},
/**
* TODO: REPLACE WITH SOMETHING OFFICIAL
*/
_getPlayerUUID: function() {
var uuid = "";
try
{
uuid = this.prefs.getCharPref("app.player_uuid");
}
catch (e)
{
uuid = "";
}
if (uuid == "")
{
var aUUIDGenerator = Components.classes["@mozilla.org/uuid-generator;1"].createInstance(Components.interfaces.nsIUUIDGenerator);
uuid = aUUIDGenerator.generateUUID();
this.prefs.setCharPref("app.player_uuid", uuid);
}
return uuid;
},
metricsInc: function( aCategory, aUniqueID, aExtraString ) {
this.metricsAdd( aCategory, aUniqueID, aExtraString, 1 );
},
metricsAdd: function( aCategory, aUniqueID, aExtraString, aIntValue ) {
// timestamps are recorded as UTC !
var d = new Date();
var timestamp = (Math.floor(d.getTime() / 3600000) * 3600000) + (d.getTimezoneOffset() * 60000);
// Cook up the key string
var key = timestamp + "." + aCategory + "." + aUniqueID;
if (aExtraString != null && aExtraString != "") key = key + "." + aExtraString;
try {
// Don't record things if we're disabled.
if (!this._isEnabled()) return;
// Make sure it's an actual int.
intvalue = parseInt(aIntValue);
// Then add our value to the old value and write it back
var cur = this._getValue( key );
var newval = cur + intvalue;
this._setValue( key, newval );
}
catch(e) {
this.LOG("error: metricsAdd( " +
aCategory +
", " +
aUniqueID +
", " +
aExtraString +
", " +
aIntValue +
" ) == '" +
key +
"'\n\n" +
e);
}
},
_initDB: function() {
if (!this._dbquery) {
this._dbquery = Components.classes["@songbirdnest.com/Songbird/DatabaseQuery;1"].
createInstance(Components.interfaces.sbIDatabaseQuery);
this._dbquery.setAsyncQuery(false);
this._dbquery.setDatabaseGUID("metrics");
this._dbquery.resetQuery();
this._dbquery.addQuery("CREATE TABLE IF NOT EXISTS metrics (keyname TEXT UNIQUE NOT NULL, keyvalue BIGINT DEFAULT 0)");
this._dbquery.execute();
}
},
_getValue: function(key) {
var retval = 0;
this._initDB();
this._dbquery.resetQuery();
this._dbquery.addQuery("SELECT * FROM metrics WHERE keyname = \"" + key + "\"");
this._dbquery.execute();
var dbresult = this._dbquery.getResultObject();
if (dbresult.getRowCount() > 0) {
retval = parseInt(dbresult.getRowCell(0, 1));
}
return retval;
},
_setValue: function(key, n) {
this._initDB();
this._dbquery.resetQuery();
this._dbquery.addQuery("INSERT OR REPLACE INTO metrics VALUES (\"" + key + "\", " + n + ")");
this._dbquery.execute();
},
_getTable: function() {
var table = new Array();
this._initDB();
this._dbquery.resetQuery();
this._dbquery.addQuery("SELECT * FROM metrics");
this._dbquery.execute();
var dbresult = this._dbquery.getResultObject();
var count = dbresult.getRowCount();
for (var i=0;i<count;i++) {
var key = dbresult.getRowCell(i, 0);
var val = parseInt(dbresult.getRowCell(i, 1));
table.push([key, val]);
}
return table;
},
_emptyTable: function() {
this._initDB();
this._dbquery.resetQuery();
this._dbquery.addQuery("DELETE FROM metrics");
this._dbquery.execute();
},
} // Metrics.prototype
function NSGetModule(compMgr, fileSpec) {
return XPCOMUtils.generateModule([Metrics]);
}