home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / phoenx05.zip / phoenix / components / nsUpdateNotifier.js < prev    next >
Text File  |  2002-12-10  |  20KB  |  600 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is the Update Notifier.
  15.  *
  16.  * The Initial Developer of the Original Code is 
  17.  * Netscape Communications Corporation.
  18.  * Portions created by the Initial Developer are Copyright (C) 2002
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *  Samir Gehani <sgehani@netscape.com> (Original Author) 
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. const kDebug               = false;
  39. const kUpdateCheckDelay    = 5 * 60 * 1000; // 5 minutes
  40. const kUNEnabledPref       = "update_notifications.enabled";
  41. const kUNDatasourceURIPref = "update_notifications.provider.0.datasource";
  42. const kUNFrequencyPref     = "update_notifications.provider.0.frequency";
  43. const kUNLastCheckedPref   = "update_notifications.provider.0.last_checked";
  44. const kUNBundleURI         = 
  45.   "chrome://communicator/locale/update-notifications.properties";
  46.  
  47. ////////////////////////////////////////////////////////////////////////
  48. // 
  49. //   nsUpdateNotifier : nsIProfileStartupListener, nsIObserver
  50. //
  51. //   Checks for updates of the client by polling a distributor's website
  52. //   for the latest available version of the software and comparing it
  53. //   with the version of the running client.
  54. //
  55. ////////////////////////////////////////////////////////////////////////
  56.  
  57. var nsUpdateNotifier = 
  58. {
  59.   onProfileStartup: function(aProfileName)
  60.   {
  61.     debug("onProfileStartup");
  62.  
  63.     // now wait for the first app window to open
  64.     var observerService = Components.
  65.       classes["@mozilla.org/observer-service;1"].
  66.       getService(Components.interfaces.nsIObserverService);
  67.     observerService.addObserver(this, "domwindowopened", false);
  68.   },
  69.  
  70.   mTimer: null, // need to hold on to timer ref
  71.  
  72.   observe: function(aSubject, aTopic, aData)
  73.   {
  74.     debug("observe: " + aTopic);
  75.  
  76.     if (aTopic == "domwindowopened")
  77.     {
  78.       try 
  79.       {
  80.         const kITimer = Components.interfaces.nsITimer;
  81.         this.mTimer = Components.classes["@mozilla.org/timer;1"].
  82.           createInstance(kITimer);
  83.         this.mTimer.init(this, kUpdateCheckDelay, kITimer.TYPE_ONE_SHOT);
  84.  
  85.         // we are no longer interested in the ``domwindowopened'' topic
  86.         var observerService = Components.
  87.           classes["@mozilla.org/observer-service;1"].
  88.           getService(Components.interfaces.nsIObserverService);
  89.         observerService.removeObserver(this, "domwindowopened");
  90.       }
  91.       catch (ex)
  92.       {
  93.         debug("Exception init'ing timer: " + ex);
  94.       }
  95.     }
  96.     else if (aTopic == "timer-callback")
  97.     {
  98.       this.mTimer = null; // free up timer so it can be gc'ed
  99.       this.checkForUpdate();
  100.     }
  101.   },
  102.  
  103.   checkForUpdate: function()
  104.   {
  105.     debug("checkForUpdate");
  106.     
  107.     if (this.shouldCheckForUpdate())
  108.     {
  109.       try
  110.       {
  111.         // get update ds URI from prefs
  112.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  113.           getService(Components.interfaces.nsIPrefBranch);
  114.         var updateDatasourceURI = prefs.
  115.           getComplexValue(kUNDatasourceURIPref,
  116.           Components.interfaces.nsIPrefLocalizedString).data;
  117.  
  118.         var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"].
  119.           getService(Components.interfaces.nsIRDFService);
  120.         var ds = rdf.GetDataSource(updateDatasourceURI);
  121.         
  122.         ds = ds.QueryInterface(Components.interfaces.nsIRDFXMLSink);
  123.         ds.addXMLSinkObserver(nsUpdateDatasourceObserver);
  124.       }
  125.       catch (ex)
  126.       {
  127.         debug("Exception getting updates.rdf: " + ex);
  128.       }
  129.     }
  130.   },
  131.  
  132.   shouldCheckForUpdate: function()
  133.   {
  134.     debug("shouldCheckForUpdate");
  135.  
  136.     var shouldCheck = false;
  137.  
  138.     try
  139.     {
  140.       var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  141.         getService(Components.interfaces.nsIPrefBranch);
  142.   
  143.       if (prefs.getBoolPref(kUNEnabledPref))
  144.       {
  145.         var freq = prefs.getIntPref(kUNFrequencyPref) * (24 * 60 * 60); // secs
  146.         var now = (new Date().valueOf())/1000; // secs
  147.  
  148.         if (!prefs.prefHasUserValue(kUNLastCheckedPref))
  149.         {
  150.           // setting last_checked pref first time so must randomize in 
  151.           // order that servers don't get flooded with updates.rdf checks
  152.           // (and eventually downloads of new clients) all at the same time
  153.  
  154.           var randomizedLastChecked = now + freq * (1 + Math.random());
  155.           prefs.setIntPref(kUNLastCheckedPref, randomizedLastChecked);
  156.  
  157.           return false;
  158.         }
  159.  
  160.         var lastChecked = prefs.getIntPref(kUNLastCheckedPref);
  161.         if ((lastChecked + freq) > now)
  162.           return false;
  163.         
  164.         prefs.setIntPref(kUNLastCheckedPref, now);
  165.         prefs = prefs.QueryInterface(Components.interfaces.nsIPrefService);
  166.         prefs.savePrefFile(null); // flush prefs now
  167.  
  168.         shouldCheck = true;
  169.       }
  170.     }
  171.     catch (ex)
  172.     {
  173.       shouldCheck = false;
  174.       debug("Exception in shouldCheckForUpdate: " + ex);
  175.     }
  176.  
  177.     return shouldCheck;
  178.   },
  179.  
  180.   QueryInterface: function(aIID)
  181.   {
  182.     if (!aIID.equals(Components.interfaces.nsIObserver) &&
  183.         !aIID.equals(Components.interfaces.nsIProfileStartupListener) &&
  184.         !aIID.equals(Components.interfaces.nsISupports))
  185.       throw Components.results.NS_ERROR_NO_INTERFACE;
  186.  
  187.     return this;
  188.   }
  189. }
  190.  
  191. ////////////////////////////////////////////////////////////////////////
  192. //
  193. //   nsUpdateDatasourceObserver : nsIRDFXMLSinkObserver
  194. //
  195. //   Gets relevant info on latest available update after the updates.rdf
  196. //   datasource has completed loading asynchronously.
  197. //
  198. ////////////////////////////////////////////////////////////////////////
  199.  
  200. var nsUpdateDatasourceObserver = 
  201. {
  202.   onBeginLoad: function(aSink)
  203.   {
  204.   },
  205.  
  206.   onInterrupt: function(aSink)
  207.   {
  208.   },
  209.  
  210.   onResume: function(aSink)
  211.   {
  212.   },
  213.  
  214.   onEndLoad: function(aSink)
  215.   {
  216.     debug("onEndLoad");
  217.     
  218.     aSink.removeXMLSinkObserver(this);
  219.  
  220.     var ds = aSink.QueryInterface(Components.interfaces.nsIRDFDataSource);
  221.     var updateInfo = this.getUpdateInfo(ds);
  222.     if (updateInfo && this.newerVersionAvailable(updateInfo))
  223.     {
  224.       var promptService = Components.
  225.         classes["@mozilla.org/embedcomp/prompt-service;1"].
  226.         getService(Components.interfaces.nsIPromptService);
  227.       var winWatcher = Components.
  228.         classes["@mozilla.org/embedcomp/window-watcher;1"].
  229.         getService(Components.interfaces.nsIWindowWatcher);
  230.       
  231.       var unBundle = this.getBundle(kUNBundleURI);
  232.       if (!unBundle)
  233.         return;
  234.  
  235.       var title = unBundle.formatStringFromName("title", 
  236.         [updateInfo.productName], 1);
  237.       var desc = unBundle.formatStringFromName("desc", 
  238.         [updateInfo.productName], 1);
  239.       var button0Text = unBundle.GetStringFromName("getItNow");
  240.       var button1Text = unBundle.GetStringFromName("noThanks");
  241.       var checkMsg = unBundle.GetStringFromName("dontAskAgain");
  242.       var checkVal = {value:0};
  243.  
  244.       var result = promptService.confirmEx(winWatcher.activeWindow, title, desc, 
  245.         (promptService.BUTTON_POS_0 * promptService.BUTTON_TITLE_IS_STRING) +
  246.         (promptService.BUTTON_POS_1 * promptService.BUTTON_TITLE_IS_STRING),
  247.         button0Text, button1Text, null, checkMsg, checkVal);
  248.  
  249.       // user wants update now so open new window 
  250.       // (result => 0 is button0)
  251.       if (result == 0)
  252.         winWatcher.openWindow(winWatcher.activeWindow, updateInfo.URL, 
  253.           "_blank", "", null);
  254.  
  255.       // if "Don't ask again" was checked disable update notifications
  256.       if (checkVal.value)
  257.       {
  258.         var prefs = Components.classes["@mozilla.org/preferences-service;1"].
  259.           getService(Components.interfaces.nsIPrefBranch);
  260.         prefs.setBoolPref(kUNEnabledPref, false);
  261.       }
  262.     }
  263.   },
  264.  
  265.   onError: function(aSink, aStatus, aErrorMsg)
  266.   {
  267.     debug("Error " + aStatus + ": " + aErrorMsg);
  268.     aSink.removeXMLSinkObserver(this);
  269.   },
  270.  
  271.   getUpdateInfo: function(aDS)
  272.   {
  273.     var info = null;
  274.  
  275.     try
  276.     {
  277.       var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"].
  278.         getService(Components.interfaces.nsIRDFService);
  279.       var src = "urn:updates:latest";
  280.  
  281.       info = new Object;
  282.       info.registryName = this.getTarget(rdf, aDS, src, "registryName");
  283.       info.version = this.getTarget(rdf, aDS, src, "version");
  284.       info.URL = this.getTarget(rdf, aDS, src, "URL");
  285.       info.productName = this.getTarget(rdf, aDS, src, "productName");
  286.     }
  287.     catch (ex)
  288.     {
  289.       info = null;
  290.       debug("Exception getting update info: " + ex);
  291.  
  292.       // NOTE: If the (possibly remote) datasource doesn't exist 
  293.       //       or fails to load the first |GetTarget()| call will fail
  294.       //       bringing us to this exception handler.  In turn, we 
  295.       //       will fail silently.  Testing has revealed that for a 
  296.       //       non-existent datasource (invalid URI) the 
  297.       //       |nsIRDFXMLSinkObserver.onEndLoad()| is called instead of
  298.       //       |nsIRDFXMLSinkObserver.onError()| as one may expect.  In 
  299.       //       addition, if we QI the aSink parameter of |onEndLoad()| 
  300.       //       to an |nsIRDFRemoteDataSource| and check the |loaded| 
  301.       //       boolean, it reflects true so we can't use that.  The 
  302.       //       safe way to know we have failed to load the datasource 
  303.       //       is by handling the first exception as we are doing now.
  304.     }
  305.  
  306.     return info;
  307.   },
  308.  
  309.   getTarget: function(aRDF, aDS, aSrc, aProp)
  310.   {
  311.     var src = aRDF.GetResource(aSrc);
  312.     var arc = aRDF.GetResource("http://home.netscape.com/NC-rdf#" + aProp);
  313.     var target = aDS.GetTarget(src, arc, true);
  314.     return target.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
  315.   },
  316.  
  317.   newerVersionAvailable: function(aUpdateInfo)
  318.   {
  319.     // sanity check 
  320.     if (!aUpdateInfo.registryName || !aUpdateInfo.version)
  321.     {
  322.       debug("Sanity check failed: aUpdateInfo is invalid!");
  323.       return false;
  324.     }
  325.  
  326.     // when we know we are updating the ``Browser'' component
  327.     // we can rely on Necko to give us the app version
  328.  
  329.     if (aUpdateInfo.registryName == "Browser")
  330.       return this.neckoHaveNewer(aUpdateInfo);
  331.  
  332.     return this.xpinstallHaveNewer(aUpdateInfo);
  333.   },
  334.  
  335.   neckoHaveNewer: function(aUpdateInfo)
  336.   {
  337.     try
  338.     {
  339.       var httpHandler = Components.
  340.         classes["@mozilla.org/network/protocol;1?name=http"].
  341.         getService(Components.interfaces.nsIHttpProtocolHandler);
  342.       var synthesized = this.synthesizeVersion(httpHandler.misc, 
  343.         httpHandler.productSub);
  344.       var local = new nsVersion(synthesized);
  345.       var server = new nsVersion(aUpdateInfo.version); 
  346.  
  347.       return (server.isNewerThan(local));
  348.     }
  349.     catch (ex)
  350.     {
  351.       // fail silently
  352.       debug("Exception getting httpHandler: " + ex);
  353.       return false;
  354.     }
  355.  
  356.     return false; // return value expected from this function
  357.   }, 
  358.  
  359.   xpinstallHaveNewer: function(aUpdateInfo)
  360.   {
  361.     // XXX Once InstallTrigger is a component we will be able to
  362.     //     get at it without needing to reference it from hiddenDOMWindow.
  363.     //     This will enable us to |compareVersion()|s even when 
  364.     //     XPInstall is disabled but update notifications are enabled.
  365.     //     See <http://bugzilla.mozilla.org/show_bug.cgi?id=121506>.
  366.     var ass = Components.classes["@mozilla.org/appshell/appShellService;1"].
  367.       getService(Components.interfaces.nsIAppShellService);
  368.     var trigger = ass.hiddenDOMWindow.InstallTrigger;
  369.     var diffLevel = trigger.compareVersion(aUpdateInfo.registryName, 
  370.       aUpdateInfo.version);
  371.     if (diffLevel < trigger.EQUAL && diffLevel != trigger.NOT_FOUND)
  372.       return true;
  373.     return false; // already have newer version or 
  374.                   // fail silently if old version not found on disk
  375.   },
  376.  
  377.   synthesizeVersion: function(aMisc, aProductSub)
  378.   {
  379.     // Strip out portion of nsIHttpProtocolHandler.misc that
  380.     // contains version info and stuff all ``missing'' portions
  381.     // with a default 0 value.  We are interested in the first 3
  382.     // numbers delimited by periods.  The 4th comes from aProductSub.
  383.     // e.g., x => x.0.0, x.1 => x.1.0, x.1.2 => x.1.2, x.1.2.3 => x.1.2
  384.     
  385.     var synthesized = "0.0.0.";
  386.  
  387.     // match only digits and periods after "rv:" in the misc
  388.     var onlyVer = /rv:([0-9.]+)/.exec(aMisc);
  389.  
  390.     // original string in onlyVer[0], matched substring in onlyVer[1]
  391.     if (onlyVer && onlyVer.length >= 2) 
  392.     {
  393.       var parts = onlyVer[1].split('.');
  394.       var len = parts.length;
  395.       if (len > 0)
  396.       {
  397.         synthesized = "";
  398.  
  399.         // extract first 3 dot delimited numbers in misc (after "rv:")
  400.         for (var i = 0; i < 3; ++i)
  401.         {
  402.           synthesized += ((len >= i+1) ? parts[i] : "0") + ".";
  403.         }
  404.       }
  405.     }
  406.  
  407.     // tack on productSub for nsVersion.mBuild field if available
  408.     synthesized += aProductSub ? aProductSub : "0";
  409.    
  410.     return synthesized;
  411.   },
  412.  
  413.   getBundle: function(aURI)
  414.   {
  415.     if (!aURI)
  416.       return null;
  417.  
  418.     var bundle = null;
  419.     try
  420.     {
  421.       var strBundleService = Components.
  422.         classes["@mozilla.org/intl/stringbundle;1"].
  423.         getService(Components.interfaces.nsIStringBundleService);
  424.       bundle = strBundleService.createBundle(aURI);
  425.     }
  426.     catch (ex)
  427.     {
  428.       bundle = null;
  429.       debug("Exception getting bundle " + aURI + ": " + ex);
  430.     }
  431.  
  432.     return bundle;
  433.   }
  434. }
  435.  
  436. ////////////////////////////////////////////////////////////////////////
  437. //
  438. //   nsVersion
  439. //
  440. //   Constructs a version object given a string representation.  This
  441. //   constructor populates the mMajor, mMinor, mRelease, and mBuild
  442. //   fields regardless of whether string contains all the fields.  
  443. //   The default for all unspecified fields is 0.
  444. //
  445. ////////////////////////////////////////////////////////////////////////
  446.  
  447. function nsVersion(aStringVersion)
  448. {
  449.   var parts = aStringVersion.split('.');
  450.   var len = parts.length;
  451.  
  452.   this.mMajor   = (len >= 1) ? this.getValidInt(parts[0]) : 0;
  453.   this.mMinor   = (len >= 2) ? this.getValidInt(parts[1]) : 0;
  454.   this.mRelease = (len >= 3) ? this.getValidInt(parts[2]) : 0;
  455.   this.mBuild   = (len >= 4) ? this.getValidInt(parts[3]) : 0;
  456. }
  457.  
  458. nsVersion.prototype = 
  459. {
  460.   isNewerThan: function(aOther)
  461.   {
  462.     if (this.mMajor == aOther.mMajor)
  463.     {
  464.       if (this.mMinor == aOther.mMinor)
  465.       {
  466.         if (this.mRelease == aOther.mRelease)
  467.         {
  468.           if (this.mBuild <= aOther.mBuild)
  469.             return false;
  470.           else
  471.             return true; // build is newer
  472.         }
  473.         else if (this.mRelease < aOther.mRelease)
  474.           return false;
  475.         else
  476.           return true; // release is newer
  477.       }
  478.       else if (this.mMinor < aOther.mMinor)
  479.         return false;
  480.       else
  481.         return true; // minor is newer
  482.     }
  483.     else if (this.mMajor < aOther.mMajor)
  484.       return false;
  485.     else
  486.       return true; // major is newer
  487.  
  488.     return false;
  489.   },
  490.  
  491.   getValidInt: function(aString)
  492.   {
  493.     var integer = parseInt(aString);
  494.     if (isNaN(integer))
  495.       return 0;
  496.     return integer;
  497.   }
  498. }
  499.  
  500. ////////////////////////////////////////////////////////////////////////
  501. //
  502. //   nsUpdateNotifierModule : nsIModule
  503. //
  504. ////////////////////////////////////////////////////////////////////////
  505.  
  506. var nsUpdateNotifierModule = 
  507. {
  508.   mClassName:     "Update Notifier",
  509.   mContractID:    "@mozilla.org/update-notifier;1",
  510.   mClassID:       Components.ID("8b6dcf5e-3b5a-4fff-bff5-65a8fa9d71b2"),
  511.  
  512.   getClassObject: function(aCompMgr, aCID, aIID)
  513.   {
  514.     if (!aCID.equals(this.mClassID))
  515.       throw Components.results.NS_ERROR_NO_INTERFACE;
  516.     if (!aIID.equals(Components.interfaces.nsIFactory))
  517.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  518.  
  519.     return this.mFactory;
  520.   },
  521.  
  522.   registerSelf: function(aCompMgr, aFileSpec, aLocation, aType)
  523.   {
  524.     if (kDebug)
  525.       dump("*** Registering nsUpdateNotifier (a JavaScript Module)\n");
  526.  
  527.     aCompMgr = aCompMgr.QueryInterface(
  528.                  Components.interfaces.nsIComponentRegistrar);
  529.     aCompMgr.registerFactoryLocation(this.mClassID, this.mClassName, 
  530.       this.mContractID, aFileSpec, aLocation, aType);
  531.  
  532.     // receive startup notification from the profile manager
  533.     // (we get |createInstance()|d at startup-notification time)
  534.     this.getCategoryManager().addCategoryEntry("profile-startup-category", 
  535.       this.mContractID, "", true, true);
  536.   },
  537.  
  538.   unregisterSelf: function(aCompMgr, aFileSpec, aLocation)
  539.   {
  540.     aCompMgr = aCompMgr.QueryInterface(
  541.                  Components.interfaces.nsIComponentRegistrar);
  542.     aCompMgr.unregisterFactoryLocation(this.mClassID, aFileSpec);
  543.  
  544.     this.getCategoryManager().deleteCategoryEntry("profile-startup-category", 
  545.       this.mContractID, true);
  546.   },
  547.  
  548.   canUnload: function(aCompMgr)
  549.   {
  550.     return true;
  551.   },
  552.  
  553.   getCategoryManager: function()
  554.   {
  555.     return Components.classes["@mozilla.org/categorymanager;1"].
  556.       getService(Components.interfaces.nsICategoryManager);
  557.   },
  558.  
  559.   //////////////////////////////////////////////////////////////////////
  560.   //
  561.   //   mFactory : nsIFactory
  562.   //
  563.   //////////////////////////////////////////////////////////////////////
  564.   mFactory:
  565.   {
  566.     createInstance: function(aOuter, aIID)
  567.     {
  568.       if (aOuter != null)
  569.         throw Components.results.NS_ERROR_NO_AGGREGATION;
  570.       if (!aIID.equals(Components.interfaces.nsIObserver) &&
  571.           !aIID.equals(Components.interfaces.nsIProfileStartupListener) &&
  572.           !aIID.equals(Components.interfaces.nsISupports))
  573.         throw Components.results.NS_ERROR_INVALID_ARG;
  574.  
  575.       // return the singleton 
  576.       return nsUpdateNotifier.QueryInterface(aIID);
  577.     },
  578.  
  579.     lockFactory: function(aLock)
  580.     {
  581.       // quiten warnings
  582.     }
  583.   }
  584. };
  585.  
  586. function NSGetModule(aCompMgr, aFileSpec)
  587. {
  588.   return nsUpdateNotifierModule;
  589. }
  590.  
  591. ////////////////////////////////////////////////////////////////////////
  592. //
  593. //   Debug helper
  594. //
  595. ////////////////////////////////////////////////////////////////////////
  596. if (!kDebug)
  597.   debug = function(m) {};
  598. else
  599.   debug = function(m) {dump("\t *** nsUpdateNotifier: " + m + "\n");};
  600.