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 / sbAddonMetadata.js < prev    next >
Text File  |  2007-10-27  |  15KB  |  467 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.  
  27.  
  28. /**
  29.  * \file sbAddonMetadata.js
  30.  * \brief Provides an nsIRDFDataSource with the contents of all 
  31.  *        addon install.rdf files.
  32.  */ 
  33.  
  34. const CONTRACTID = "@mozilla.org/rdf/datasource;1?name=addon-metadata";
  35. const CLASSNAME = "Songbird Addon Metadata Datasource";
  36. const CID = Components.ID("{a1edd551-0f29-4ce9-aebc-92fbee77f37e}");
  37. const IID = Components.interfaces.nsIRDFDataSource;
  38.  
  39. const FILE_INSTALL_MANIFEST = "install.rdf";
  40. const FILE_EXTENSIONS       = "extensions.rdf";
  41. const FILE_ADDONMETADATA    = "addon-metadata.rdf";
  42.  
  43. const PREF_LASTMODIFIED = "songbird.addonmetadata.lastModifiedTime";
  44.  
  45. const KEY_PROFILEDIR                  = "ProfD";
  46.  
  47. const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
  48. const RDFURI_ADDON_ROOT               = "urn:songbird:addon:root"
  49. const PREFIX_ADDON_URI                = "urn:songbird:addon:";
  50. const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
  51. const PREFIX_NS_SONGBIRD              = "http://www.songbirdnest.com/2007/addon-metadata-rdf#";
  52. const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
  53.  
  54. function SONGBIRD_NS(property) {
  55.   return PREFIX_NS_SONGBIRD + property;
  56. }
  57.  
  58. function EM_NS(property) {
  59.   return PREFIX_NS_EM + property;
  60. }
  61.  
  62. function ADDON_NS(id) {
  63.   return PREFIX_ADDON_URI + id;
  64. }
  65.  
  66. function ITEM_NS(id) {
  67.   return PREFIX_ITEM_URI + id;
  68. }
  69.  
  70.  
  71. /**
  72.  * /class AddonMetadata
  73.  * /brief Provides an nsIRDFDataSource with the contents of all 
  74.  *        addon install.rdf files.
  75.  */
  76. function AddonMetadata() {
  77.   //debug("\nAddonMetadata: constructed\n");
  78.   
  79.   this._RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
  80.                    .getService(Components.interfaces.nsIRDFService);
  81.  
  82.   try {
  83.  
  84.     // If possible, load the cached datasource from disk. 
  85.     // Otherwise rebuild it by reading the metadata from all addons
  86.     if (!this._loadDatasource() || this._isRebuildRequired()) {
  87.       //debug("AddonMetadata: rebuilding addon metadata datasource\n");
  88.       this._purgeDatasource();
  89.       this._buildDatasource();
  90.     }
  91.     
  92.   } catch (e) {
  93.     dump("AddonMetadata: Constructor Error: " + e.toString());
  94.   }
  95.   
  96.   var os  = Components.classes["@mozilla.org/observer-service;1"]
  97.                       .getService(Components.interfaces.nsIObserverService);
  98.   os.addObserver(this, "xpcom-shutdown", false);
  99. };
  100.  
  101. AddonMetadata.prototype = {
  102.  
  103.   constructor: AddonMetadata,
  104.  
  105.   _RDF: null,
  106.   _datasource: null,
  107.  
  108.   
  109.   /**
  110.    * Return true if cached addons metadata datasource is no longer valid.
  111.    * Uses the last modified date on the extension manager's data file to
  112.    * determine if extensions have changed.
  113.    */
  114.   _isRebuildRequired: function _isRebuildRequired() {
  115.     var emDataFile = this._getProfileFile(FILE_EXTENSIONS);
  116.     
  117.     if (!emDataFile.exists()) {      
  118.       //debug("AddonMetadata._isRebuildRequired: " + FILE_EXTENSIONS + " not found\n");
  119.       return true;
  120.     }
  121.  
  122.     var newLastModified = emDataFile.lastModifiedTime.toString();
  123.  
  124.     // When was the last time we noticed that extensions.rdf had been modified
  125.     var prefs = Components.classes["@mozilla.org/preferences-service;1"]
  126.                           .getService(Components.interfaces.nsIPrefBranch);
  127.     var lastModified = 0;
  128.     try {  
  129.       lastModified = prefs.getCharPref(PREF_LASTMODIFIED);
  130.     } catch (e) {}    
  131.     
  132.     // If the extensions.rdf hasn't changed, then we don't need to rebuild
  133.     //debug("AddonMetadata._isRebuildRequired: " + lastModified + " == " + newLastModified + "\n");
  134.     if (lastModified == newLastModified) {
  135.       return false;
  136.     }
  137.         
  138.     // Store the new last modified time
  139.     prefs.setCharPref(PREF_LASTMODIFIED, newLastModified);
  140.  
  141.     //debug("AddonMetadata._isRebuildRequired: true\n");
  142.  
  143.     // Extensions.rdf has changed, so we will need to rebuild the addons datasource.
  144.     return true;
  145.   },
  146.   
  147.   
  148.   
  149.   /**
  150.    * Attempt to load the data source from disk.  Return false 
  151.    * if the datasource needs populating. 
  152.    */
  153.   _loadDatasource: function _loadDatasource() {
  154.     //debug("\nAddonMetadata: _loadDatasource \n");
  155.     
  156.     var file = this._getProfileFile(FILE_ADDONMETADATA);
  157.     
  158.     try { 
  159.       this._datasource = this._getDatasource(file);
  160.     } catch (e) {
  161.       // Load did not go ok.  Will need to rebuild
  162.       return false;
  163.     }
  164.     
  165.     if (!file.exists()) {
  166.       return false;
  167.     }
  168.     
  169.     return true;
  170.   },
  171.   
  172.  
  173.  
  174.   /**
  175.    * Clean out the contents of the datasource
  176.    */
  177.   _purgeDatasource: function _purgeDatasource() {
  178.     //debug("\nAddonMetadata: _purgeDatasource \n");
  179.     
  180.     var file = this._getProfileFile(FILE_ADDONMETADATA);
  181.     
  182.     // Make sure the RDF service isn't caching our ds
  183.     this._RDF.UnregisterDataSource(this._datasource);
  184.     
  185.     // Kill the file
  186.     try { 
  187.       if (file.exists()) {
  188.         file.remove(false);
  189.       }
  190.     } catch (e) {
  191.       debug("\nAddonMetadata: _purgeDatasource: Could not remove " 
  192.             + "addon-metadata.rdf. Bad things may happen.\n");
  193.     }
  194.  
  195.     // Reload the datasource.  Since the file no longer exists
  196.     // this will result in a new empty DS.
  197.     this._loadDatasource();
  198.   },
  199.   
  200.     
  201.   
  202.  
  203.   /**
  204.    * Given a filename, returns an nsIFile pointing to 
  205.    * profile/filename
  206.    */
  207.   _getProfileFile: function _getProfileFile(filename) {
  208.     // get profile directory
  209.     var file = Components.classes["@mozilla.org/file/directory_service;1"]
  210.                          .getService(Components.interfaces.nsIProperties)
  211.                          .get("ProfD", Components.interfaces.nsIFile);
  212.     var localFile = file.QueryInterface(Components.interfaces.nsILocalFile);                         
  213.     localFile.appendRelativePath(filename)
  214.     return file;
  215.   },
  216.  
  217.  
  218.   
  219.   /**
  220.    * Populate the datasource with the contents of all 
  221.    * addon install.rdf files.
  222.    */
  223.   _buildDatasource: function _buildDatasource() {
  224.     //debug("\nAddonMetadata: _buildDatasource \n");
  225.  
  226.     // Make a container to list all installed extensions
  227.     var itemRoot = this._RDF.GetResource(RDFURI_ADDON_ROOT);    
  228.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  229.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  230.     var container = cu.MakeSeq(this._datasource, itemRoot);
  231.  
  232.     var extManager = Components.classes["@mozilla.org/extensions/manager;1"]
  233.                            .getService(Components.interfaces.nsIExtensionManager);
  234.    
  235.     // Read the install.rdf for every addon
  236.     var addons = extManager.getItemList(Components.interfaces.nsIUpdateItem.TYPE_ADDON, {});
  237.     for (var i = 0; i < addons.length; i++)
  238.     {
  239.       var id = addons[i].id;
  240.             
  241.       // If the extension is disabled, do not include it in our datasource 
  242.       if (this._isExtensionDisabled(id))  {
  243.         //debug("\nAddonMetadata:  id {" + id +  "} is disabled.\n");
  244.         continue;
  245.       }
  246.       
  247.       //debug("\nAddonMetadata:  loading install.rdf for id {" + id +  "}\n");
  248.       
  249.       var location = extManager.getInstallLocation(id);
  250.       var installManifestFile = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  251.  
  252.       if (!installManifestFile.exists()) {
  253.         this._reportErrors(["install.rdf for id " + id +  " was not found " + 
  254.                   "at location " + installManifestFile.path]);
  255.       }
  256.       
  257.       var manifestDS = this._getDatasource(installManifestFile);
  258.       var itemNode = this._RDF.GetResource(ADDON_NS(id));
  259.       // Copy the install.rdf metadata into the master datasource
  260.       this._copyManifest(itemNode, manifestDS);
  261.       
  262.       // Add the new install.rdf root to the list of extensions
  263.       container.AppendElement(itemNode);
  264.     }
  265.  
  266.     // Save changes  
  267.     this._datasource.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource)
  268.                     .Flush();
  269.     //debug("\nAddonMetadata: _buildDatasource complete \n");
  270.   },
  271.   
  272.   
  273.   
  274.   /**
  275.    * Return true if the given extension GUID has been disabled in the EM
  276.    */
  277.   _isExtensionDisabled: function _isExtensionDisabled(id) {
  278.     var item = this._RDF.GetResource(ITEM_NS(id));
  279.     var extManager = Components.classes["@mozilla.org/extensions/manager;1"]
  280.                         .getService(Components.interfaces.nsIExtensionManager);
  281.     var userDisabled = this._RDF.GetResource(EM_NS("userDisabled"));
  282.     if (extManager.datasource.hasArcOut(item, userDisabled)) {
  283.       var target = extManager.datasource.GetTarget(item, userDisabled, true);
  284.       if (target instanceof Components.interfaces.nsIRDFLiteral){
  285.         target = target.QueryInterface(Components.interfaces.nsIRDFLiteral);
  286.         return target.Value == "true";
  287.       }
  288.     }                      
  289.     return false;
  290.   },
  291.  
  292.   
  293.   
  294.   /**
  295.    * Copy all nodes and assertions into the main datasource,
  296.    * renaming the install manifest to the id of the extension.
  297.    */
  298.   _copyManifest:  function _copyManifest(itemNode, manifestDS) {
  299.   
  300.     // Copy all resources from the manifest to the main DS
  301.     var resources = manifestDS.GetAllResources();
  302.     while (resources.hasMoreElements()) {
  303.       var resource = resources.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  304.       
  305.       // Get all arcs out of the resource
  306.       var arcs = manifestDS.ArcLabelsOut(resource);
  307.       while (arcs.hasMoreElements()) {
  308.         var arc = arcs.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  309.               
  310.         // For each arc, get all targets
  311.         var targets = manifestDS.GetTargets(resource, arc, true);
  312.         while (targets.hasMoreElements()) {
  313.           var target = targets.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  314.           
  315.           // If this resource is the manifest root, replace it
  316.           // with a resource representing the current addon
  317.           newResource = resource;
  318.           if (resource.Value == RDFURI_INSTALL_MANIFEST_ROOT) {
  319.             newResource = itemNode;
  320.           }
  321.                     
  322.           // Otherwise, assert into the main ds
  323.           this._datasource.Assert(newResource, arc, target, true);
  324.         }
  325.       }                   
  326.     }
  327.   },
  328.  
  329.   
  330.  
  331.  
  332.  
  333.   /**
  334.    * Gets a datasource from a file.
  335.    * @param   file
  336.    *          The nsIFile that containing RDF/XML
  337.    * @returns RDF datasource
  338.    */
  339.   _getDatasource: function _getDatasource(file) {
  340.     var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  341.                            .getService(Components.interfaces.nsIIOService);
  342.     var fph = ioServ.getProtocolHandler("file")
  343.                     .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  344.  
  345.     var fileURL = fph.getURLSpecFromFile(file);
  346.     var ds = this._RDF.GetDataSourceBlocking(fileURL);
  347.  
  348.     return ds;
  349.   },
  350.  
  351.  
  352.   
  353.   _deinit: function _deinit() {
  354.     //debug("\nAddonMetadata: deinit\n");
  355.  
  356.     this._RDF = null;
  357.     this._datasource = null;
  358.   },
  359.     
  360.   
  361.  
  362.   // watch for XRE startup and shutdown messages 
  363.   observe: function(subject, topic, data) {
  364.     var os = Components.classes["@mozilla.org/observer-service;1"]
  365.                        .getService(Components.interfaces.nsIObserverService);
  366.     switch (topic) {
  367.     case "xpcom-shutdown":
  368.       os.removeObserver(this, "xpcom-shutdown");
  369.       this._deinit();
  370.       break;
  371.     }
  372.   },
  373.   
  374.   
  375.   
  376.   _reportErrors: function _reportErrors(errorList) {
  377.     var consoleService = Components.classes["@mozilla.org/consoleservice;1"].
  378.          getService(Components.interfaces.nsIConsoleService);
  379.     for (var i = 0; i  < errorList.length; i++) {
  380.       consoleService.logStringMessage("Addon Metadata Reader: " + errorList[i]);
  381.       debug("AddonMetadataReader: " + errorList[i] + "\n");
  382.     }
  383.   },
  384.  
  385.  
  386.  
  387.   /**
  388.    * See nsISupports.idl
  389.    */
  390.   QueryInterface: function(iid) {
  391.     if (!iid.equals(IID) &&
  392.         !iid.equals(Components.interfaces.nsIObserver) && 
  393.         !iid.equals(Components.interfaces.nsISupports))
  394.       throw Components.results.NS_ERROR_NO_INTERFACE;
  395.     
  396.     // THIS IS SO WRONG! 
  397.     // We can get away with it though since we really only want the datasource
  398.     // and never the object that builds it.
  399.     if (iid.equals(IID)) {
  400.       return this._datasource;
  401.     }
  402.     
  403.     return this;
  404.   }
  405. }; // AddonMetadata.prototype
  406.  
  407.  
  408.  
  409.  
  410.  
  411.  
  412.  
  413. /**
  414.  * ----------------------------------------------------------------------------
  415.  * Registration for XPCOM
  416.  * ----------------------------------------------------------------------------
  417.  */
  418. var gModule = {
  419.   registerSelf: function(componentManager, fileSpec, location, type) {
  420.     componentManager = componentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  421.     for (var key in this._objects) {
  422.       var obj = this._objects[key];
  423.       componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  424.                                                fileSpec, location, type);
  425.     }
  426.   },
  427.  
  428.   getClassObject: function(componentManager, cid, iid) {
  429.     if (!iid.equals(Components.interfaces.nsIFactory))
  430.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  431.  
  432.     for (var key in this._objects) {
  433.       if (cid.equals(this._objects[key].CID))
  434.         return this._objects[key].factory;
  435.     }
  436.     
  437.     throw Components.results.NS_ERROR_NO_INTERFACE;
  438.   },
  439.  
  440.   _makeFactory: #1= function(ctor) {
  441.     function ci(outer, iid) {
  442.       if (outer != null)
  443.         throw Components.results.NS_ERROR_NO_AGGREGATION;
  444.       return (new ctor()).QueryInterface(iid);
  445.     } 
  446.     return { createInstance: ci };
  447.   },
  448.   
  449.   _objects: {
  450.     AddonMetadata:     {   CID        : CID,
  451.                            contractID : CONTRACTID,
  452.                            className  : CLASSNAME,
  453.                            factory    : #1#(AddonMetadata)
  454.                          },
  455.   },
  456.  
  457.   canUnload: function(componentManager) { 
  458.     return true; 
  459.   }
  460. }; // gModule
  461.  
  462. function NSGetModule(comMgr, fileSpec) {
  463.   return gModule;
  464. } // NSGetModule
  465.  
  466.  
  467.