home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February / PCWorld_2008-02_cd.bin / temacd / songbird / Songbird_0.4_windows-i686.exe / components / sbDisplayPanes.js < prev    next >
Text File  |  2007-12-21  |  21KB  |  644 lines

  1. /**
  2. //
  3. // BEGIN SONGBIRD GPL
  4. // 
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 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. Components.utils.import("resource://app/components/ArrayConverter.jsm");
  28.  
  29. const SONGBIRD_DISPLAYPANE_MANAGER_IID = Components.interfaces.sbIDisplayPaneManager;
  30.  
  31. const RDFURI_ADDON_ROOT               = "urn:songbird:addon:root" 
  32. const PREFIX_NS_SONGBIRD              = "http://www.songbirdnest.com/2007/addon-metadata-rdf#";
  33.  
  34.  
  35. function SB_NewDataRemote(a,b) {
  36.   return (new Components.Constructor("@songbirdnest.com/Songbird/DataRemote;1",
  37.                     "sbIDataRemote", "init"))(a,b);
  38. }
  39.  
  40.  
  41.  
  42.  
  43. /**
  44.  * Debug helper that serializes an RDF datasource to the console
  45.  */
  46. function dumpDS(prefix, ds) {
  47.   var outputStream = {
  48.     data: "",
  49.     close : function(){},
  50.     flush : function(){},
  51.     write : function (buffer,count){
  52.       this.data += buffer;
  53.       return count;
  54.     },
  55.     writeFrom : function (stream,count){},
  56.     isNonBlocking: false
  57.   }
  58.  
  59.   var serializer = Components.classes["@mozilla.org/rdf/xml-serializer;1"]
  60.                            .createInstance(Components.interfaces.nsIRDFXMLSerializer);
  61.   serializer.init(ds);
  62.  
  63.   serializer.QueryInterface(Components.interfaces.nsIRDFXMLSource);
  64.   serializer.Serialize(outputStream);
  65.   
  66.   outputStream.data.split('\n').forEach( function(line) {
  67.     dump(prefix + line + "\n");
  68.   });
  69. }
  70.  
  71. /**
  72.  * sbIContentPaneInfo
  73.  */
  74. function PaneInfo() {};
  75. PaneInfo.prototype = {
  76.  
  77.   requiredProperties: [ "contentUrl", 
  78.                         "contentTitle",
  79.                         "contentIcon",
  80.                         "suggestedContentGroups",
  81.                         "defaultWidth", 
  82.                         "defaultHeight" ],
  83.   optionalProperties: [ "showOnInstall" ],
  84.   
  85.   updateContentInfo: function(aNewTitle, aNewIcon) {
  86.     this.contentTitle = aNewTitle;
  87.     this.contentIcon = aNewIcon;
  88.   },
  89.   
  90.   verify: function() {
  91.     for (var i = 0; i < this.requiredProperties.length; i++) {
  92.       var property = this.requiredProperties[i];
  93.       if (! (typeof(this[property]) == 'string'
  94.                && this[property].length > 0)) 
  95.       {
  96.         throw("Invalid description. '" + property + "' is a required property.");
  97.       }
  98.     }
  99.   },
  100.   
  101.   QueryInterface: function(iid) {
  102.     if (!iid.equals(Components.interfaces.sbIDisplayPaneContentInfo)) 
  103.       throw Components.results.NS_ERROR_NO_INTERFACE;
  104.     return this;
  105.   }
  106. };
  107.  
  108.  
  109.  
  110.  
  111.  
  112.  
  113. /**
  114.  * /class DisplayPaneMetadataReader
  115.  * Responsible for reading addon metadata and performing 
  116.  * registration with DisplayPaneManager
  117.  */
  118. function DisplayPaneMetadataReader() {
  119.   //debug("DisplayPaneMetadataReader: ctor\n");
  120.   this._RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
  121.                         .getService(Components.interfaces.nsIRDFService);
  122.   this._datasource = this._RDF.GetDataSourceBlocking("rdf:addon-metadata");
  123.   this._manager = Components.classes["@songbirdnest.com/Songbird/DisplayPane/Manager;1"]
  124.                             .getService(SONGBIRD_DISPLAYPANE_MANAGER_IID);
  125.     
  126.   this._resources = {
  127.     root: this._RDF.GetResource(RDFURI_ADDON_ROOT),
  128.     // Helper to convert a string array into 
  129.     // RDF resources in this object
  130.     addSongbirdResources: function(list){
  131.       for (var i = 0; i < list.length; i++) {
  132.         this[list[i]] = this._RDF.GetResource(PREFIX_NS_SONGBIRD + list[i]);
  133.       }
  134.     },
  135.     _RDF: this._RDF
  136.   };
  137.   
  138.   // Make RDF resources for all properties expected
  139.   // in the displayPanes portion of an install.rdf     
  140.   this._resources.addSongbirdResources(PaneInfo.prototype.requiredProperties);
  141.   this._resources.addSongbirdResources(PaneInfo.prototype.optionalProperties);
  142.   this._resources.addSongbirdResources(
  143.      [ "displayPane",
  144.        "displayPanes" ] );
  145. }
  146. DisplayPaneMetadataReader.prototype = {
  147.  
  148.   _RDF: null,
  149.   
  150.   _manager: null,
  151.  
  152.   // Addon metadata rdf datasource
  153.   _datasource: null,
  154.  
  155.   // Hash of addon metadata RDF resources
  156.   _resources: null,
  157.  
  158.   /**
  159.    * Populate DisplayPaneManager using addon metadata
  160.    */
  161.   loadPanes: function loadPanes() {
  162.     //debug("DisplayPaneMetadataReader: loadPanes\n");
  163.     
  164.     // Get addon list
  165.     var containerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"]
  166.                                    .getService(Components.interfaces.nsIRDFContainerUtils);
  167.     var container = containerUtils.MakeSeq(this._datasource, this._resources.root);
  168.     var addons = container.GetElements();
  169.     
  170.     // Search all addons for displayPanes metadata
  171.     while (addons.hasMoreElements()) {
  172.       var addon = addons.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  173.       //debug("DisplayPaneMetadataReader.displayPanes: - processing " + addon.Value + "\n");
  174.       try {
  175.       
  176.         if (this._datasource.hasArcOut(addon, this._resources.displayPanes)) {
  177.           var displayPanesTarget = this._datasource.GetTarget(addon, this._resources.displayPanes, true)
  178.                                        .QueryInterface(Components.interfaces.nsIRDFResource);
  179.           
  180.           // Process all pane metadata
  181.           var items = this._datasource.GetTargets(displayPanesTarget, this._resources.displayPane, true)
  182.           while (items.hasMoreElements()) {
  183.             var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  184.             this._processDisplayPane(addon, item);
  185.           }
  186.         }
  187.         
  188.       } catch (e) {
  189.         this._reportErrors("", [  "An error occurred while processing " +
  190.                     "extension " + addon.Value + ".  Exception: " + e  ]);
  191.       }
  192.     }
  193.   },
  194.   
  195.   
  196.   /**
  197.    * Extract pane metadata
  198.    */
  199.   _processDisplayPane: function _processDisplayPane(addon, pane) {
  200.     var info = new PaneInfo();
  201.     
  202.     // Array of error messages
  203.     var errorList = [];
  204.     
  205.     // Fill the description object
  206.     this._populateDescription(pane, info, errorList);
  207.  
  208.     try {
  209.       info.verify();
  210.       info.defaultWidth = parseInt(info.defaultWidth);
  211.       info.defaultHeight = parseInt(info.defaultHeight);
  212.       info.showOnInstall = info.showOnInstall == "true";
  213.     } catch (e) {
  214.       errorList.push(e.toString());
  215.     }
  216.     
  217.     // If errors were encountered, then do not submit 
  218.     // to the Display Pane Manager
  219.     if (errorList.length > 0) {
  220.       this._reportErrors(
  221.           "Ignoring display pane addon in the install.rdf of extension " +
  222.           addon.Value + ". Message: ", errorList);
  223.       return;
  224.     }
  225.     
  226.     // Submit description
  227.     this._manager.registerContent( info.contentUrl, 
  228.                                    info.contentTitle,
  229.                                    info.contentIcon,
  230.                                    info.defaultWidth,
  231.                                    info.defaultHeight,
  232.                                    info.suggestedContentGroups,
  233.                                    info.showOnInstall );
  234.     
  235.     //debug("DisplayPaneMetadataReader: registered pane " + info.contentTitle
  236.     //        + " from addon " + addon.Value + " \n");
  237.   },
  238.  
  239.  
  240.  
  241.   /**
  242.    * \brief Populate a description object by looking up requiredProperties and
  243.    *        optionalProperties in a the given rdf source.
  244.    *
  245.    * \param source RDF resource from which to obtain property values
  246.    * \param description Object with requiredProperties and optionalProperties arrays
  247.    * \param errorList An array to which error messages should be added
  248.    */
  249.   _populateDescription: function _populateDescription(source, description, errorList) {
  250.  
  251.     for (var i = 0; i < description.requiredProperties.length; i++) {
  252.       this._requireProperty(source, description, 
  253.                 description.requiredProperties[i], errorList);
  254.     }
  255.     for (var i = 0; i < description.optionalProperties.length; i++) {
  256.       this._copyProperty(source, description, 
  257.                 description.optionalProperties[i], errorList);
  258.     }
  259.   },
  260.   
  261.   
  262.   /**
  263.    * \brief Attempts to copy a property from an RDFResource into a
  264.    *        container object and reports an error on failure.
  265.    *
  266.    * \param source RDF resource from which to obtain the value
  267.    * \param description Container object to receive the value
  268.    * \param property String property to be copied
  269.    * \param errorList An array to which error messages should be added
  270.    */
  271.   _requireProperty: function _requireProperty(source, description, property, errorList) {
  272.     this._copyProperty(source, description, property);
  273.     if (description[property] == undefined) 
  274.     {
  275.       errorList.push(
  276.         property + " is a required property."
  277.       );
  278.     }
  279.   },
  280.  
  281.   /**
  282.    * \brief Copies a property from an RDFResource into a container object.
  283.    *
  284.    * \param source RDF resource from which to obtain the value
  285.    * \param description Container object to receive the value
  286.    * \param property String property to be copied
  287.    */
  288.   _copyProperty: function _copyProperty(source, description, property) {
  289.     description[property] = this._getProperty(source, property);
  290.   },
  291.  
  292.  
  293.   /**
  294.    * \brief Copies a property from an RDFResource into a container object.
  295.    *
  296.    * \param source RDF resource from which to obtain the value
  297.    * \param description Container object to receive the value
  298.    * \param property String property to be copied
  299.    */  
  300.   _getProperty: function _getProperty(source, property) {
  301.     //debug("DisplayPaneMetadataReader._getProperty " + source.Value + " " + property + "\n");
  302.     var target = this._datasource.GetTarget(source, this._resources[property], true);
  303.     if ( target instanceof Components.interfaces.nsIRDFInt
  304.          || target instanceof Components.interfaces.nsIRDFLiteral 
  305.          || target instanceof Components.interfaces.nsIRDFResource )
  306.     {
  307.       return target.Value;
  308.     }
  309.     return undefined;
  310.   },
  311.  
  312.   
  313.   /**
  314.    * \brief Dump a list of errors to the console and jsconsole
  315.    *
  316.    * \param contextMessage Additional prefix to use before every line
  317.    * \param errorList Array of error messages
  318.    */
  319.   _reportErrors: function _reportErrors(contextMessage, errorList) {
  320.     var consoleService = Components.classes["@mozilla.org/consoleservice;1"].
  321.          getService(Components.interfaces.nsIConsoleService);
  322.     for (var i = 0; i  < errorList.length; i++) {
  323.       consoleService.logStringMessage("Display Pane Metadata Reader: " 
  324.                                        + contextMessage + errorList[i]);
  325.       dump("DisplayPaneMetadataReader: " + contextMessage + errorList[i] + "\n");
  326.     }
  327.   }
  328. }
  329.  
  330.  
  331.  
  332.  
  333.  
  334.  
  335. /**
  336.  * /class DisplayPaneManager
  337.  * /brief Coordinates display pane content
  338.  *
  339.  * Acts as a registry for display panes and available content.
  340.  *
  341.  * \sa sbIDisplayPaneManager
  342.  */
  343. function DisplayPaneManager() {
  344. }
  345.  
  346. DisplayPaneManager.prototype.constructor = DisplayPaneManager;
  347.  
  348. DisplayPaneManager.prototype = {
  349.   classDescription: "Songbird Display Pane Manager Service Interface",
  350.   classID:          Components.ID("{6aef120f-d7ad-414d-a93d-3ac945e64301}"),
  351.   contractID:       "@songbirdnest.com/Songbird/DisplayPane/Manager;1",
  352.  
  353.   LOG: function(str) {
  354.     var consoleService = Components.classes['@mozilla.org/consoleservice;1']
  355.                             .getService(Components.interfaces.nsIConsoleService);
  356.     consoleService.logStringMessage(str);
  357.   },
  358.   
  359.   _contentList: [],
  360.   _instantiatorsList: [],
  361.   _delayedInstantiations: [],
  362.   _listenersList: [],
  363.  
  364.   _addonMetadataLoaded: false,
  365.   
  366.   /**
  367.    * Make sure that we've read display pane registration
  368.    * metadata from all extension install.rdf files.
  369.    */
  370.   ensureAddonMetadataLoaded: function() {
  371.     if (this._addonMetadataLoaded) {
  372.       return;
  373.     }
  374.     this._addonMetadataLoaded = true;
  375.     
  376.     // Load the addon metadata
  377.     var metadataReader = new DisplayPaneMetadataReader();
  378.     metadataReader.loadPanes();
  379.   },
  380.   
  381.  
  382.   /**
  383.    * given a list of pane parameters, return a new sbIDisplayPaneContentInfo
  384.    */
  385.   makePaneInfo: function(aContentUrl,
  386.                          aContentTitle,
  387.                          aContentIcon,
  388.                          aSuggestedContentGroups,
  389.                          aDefaultWidth,
  390.                          aDefaultHeight) {
  391.     var paneInfo = new PaneInfo();
  392.     paneInfo.contentUrl = aContentUrl;
  393.     paneInfo.contentTitle = aContentTitle;
  394.     paneInfo.contentIcon = aContentIcon;
  395.     paneInfo.suggestedContentGroups = aSuggestedContentGroups;
  396.     paneInfo.defaultWidth = aDefaultWidth;
  397.     paneInfo.defaultHeight = aDefaultHeight;
  398.     
  399.     return paneInfo;
  400.   },
  401.  
  402.   /**
  403.    * \see sbIDisplayPaneManager
  404.    */
  405.   getPaneInfo: function(aContentUrl) {
  406.     this.ensureAddonMetadataLoaded();
  407.   
  408.     for each (var pane in this._contentList) {
  409.       ////debug("PANE: " + pane.contentTitle  + " XXX " + aContentUrl + "\n\n");
  410.       if (pane.contentUrl == aContentUrl) 
  411.         return pane;
  412.     }
  413.     return null;
  414.   },
  415.  
  416.   /**
  417.    * \see sbIDisplayPaneManager
  418.    */
  419.   get contentList() {
  420.     this.ensureAddonMetadataLoaded();
  421.     return ArrayConverter.enumerator(this._contentList);
  422.   },
  423.   
  424.   /**
  425.    * \see sbIDisplayPaneManager
  426.    */
  427.   get instantiatorsList() {
  428.     for (var i = this._instantiatorsList.length - 1; i >= 0; --i) {
  429.       if (!(this._instantiatorsList[i] instanceof
  430.             Components.interfaces.sbIDisplayPaneInstantiator)) {
  431.         Components.utils.reportError("Warning: found bad instantiator; "+
  432.                                      "possibly via removal from DOM");
  433.         this._instantiatorsList.splice(i, 1);
  434.       }
  435.     }
  436.     return ArrayConverter.enumerator(this._instantiatorsList);
  437.   },
  438.   
  439.   /**
  440.    * \see sbIDisplayPaneManager
  441.    */
  442.   registerContent: function(aContentUrl,
  443.                             aContentTitle,
  444.                             aContentIcon,
  445.                             aDefaultWidth,
  446.                             aDefaultHeight,
  447.                             aSuggestedContentGroups,
  448.                             aAutoShow) {
  449.                             
  450.     ////debug("REGISTER: " + aContentUrl + "\n");
  451.     
  452.     var info = this.getPaneInfo(aContentUrl);
  453.     if (info) {
  454.       throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
  455.     }
  456.     info = this.makePaneInfo(aContentUrl,
  457.                              aContentTitle,
  458.                              aContentIcon,
  459.                              aSuggestedContentGroups,
  460.                              aDefaultWidth,
  461.                              aDefaultHeight);
  462.     this._contentList.push(info);
  463.     for each (var listener in this._listenersList) {
  464.       listener.onRegisterContent(info);
  465.     }
  466.     // if we have never seen this pane, show it in its prefered group
  467.     var known = SB_NewDataRemote("displaypane.known." + aContentUrl, null);
  468.     if (!known.boolValue) {
  469.       if (aAutoShow) {
  470.         if (!this.tryInstantiation(info)) {
  471.           this._delayedInstantiations.push(info);
  472.         }
  473.       }
  474.       // remember we've seen this pane, let the pane hosts reload on their own if they need to
  475.       known.boolValue = true;
  476.     }
  477.   },
  478.   
  479.   /**
  480.    * \see sbIDisplayPaneManager
  481.    */
  482.   unregisterContent: function(aContentUrl) {
  483.     for (var contentIndex = 0; contentIndex < this._contentList.length; contentIndex++) {
  484.       if (this._contentList[contentIndex].contentUrl != aContentUrl) {
  485.         continue;
  486.       }
  487.  
  488.       // any instantiator currently hosting this url should be emptied
  489.       for each (var instantiator in this._instantiatorsList) {
  490.         if (instantiator.contentUrl == aContentUrl) {
  491.           instantiator.hide();
  492.         }
  493.       }
  494.       // also remove it from the delayed instantiation list
  495.       for (instantiatorIndex = this._delayedInstantiations.length - 1; instantiatorIndex >= 0; --instantiatorIndex) {
  496.         if (this._delayedInstantiations[instantiatorIndex].contentUrl == aContentUrl) {
  497.           this._delayedInstantiations.splice(instantiatorIndex, 1);
  498.         }
  499.       }
  500.  
  501.       var [info] = this._contentList.splice(contentIndex, 1);
  502.       
  503.       for each (var listener in this._listenersList) {
  504.         listener.onUnregisterContent(info);
  505.       }
  506.       return;
  507.     }
  508.   },
  509.   
  510.   /**
  511.    * \see sbIDisplayPaneManager
  512.    */
  513.   registerInstantiator: function(aInstantiator) {
  514.     if (this._instantiatorsList.indexOf(aInstantiator) > -1) {
  515.       Components.utils.reportError("Attempt to re-register instantiator ignored\n" +
  516.                                    (new Error()).stack);
  517.       return;
  518.     }
  519.     this._instantiatorsList.push(aInstantiator);
  520.     for each (var listener in this._listenersList) {
  521.       listener.onRegisterInstantiator(aInstantiator);
  522.     }
  523.     this.processDelayedInstantiations();
  524.   },
  525.  
  526.   /**
  527.    * \see sbIDisplayPaneManager
  528.    */
  529.   unregisterInstantiator: function(aInstantiator) {
  530.     var index = this._instantiatorsList.indexOf(aInstantiator);
  531.     if (index < 0) {
  532.       // not found
  533.       return;
  534.     }
  535.     this._instantiatorsList.splice(index, 1);
  536.     for each (var listener in this._listenersList) {
  537.       listener.onUnregisterInstantiator(aInstantiator);
  538.     }
  539.   },
  540.   
  541.   /**
  542.    * given a content group list (from a sbIDisplayPaneContentInfo),
  543.    * return the the first instantiator that matches the earliest possible
  544.    * content group
  545.    */
  546.   getFirstInstantiatorForGroupList: function(aContentGroupList) {
  547.     var groups = aContentGroupList.toUpperCase().split(";");
  548.     for each (var group in groups) {
  549.       for each (var instantiator in this._instantiatorsList) {
  550.         if (instantiator.contentGroup.toUpperCase() == group) {
  551.           return instantiator;
  552.         }
  553.       }
  554.     }
  555.     return null;
  556.   },
  557.   
  558.   processDelayedInstantiations: function() {
  559.     var table = [];
  560.     for each (var info in this._delayedInstantiations) {
  561.       if (!this.isValidPane(info) || this.tryInstantiation(info)) {
  562.         continue;
  563.       }
  564.       table.push(info);
  565.     }
  566.     this._delayedInstantiations = table;
  567.   },
  568.   
  569.   tryInstantiation: function(info) {
  570.     var instantiator = this.getFirstInstantiatorForGroupList(info.suggestedContentGroups);
  571.     if (instantiator) {
  572.       instantiator.loadContent(info);
  573.       return true;
  574.     }
  575.     return false;
  576.   },
  577.   
  578.   isValidPane: function(aPane) {
  579.     this.ensureAddonMetadataLoaded();
  580.     for each (var pane in this._contentList) {
  581.       if (pane == aPane) return true;
  582.     }
  583.     return false;
  584.   },
  585.  
  586.   showPane: function(aContentUrl) {
  587.     for each (var instantiator in this._instantiatorsList) {
  588.       if (instantiator.contentUrl == aContentUrl) {
  589.         // we already have a pane with this content
  590.         instantiator.collapsed = false;
  591.         return;
  592.       }
  593.     }
  594.     var info = this.getPaneInfo(aContentUrl);
  595.     if (info) {
  596.       if (!this.tryInstantiation(info)) {
  597.         this._delayedInstantiations.push(info);
  598.       }
  599.     } else {
  600.       throw new Error("Content URL was not found in list of registered panes");
  601.     }
  602.   },
  603.   
  604.   addListener: function(aListener) {
  605.     this._listenersList.push(aListener);
  606.   },
  607.   
  608.   removeListener: function(aListener) {
  609.     var index = this._listenersList.indexOf(aListener);
  610.     if (index > -1)
  611.       this._listenersList.splice(index, 1);
  612.   },
  613.   
  614.   updateContentInfo: function(aContentUrl, aNewContentTitle, aNewContentIcon) {
  615.     var info = this.getPaneInfo(aContentUrl);
  616.     if (!info) {
  617.       throw Components.results.NS_ERROR_NOT_INITIALIZED;
  618.     }
  619.  
  620.     info.updateContentInfo(aNewContentTitle, aNewContentIcon);
  621.     // change the live title for every instance of this content
  622.     for each (var instantiator in this._instantiatorsList) {
  623.       if (instantiator.contentUrl == aContentUrl) {
  624.         instantiator.contentTitle = aNewContentTitle;
  625.         instantiator.contentIcon = aNewContentIcon;
  626.       }
  627.     }
  628.     for each (var listener in this._listenersList) {
  629.       listener.onPaneInfoChanged(info);
  630.     }
  631.   },
  632.  
  633.   /**
  634.    * \see nsISupports.idl
  635.    */
  636.   QueryInterface:
  637.     XPCOMUtils.generateQI([SONGBIRD_DISPLAYPANE_MANAGER_IID])
  638. }; // DisplayPaneManager.prototype
  639.  
  640. function NSGetModule(compMgr, fileSpec) {
  641.   return XPCOMUtils.generateModule([DisplayPaneManager]);
  642. }
  643.  
  644.