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 >
Text File  |  2007-10-27  |  14KB  |  452 lines

  1. /**
  2. //
  3. // BEGIN SONGBIRD GPL
  4. // 
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2007 POTI, Inc.
  8. // http://songbirdnest.com
  9. // 
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. // 
  13. // Software distributed under the License is distributed 
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
  15. // express or implied. See the GPL for the specific language 
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this 
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc., 
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. // 
  23. // END SONGBIRD GPL
  24. //
  25.  */
  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_POSTMETRICS_PREFKEY = "songbird.url.metrics";
  34.  
  35. const SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS = 1; // every day
  36.  
  37. function Metrics() {
  38.     this.prefs = Components.classes["@mozilla.org/preferences-service;1"]
  39.                       .getService(Components.interfaces.nsIPrefBranch);
  40. }
  41.  
  42. Metrics.prototype = {
  43.   classDescription: SONGBIRD_METRICS_CLASSNAME,
  44.   classID:          SONGBIRD_METRICS_CID,
  45.   contractID:       SONGBIRD_METRICS_CONTRACTID,
  46.   QueryInterface:   XPCOMUtils.generateQI([
  47.     SONGBIRD_METRICS_IID,
  48.     Components.interfaces.nsIWebProgressListener,
  49.     Components.interfaces.nsISupportsWeakReference
  50.   ]),
  51.  
  52.   _postreq: null,
  53.   _dbquery: null,
  54.   
  55.   LOG: function(str) {
  56.     var consoleService = Components.classes['@mozilla.org/consoleservice;1']
  57.                             .getService(Components.interfaces.nsIConsoleService);
  58.     consoleService.logStringMessage(str);
  59.   },
  60.  
  61.  
  62.   /**
  63.    * Check to see if metrics should be submitted.
  64.    */
  65.   checkUploadMetrics: function()
  66.   {
  67.     if (!this._isEnabled()) return;
  68.     
  69.     var timeUp = this._isWaitPeriodUp();
  70.     
  71.     if (timeUp)
  72.     {
  73.       this.uploadMetrics();
  74.     } 
  75.   },
  76.   
  77.   /**
  78.    * Bundle all metrics info and send it to the server.
  79.    *
  80.    * TODO: Rethink version and OS strings
  81.    */
  82.   uploadMetrics: function()
  83.   {
  84.     dump("*** UPLOADING METRICS ***");
  85.     var user_install_uuid = this._getPlayerUUID();
  86.     
  87.     var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULRuntime);    
  88.     var user_os = xulRuntime.OS;
  89.  
  90.     
  91.     var metrics = this._getTable();
  92.  
  93.     var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
  94.     // appInfo.name + " " + appInfo.version + " - " + appInfo.appBuildID;    
  95.  
  96.     var abi = "Unknown";
  97.     // Not all builds have a known ABI
  98.     try {
  99.       abi = appInfo.XPCOMABI;
  100.       
  101.       // TODO: Throwing an exception every time is bad.. should probably detect os x
  102.       
  103.       // Mac universal build should report a different ABI than either macppc
  104.       // or mactel.
  105.       var macutils = Components.classes["@mozilla.org/xpcom/mac-utils;1"]
  106.                                .getService(Components.interfaces.nsIMacUtils); 
  107.       if (macutils.isUniversalBinary)  abi = "Universal-gcc3";
  108.     }
  109.     catch (e) {}
  110.  
  111.     var platform = appInfo.OS + "_" + abi;
  112.     
  113.     var tzo = (new Date()).getTimezoneOffset();
  114.     var neg = (tzo < 0);
  115.     tzo = Math.abs(tzo);
  116.     var tzh = Math.floor(tzo / 60);
  117.     var tzm = tzo - (tzh*60);
  118.     // note: timezone is -XX:XX if the offset is positive, and +XX:00 if the offset is negative !
  119.     // this is because the offset has the reverse sign compared to the timezone, since
  120.     // the offset is what you should add to localtime to get UTC, so if you add -XX:XX, you're
  121.     // subtracting XX:XX, because the timezone is UTC+XX:XX
  122.     var tz = (neg ? "+" : "-") + this.formatDigits(tzh,2) + ":" + this.formatDigits(tzm,2);
  123.     
  124.     // build xml
  125.     
  126.     var xml = "";
  127.     xml += '<metrics schema_version="2.0" guid="' + user_install_uuid 
  128.             + '" version="' + appInfo.version 
  129.             + '" build="' + appInfo.appBuildID
  130.             + '" product="' + appInfo.name
  131.             + '" platform="' + platform
  132.             + '" os="' + user_os 
  133.             + '" timezone="' + tz 
  134.             + '">\n';
  135.  
  136.     for (var i = 0; i < metrics.length; i++) 
  137.     {
  138.       var key = metrics[i][0];
  139.       var val = metrics[i][1];
  140.       if ( val > 0 )
  141.       {
  142.         var dot = key.indexOf(".");
  143.         if (dot >= 0) {
  144.           var timestamp = key.substr(0, dot);
  145.           var cleanKey = key.substr(dot + 1);
  146.           var date = new Date();
  147.           date.setTime(timestamp);
  148.           
  149.           var hourstart = date.getFullYear() + "-" + 
  150.                           this.formatDigits(date.getMonth()+1,2) + "-" + 
  151.                           this.formatDigits(date.getDate(),2) + " " + 
  152.                           this.formatDigits(date.getHours(),2) + ":" + 
  153.                           this.formatDigits(date.getMinutes(),2) + ":" + 
  154.                           this.formatDigits(date.getSeconds(),2);
  155.           xml += '\t<item hour_start="' + hourstart + '" key="' + encodeURIComponent(cleanKey) + '" value="' + val + '"/>\n';
  156.         }
  157.       }
  158.     }
  159.     xml += '</metrics>';
  160.  
  161. /*
  162.     // Happy little self-contained test display
  163.     var gPrompt = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  164.                           .getService(Components.interfaces.nsIPromptService);
  165.     gPrompt.alert( null, "METRICS XML", xml );
  166. */
  167.  
  168.     // upload xml
  169.  
  170.     var domparser = Components.classes["@mozilla.org/xmlextras/domparser;1"]
  171.                       .getService(Components.interfaces.nsIDOMParser);   
  172.     var document = domparser.parseFromString(xml, "text/xml");
  173.  
  174.     var onpostload = { 
  175.       _that: null, 
  176.       handleEvent: function( event ) { this._that.onPostLoad(); } 
  177.     };
  178.     onpostload._that = this;
  179.     
  180.     var onposterror = { 
  181.       _that: null, 
  182.       handleEvent: function( event ) { this._that.onPostError(); } 
  183.     };
  184.     onposterror._that = this;
  185.  
  186.     var postURL = this.prefs.getCharPref(SONGBIRD_POSTMETRICS_PREFKEY);
  187.     
  188.     this._postreq = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Components.interfaces.nsIXMLHttpRequest); 
  189.     this._postreq.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest).addEventListener("load", onpostload, false);
  190.     this._postreq.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest).addEventListener("error", onposterror, false);
  191.     this._postreq.open('POST', postURL, true); 
  192.     this._postreq.send(document);
  193.   },
  194.   
  195.   formatDigits: function(str, n) {
  196.     str = str+'';
  197.     while (str.length < n) str = "0" + str;
  198.     return str;
  199.   },
  200.  
  201.   onPostLoad: function() {
  202.     this.LOG("POST metrics done: "  + this._postreq.status + " - " + this._postreq.responseText);
  203.     
  204.     // POST successful, reset all metrics to 0
  205.     if (this._postreq.status == 200 && this._postreq.responseText == "OK") 
  206.     {
  207.       this._emptyTable();
  208.       var pref = Components.classes["@mozilla.org/preferences-service;1"]
  209.                         .getService(Components.interfaces.nsIPrefBranch);
  210.       var timenow = new Date();
  211.       var now = timenow.getTime();
  212.       
  213.       pref.setCharPref("app.metrics.last_upload", now);
  214.       pref.setCharPref("app.metrics.last_version", this._getCurrentVersion());
  215.       pref.setIntPref("app.metrics.last_update_count", this._getUpdateCount());
  216.       
  217.       this.LOG("metrics reset");
  218.     }    
  219.     else 
  220.     {
  221.       this.LOG("POST metrics failed: " + this._postreq.responseText);
  222.     }
  223.   },
  224.  
  225.   onPostError: function() {
  226.     this.LOG("POST metrics error");
  227.   },
  228.   
  229.   
  230.   
  231.   /**
  232.    * Return true unless metrics have been 
  233.    * explicitly disabled.
  234.    */
  235.   _isEnabled: function() {
  236.   
  237.     // Make sure we are allowed to send metrics
  238.     var enabled = 0;
  239.     try {
  240.       enabled = parseInt(this.prefs.getCharPref("app.metrics.enabled"));
  241.     }
  242.     catch (e) { }
  243.     //if (!enabled) dump("*** METRICS ARE DISABLED ***\n");
  244.     
  245.     return enabled;
  246.   },
  247.   
  248.   
  249.   /**
  250.    * Return true if SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS days have passed
  251.    * since last submission
  252.    */
  253.   _isWaitPeriodUp: function() { 
  254.                   
  255.     var timenow = new Date();
  256.     var now = timenow.getTime();
  257.     var last = 0;
  258.     try 
  259.     {
  260.       last = parseInt(this.prefs.getCharPref("app.metrics.last_upload"));
  261.     }
  262.     catch (e)
  263.     {
  264.       // first start, pretend we just uploaded so we'll trigger the next upload in n days
  265.       this.prefs.setCharPref("app.metrics.last_upload", now);
  266.       last = now;
  267.     }
  268.     
  269.     var diff = now - last;
  270.     
  271.     return (diff > (1000 /*one second*/ * 60 /*one minute*/ * 60 /*one hour*/ * 24 /*one day*/ * SONGBIRD_UPLOAD_METRICS_EVERY_NDAYS))
  272.   },
  273.  
  274.  
  275.   /**
  276.    * Has the version changed since last metrics submission
  277.    */
  278.   _hasVersionChanged: function() {
  279.   
  280.     var upgraded = false;
  281.     
  282.     var currentVersion = this._getCurrentVersion();
  283.     var lastVersion = null;
  284.     
  285.     try 
  286.     {
  287.       lastVersion = this.prefs.getCharPref("app.metrics.last_version");
  288.     }
  289.     catch (e) { }    
  290.     
  291.     if (currentVersion != lastVersion) 
  292.     {
  293.         upgraded = true;
  294.     }
  295.     
  296.     return upgraded;
  297.   },
  298.   
  299.   /**
  300.    * TODO: REPLACE WITH SOMETHING OFFICIAL
  301.    */  
  302.   _getCurrentVersion: function() {
  303.   
  304.     var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
  305.     return appInfo.name + " " + appInfo.version + " - " + appInfo.appBuildID;    
  306.   },
  307.   
  308.  
  309.   /**
  310.    * Find out how many updates have been applied through the update manager
  311.    */  
  312.   _getUpdateCount: function() {
  313.     var updateManager = Components.classes["@mozilla.org/updates/update-manager;1"].getService(Components.interfaces.nsIUpdateManager);
  314.     return updateManager.updateCount;    
  315.   },
  316.   
  317.   
  318.   /**
  319.    * TODO: REPLACE WITH SOMETHING OFFICIAL
  320.    */  
  321.   _getPlayerUUID: function() {
  322.   
  323.     var uuid = "";
  324.    
  325.     try 
  326.     {
  327.       uuid = this.prefs.getCharPref("app.player_uuid");
  328.     }
  329.     catch (e)
  330.     {
  331.       uuid = "";
  332.     }   
  333.     
  334.     if (uuid == "")
  335.     {
  336.         var aUUIDGenerator = Components.classes["@mozilla.org/uuid-generator;1"].createInstance(Components.interfaces.nsIUUIDGenerator);
  337.         uuid = aUUIDGenerator.generateUUID();
  338.         this.prefs.setCharPref("app.player_uuid", uuid);
  339.     }
  340.     
  341.     return uuid;     
  342.   },
  343.   
  344.   metricsInc: function( aCategory, aUniqueID, aExtraString ) {
  345.     this.metricsAdd( aCategory, aUniqueID, aExtraString, 1 );
  346.   },
  347.   
  348.   metricsAdd: function( aCategory, aUniqueID, aExtraString, aIntValue ) {
  349.     // timestamps are recorded as UTC !
  350.     var d = new Date();
  351.     var timestamp = (Math.floor(d.getTime() / 3600000) * 3600000) + (d.getTimezoneOffset() * 60000);
  352.  
  353.     // Cook up the key string
  354.     var key = timestamp + "." + aCategory + "." + aUniqueID;
  355.     if (aExtraString != null && aExtraString != "") key = key + "." + aExtraString;
  356.     
  357.     try {
  358.       // Don't record things if we're disabled.
  359.       if (!this._isEnabled()) return;
  360.  
  361.       // Make sure it's an actual int.      
  362.       intvalue = parseInt(aIntValue);
  363.     
  364.       // Then add our value to the old value and write it back
  365.       var cur = this._getValue( key );
  366.       var newval = cur + intvalue;
  367.       this._setValue( key, newval );
  368.     } 
  369.     catch(e) { 
  370.       this.LOG("error: metricsAdd( " + 
  371.                aCategory +
  372.                ", " +
  373.                aUniqueID + 
  374.                ", " + 
  375.                aExtraString + 
  376.                ", " + 
  377.                aIntValue + 
  378.                " ) == '" + 
  379.                key + 
  380.                "'\n\n" + 
  381.                e);
  382.     }
  383.   },
  384.   
  385.   _initDB: function() {
  386.     if (!this._dbquery) {
  387.       this._dbquery = Components.classes["@songbirdnest.com/Songbird/DatabaseQuery;1"].
  388.                                  createInstance(Components.interfaces.sbIDatabaseQuery);
  389.       this._dbquery.setAsyncQuery(false);
  390.       this._dbquery.setDatabaseGUID("metrics");
  391.       this._dbquery.resetQuery();
  392.       this._dbquery.addQuery("CREATE TABLE IF NOT EXISTS metrics (keyname TEXT UNIQUE NOT NULL, keyvalue BIGINT DEFAULT 0)");
  393.       this._dbquery.execute();
  394.     }
  395.   },
  396.   
  397.   _getValue: function(key) {
  398.     var retval = 0;
  399.  
  400.     this._initDB();
  401.     this._dbquery.resetQuery();
  402.   
  403.     this._dbquery.addQuery("SELECT * FROM metrics WHERE keyname = \"" + key + "\"");
  404.     this._dbquery.execute();
  405.  
  406.     var dbresult = this._dbquery.getResultObject();
  407.     if (dbresult.getRowCount() > 0) {
  408.       retval = parseInt(dbresult.getRowCell(0, 1));
  409.     }
  410.  
  411.     return retval;
  412.   },
  413.    
  414.   _setValue: function(key, n) {
  415.     this._initDB();
  416.     this._dbquery.resetQuery();
  417.     this._dbquery.addQuery("INSERT OR REPLACE INTO metrics VALUES (\"" + key + "\", " + n + ")");
  418.     this._dbquery.execute();
  419.   },
  420.   
  421.   _getTable: function() {
  422.     var table = new Array();
  423.     this._initDB();
  424.     this._dbquery.resetQuery();
  425.     this._dbquery.addQuery("SELECT * FROM metrics");
  426.     this._dbquery.execute();
  427.  
  428.     var dbresult = this._dbquery.getResultObject();
  429.     var count = dbresult.getRowCount();
  430.  
  431.     for (var i=0;i<count;i++) {
  432.       var key = dbresult.getRowCell(i, 0);
  433.       var val = parseInt(dbresult.getRowCell(i, 1));
  434.       table.push([key, val]);
  435.     }
  436.     
  437.     return table;
  438.   },
  439.   
  440.   _emptyTable: function() {
  441.     this._initDB();
  442.     this._dbquery.resetQuery();
  443.     this._dbquery.addQuery("DELETE FROM metrics");
  444.     this._dbquery.execute();
  445.   },
  446. } // Metrics.prototype
  447.  
  448. function NSGetModule(compMgr, fileSpec) {
  449.   return XPCOMUtils.generateModule([Metrics]);
  450. }
  451.  
  452.