home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / components / sbMediaPageManager.js < prev    next >
Text File  |  2012-06-08  |  17KB  |  534 lines

  1. /*
  2.  *=BEGIN SONGBIRD GPL
  3.  *
  4.  * This file is part of the Songbird web player.
  5.  *
  6.  * Copyright(c) 2005-2010 POTI, Inc.
  7.  * http://www.songbirdnest.com
  8.  *
  9.  * This file may be licensed under the terms of of the
  10.  * GNU General Public License Version 2 (the ``GPL'').
  11.  *
  12.  * Software distributed under the License is distributed
  13.  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
  14.  * express or implied. See the GPL for the specific language
  15.  * governing rights and limitations.
  16.  *
  17.  * You should have received a copy of the GPL along with this
  18.  * program. If not, go to http://www.gnu.org/licenses/gpl.html
  19.  * or write to the Free Software Foundation, Inc.,
  20.  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21.  *
  22.  *=END SONGBIRD GPL
  23.  */
  24.  
  25. const Cc = Components.classes;
  26. const Ci = Components.interfaces;
  27. const Cr = Components.results;
  28. const Ce = Components.Exception;
  29. const Cu = Components.utils;
  30.  
  31. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  32. Cu.import("resource://app/jsmodules/ArrayConverter.jsm");
  33. Cu.import("resource://app/jsmodules/RDFHelper.jsm");
  34. Cu.import("resource://app/jsmodules/sbProperties.jsm");
  35.  
  36. function MediaPageManager() {
  37. }
  38.  
  39. MediaPageManager.prototype = {
  40.   classDescription: "Songbird MediaPage Manager",
  41.   classID:          Components.ID("{e63463d0-357c-4035-af33-db670ee1b7f2}"),
  42.   contractID:       "@songbirdnest.com/Songbird/MediaPageManager;1",
  43.   QueryInterface:   XPCOMUtils.generateQI([Ci.sbIMediaPageManager]),
  44.   
  45.   _pageInfoArray: [],
  46.   
  47.   // PageInfo objects for the fallback mediapages.  Set by _registerDefaults.
  48.   _defaultPlaylistPage:  null,
  49.   _defaultFilteredPlaylistPage: null,
  50.   
  51.   
  52.   registerPage: function(aName, aURL, aIcon, aMatchInterface) {
  53.     // Make sure we don't already have a page with
  54.     // the given url
  55.     var pageInfo;
  56.     
  57.     for each (pageInfo in this._pageInfoArray) {
  58.       if (pageInfo.contentUrl == aURL) {
  59.         throw new Error("Page URL already registered: " + aURL);
  60.       }
  61.     }
  62.     
  63.     // Make a PageInfo object
  64.     pageInfo = {
  65.       get contentTitle() { 
  66.         return aName;
  67.       },
  68.       get contentUrl() {
  69.         return aURL;
  70.       },
  71.       get contentIcon() {
  72.         return aIcon;
  73.       },
  74.       get matchInterface() {
  75.         return aMatchInterface;
  76.       },
  77.       QueryInterface: function(iid) {
  78.         if (!iid.equals(Ci.sbIMediaPageInfo) &&
  79.             !iid.equals(Ci.nsISupports))
  80.           throw Cr.NS_ERROR_NO_INTERFACE;
  81.         return this;
  82.       }
  83.     };
  84.     
  85.     // Store the page into our page array
  86.     this._pageInfoArray.push(pageInfo);
  87.     
  88.     // And return it to the caller so he may use that to unregister
  89.     return pageInfo;
  90.   },
  91.   
  92.   unregisterPage: function(aPageInfo) {
  93.  
  94.     // If unregistering the default pages, must remove shortcut pointers
  95.     if (this._defaultFilteredPlaylistPage && 
  96.         this._defaultFilteredPlaylistPage.contentUrl == aPageInfo.contentUrl) {
  97.       this._defaultFilteredPlaylistPage = null;
  98.     } else if (this._defaultPlaylistPage && 
  99.       this._defaultPlaylistPage.contentUrl == aPageInfo.contentUrl) {
  100.       this._defaultPlaylistPage = null;
  101.     }
  102.   
  103.     // Search the array for the matching page
  104.     for (var i in this._pageInfoArray) {
  105.       if (aPageInfo.contentUrl == this._pageInfoArray[i].contentUrl) {
  106.         // found, remove it and stop
  107.         this._pageInfoArray.splice(i, 1);
  108.         return;
  109.       }
  110.     }
  111.     // Page not found, throw!
  112.     throw new Error("Page " + aPageInfo.contentTitle + " not found in unregisterPage");
  113.   },
  114.  
  115.   getAvailablePages: function(aList, aConstraint) {
  116.     this._ensureMediaPageRegistration();
  117.     // If no list is provided, return the entire set
  118.     if (!aList) {
  119.       return ArrayConverter.enumerator(this._pageInfoArray); 
  120.     }
  121.     // Otherwise, make a list of what matches the list
  122.     var tempArray = [];
  123.     for (var i in this._pageInfoArray) {
  124.       var pageInfo = this._pageInfoArray[i];
  125.       if (pageInfo.matchInterface.match(aList, aConstraint)) {
  126.         tempArray.push(pageInfo);
  127.       }
  128.     }
  129.  
  130.     // ... and return that.
  131.     return ArrayConverter.enumerator(tempArray); 
  132.   },
  133.  
  134.   getPage: function(aList, aConstraint, aType) {
  135.     this._ensureMediaPageRegistration();
  136.  
  137.     // use the outermost list
  138.     aList = this._getOutermostList(aList);
  139.  
  140.     // Read the saved state
  141.     var remote = Cc["@songbirdnest.com/Songbird/DataRemote;1"]
  142.                    .createInstance(Ci.sbIDataRemote);
  143.     var baseKey = "mediapages." + aList.guid;
  144.     var key = baseKey;
  145.     if (aType)
  146.       key = baseKey + "." + aType;
  147.     remote.init(key, null);
  148.     var savedPageURL = remote.stringValue;
  149.     if (savedPageURL && savedPageURL != "") {
  150.       // Check that the saved url is still registered 
  151.       // and still supports this list
  152.       let pageInfo = this._checkPageForList(aList, aConstraint, savedPageURL);
  153.       if (pageInfo) return pageInfo;
  154.     }
  155.     // fall back to prefs with no type
  156.     else if (aType) {
  157.       let remote = Cc["@songbirdnest.com/Songbird/DataRemote;1"]
  158.                      .createInstance(Ci.sbIDataRemote);
  159.       remote.init(baseKey, null);
  160.       let savedOldPageURL = remote.stringValue;
  161.       if (savedOldPageURL && savedOldPageURL != "") {
  162.         // Check that the saved url is still registered 
  163.         // and still supports this list
  164.         let oldPageInfo = this._checkPageForList(aList,
  165.                                                  aConstraint,
  166.                                                  savedOldPageURL);
  167.         if (oldPageInfo) return oldPageInfo;
  168.       }
  169.     }
  170.  
  171.     // Read the list's default
  172.     var defaultPageURL = aList.getProperty(SBProperties.defaultMediaPageURL);
  173.     if (defaultPageURL && defaultPageURL != "") {
  174.       // Check that the saved url is still registered 
  175.       // and still supports this list
  176.       var pageInfo = this._checkPageForList(aList, aConstraint, defaultPageURL);
  177.       if (pageInfo) return pageInfo;
  178.     }
  179.  
  180.     // No saved state and no default, this is either the first time this list
  181.     // is shown, or its previous saved/default page isn't valid anymore, so
  182.     // pick a new one
  183.  
  184.     // Hardcoded first run logic:
  185.     // Everybody gets the listview (playlistPage)
  186.     if (this._defaultPlaylistPage)  {
  187.       return this._defaultPlaylistPage;
  188.     } else {
  189.       // No hardcoded defaults.  Look for anything
  190.       // that matches.
  191.       for (var i in this._pageInfoArray) {
  192.         var pageInfo = this._pageInfoArray[i];
  193.         if (pageInfo.matchInterface.match(aList, aConstraint)) {
  194.           return pageInfo;
  195.         }
  196.       }
  197.     }
  198.  
  199.     // Oh crap.
  200.     throw new Error("MediaPageManager unable to determine a page for " + aList.guid);
  201.  
  202.     // keep js happy ?
  203.     return null;
  204.   },
  205.  
  206.   setPage: function(aList, aPageInfo, aType) {
  207.     // use the outermost list
  208.     aList = this._getOutermostList(aList);
  209.  
  210.     // Save the state
  211.     var remote = Cc["@songbirdnest.com/Songbird/DataRemote;1"]
  212.                  .createInstance(Ci.sbIDataRemote);
  213.     var key = "mediapages." + aList.guid;
  214.     if (aType)
  215.       key = key + "." + aType;
  216.     remote.init(key, null);
  217.     remote.stringValue = aPageInfo.contentUrl;
  218.   },
  219.  
  220.   // internal, get the outermost list for a given list. for instance, when
  221.   // given a smart medialist's storage list, this returns the original
  222.   // smart medialist.
  223.   _getOutermostList: function(aList) {
  224.     var outerGuid = aList.getProperty(SBProperties.outerGUID);
  225.     if (outerGuid)
  226.       aList = aList.library.getMediaItem(outerGuid);
  227.     return aList;
  228.   },
  229.  
  230.   // internal. checks that a url is registered in the list of pages, and that 
  231.   // its matching test succeeds for a given list
  232.   _checkPageForList: function(aList, aConstraint, aUrl) {
  233.     for (var i in this._pageInfoArray) {
  234.       var pageInfo = this._pageInfoArray[i];
  235.       if (pageInfo.contentUrl != aUrl) continue;
  236.       if (!pageInfo.matchInterface.match(aList, aConstraint)) continue;
  237.       return pageInfo;
  238.     }
  239.     return null;
  240.   },
  241.  
  242.   _ensureMediaPageRegistration: function() {
  243.     if(this._registrationComplete) { return };
  244.     
  245.     this._registerDefaults();
  246.     MediaPageMetadataReader.loadMetadata(this);
  247.     
  248.     this._registrationComplete = true;
  249.   },
  250.   
  251.   _registerDefaults: function() {
  252.     var playlistString = "mediapages.playlistpage";
  253.     var filteredPlaylistString = "mediapages.filteredplaylistpage";
  254.     try {
  255.       var stringBundleService = Cc["@mozilla.org/intl/stringbundle;1"]
  256.                                   .getService(Ci.nsIStringBundleService);
  257.       var stringBundle = stringBundleService.createBundle(
  258.            "chrome://songbird/locale/songbird.properties" );
  259.       playlistString = stringBundle.GetStringFromName(playlistString);
  260.       filteredPlaylistString = stringBundle.GetStringFromName(
  261.                   filteredPlaylistString);
  262.       stringBundleService = null;
  263.       stringBundle = null;
  264.     } catch (e) {
  265.       Component.utils.reportError("MediaPageManager: Couldn't localize default media page name.\n")
  266.     }
  267.  
  268.     // the default page matches everything    
  269.     var matchAll = {match: function() true};
  270.  
  271.     // Register the playlist with filters
  272.     this._defaultFilteredPlaylistPage =
  273.         this.registerPage( filteredPlaylistString,
  274.         "chrome://songbird/content/mediapages/filtersPage.xul",
  275.         null,
  276.         matchAll);
  277.  
  278.     // And the playlist without filters
  279.     this._defaultPlaylistPage = 
  280.         this.registerPage( playlistString,
  281.         "chrome://songbird/content/mediapages/playlistPage.xul",
  282.         null,
  283.         matchAll);
  284.   },
  285.   
  286. }; // MediaPageManager.prototype
  287.  
  288.  
  289.  
  290. /**
  291.  * MediaPageMetadataReader
  292.  * Reads the Add-on Metadata RDF datasource for Media Page declarations.
  293.  */
  294. var MediaPageMetadataReader = {
  295.   loadMetadata: function(manager) {
  296.     this._manager = manager;
  297.     
  298.     var addons = RDFHelper.help(
  299.       "rdf:addon-metadata",
  300.       "urn:songbird:addon:root",
  301.       RDFHelper.DEFAULT_RDF_NAMESPACES
  302.     );
  303.     
  304.     for (var i = 0; i < addons.length; i++) {
  305.       // skip addons with no panes.
  306.       if (!addons[i].mediaPage) 
  307.         continue;
  308.       try {
  309.         var pages = addons[i].mediaPage;
  310.         for (var j = 0; j < pages.length; j++) {
  311.           this._registerMediaPage(addons[i], pages[j]) 
  312.         }
  313.       } catch (e) {
  314.         this._reportErrors("", [  "An error occurred while processing " +
  315.                   "extension " + addons[i].Value + ".  Exception: " + e  ]);
  316.       }
  317.     }
  318.   },
  319.   
  320.   /**
  321.    * Extract pane metadata and register it with the manager.
  322.    */
  323.   _registerMediaPage: function _registerMediaPage(addon, page) {
  324.     // create and validate our page info
  325.     var errorList = [];
  326.     var warningList = [];
  327.     
  328.     var info = {};
  329.     for (property in page) {
  330.       if (page[property])
  331.        info[property] = page[property][0];
  332.     }
  333.     
  334.     this._validateProperties(info, errorList, warningList);
  335.     
  336.     // create a match function
  337.     var matchFunction;
  338.     if (page.match) {
  339.       var matchList = this._createMatchList(page, warningList);
  340.       matchFunction = this._createMatchFunction(matchList);
  341.     }
  342.     else {
  343.       matchFunction = this._createMatchAllFunction();
  344.     }
  345.     
  346.     // If errors were encountered, then do not submit
  347.     if (warningList.length > 0){
  348.       this._reportErrors(
  349.           "Warning: " + addon.Value + " install.rdf loading media page: " , warningList);
  350.     } 
  351.     if (errorList.length > 0) {
  352.       this._reportErrors(
  353.           "ERROR: " + addon.Value + " install.rdf IGNORED media page: ", errorList);
  354.       return;
  355.     }
  356.     
  357.     // Submit description
  358.     this._manager.registerPage( info.contentTitle,
  359.                                 info.contentUrl,
  360.                                 info.contentIcon,
  361.                                 {match: matchFunction}
  362.                                );
  363.     
  364.     //dump("MediaPageMetadataReader: registered pane " + info.contentTitle
  365.     //     + " at " + info.contentUrl + " from addon " + addon.Value + " \n");
  366.   },
  367.   
  368.   _validateProperties: function(info, errorList, warningList) {
  369.     var requiredProperties = ["contentTitle", "contentUrl"];
  370.     var optionalProperties = ["contentIcon", "match"];
  371.     
  372.     // check for required properties
  373.     for (var p in requiredProperties) { 
  374.       if (!info[requiredProperties[p]]) {
  375.         errorList.push("Missing required property " + requiredProperties[p] + ".\n")
  376.       }
  377.     }
  378.     
  379.     // check for unused RDF nodes and warn about them.
  380.     var template = {};
  381.     for(var i in requiredProperties) {
  382.       template[requiredProperties[i]] = "required";
  383.     }
  384.     for(var i in optionalProperties) {
  385.       template[optionalProperties[i]] = "optional";
  386.     }
  387.     
  388.     for (var p in info) {
  389.       if (!template[p]) {
  390.         warningList.push("Unrecognized property " + p + ".\n")
  391.       }
  392.     }
  393.   },
  394.   
  395.   _createMatchList: function(page, warningList) {
  396.     // create a set of property-comparison objects
  397.     // one for each <match>. a mediapage will work for medialists which match
  398.     // all the properties in a hash.
  399.     var matchList = [];
  400.     
  401.     for (var i = 0; i < page.match.length; i++) {
  402.       var fields = page.match[i].split(/\s+/);
  403.       var properties = {}
  404.       for(var f in fields) {
  405.         var key; var value;
  406.         [key, value] = fields[f].split(":");
  407.         if(!value) { value = key; key = "type" };
  408.         
  409.         // TODO: quoted string values ala name:"My Funky List"
  410.         // TODO: check if the key is actually a legal key-type and warn if not
  411.         
  412.         if(properties[key]) {
  413.            warningList.push("Attempting to match two values for "
  414.                             +key+": "+properties[key]+" and "+value+".");
  415.         }
  416.         
  417.         properties[key] = value;
  418.       }
  419.       
  420.       matchList.push(properties); 
  421.     }
  422.     
  423.     return matchList;
  424.   },
  425.   
  426.   _createMatchFunction: function(matchList) {
  427.     // create a match function that uses the match options
  428.     var matchFunction = function(mediaList, aConstraint) {
  429.       // just in case someone's passing in bad values
  430.       if(!mediaList) {
  431.         return false;
  432.       }
  433.  
  434.       // check each <match/>'s values
  435.       // if any one set works, this is a good media page for the list
  436.       for (var m in matchList) {
  437.         let match = matchList[m];
  438.  
  439.         // first, see if we just want to opt out of this match
  440.         // our definition of an opt-out-able list is:
  441.         // one that is so unspecific as to only target by "type"
  442.         // or to target everything. (see below)
  443.         // (this is a bit natty)
  444.         var numProperties = 0;
  445.         for (var i in match) {
  446.           numProperties++
  447.         }
  448.         // if we *only* target the "type" of the list
  449.         // and the list wants to opt out
  450.         if (numProperties == 1 && match["type"]) {
  451.           if (mediaList.getProperty(SBProperties.onlyCustomMediaPages) == "1") {
  452.             return false;
  453.           }
  454.         }
  455.  
  456.         var thisListMatches = true;
  457.         for (var i in match) {
  458.           switch(i) {
  459.             case "contentType":
  460.               // this is set on the constraint instead
  461.               if (aConstraint) {
  462.                 for (let group in ArrayConverter.JSEnum(aConstraint.groups)) {
  463.                   if (!(group instanceof Ci.sbILibraryConstraintGroup)) {
  464.                     continue;
  465.                   }
  466.                   if (!group.hasProperty(SBProperties.contentType)) {
  467.                     continue;
  468.                   }
  469.                   let contentTypes =
  470.                     ArrayConverter.JSArray(group.getValues(SBProperties.contentType));
  471.                   if (contentTypes.indexOf(match[i]) == -1) {
  472.                     thisListMatches = false;
  473.                     break;
  474.                   }
  475.                 }
  476.               }
  477.               break;
  478.             default:
  479.               if (i in mediaList) {
  480.                 thisListMatches &= (match[i] == mediaList[i]);
  481.               }
  482.               else {
  483.                 // Use getProperty notation if the desired field is not
  484.                 // specified as a true JS property on the object.
  485.                 // TODO: This should be improved.
  486.                 thisListMatches &= (match[i] == mediaList.getProperty(SBProperties[i]));
  487.               }
  488.           }
  489.           if (!thisListMatches) {
  490.             break;
  491.           }
  492.         }
  493.         if (thisListMatches) {
  494.           return true;
  495.         }
  496.       }
  497.  
  498.       // arriving here means none of the <match>
  499.       // elements fit the medialist passed in
  500.       return false;
  501.     }
  502.  
  503.     return matchFunction;
  504.   },
  505.   
  506.   _createMatchAllFunction: function() {
  507.     var matchFunction = function(mediaList) {
  508.       // opt-out lists will also exclude completely generic pages
  509.       // this detail must be clearly communicated to the MP dev'rs!
  510.       return(mediaList && mediaList.getProperty(SBProperties.onlyCustomMediaPages) != "1");
  511.     };
  512.     return matchFunction;
  513.   },
  514.   
  515.   /**
  516.    * \brief Dump a list of errors to the console and jsconsole
  517.    *
  518.    * \param contextMessage Additional prefix to use before every line
  519.    * \param errorList Array of error messages
  520.    */
  521.   _reportErrors: function _reportErrors(contextMessage, errorList) {
  522.     var consoleService = Cc["@mozilla.org/consoleservice;1"]
  523.                            .getService(Ci.nsIConsoleService);
  524.     for (var i = 0; i  < errorList.length; i++) {
  525.       Cu.reportError("MediaPage Addon Metadata: " + contextMessage + errorList[i]);
  526.     }
  527.   }
  528. }
  529.  
  530. function NSGetModule(compMgr, fileSpec) {
  531.   return XPCOMUtils.generateModule([MediaPageManager]);
  532. }
  533.  
  534.