home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / components / sbLibraryUtils.jsm < prev    next >
Text File  |  2012-06-08  |  28KB  |  909 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://app/jsmodules/sbProperties.jsm");
  27. Components.utils.import("resource://app/jsmodules/sbColumnSpecParser.jsm");
  28.  
  29. EXPORTED_SYMBOLS = ["LibraryUtils"];
  30.  
  31. const Cc = Components.classes;
  32. const Ci = Components.interfaces;
  33. const Cr = Components.results;
  34.  
  35. /**
  36.  * \class LibraryUtils
  37.  * \brief Javascript wrappers for common library tasks
  38.  */
  39. var LibraryUtils = {
  40.  
  41.   get manager() {
  42.     var manager = Cc["@songbirdnest.com/Songbird/library/Manager;1"].
  43.                   getService(Ci.sbILibraryManager);
  44.     if (manager) {
  45.       // Succeeded in getting the library manager, don't do this again.
  46.       this.__defineGetter__("manager", function() {
  47.         return manager;
  48.       });
  49.     }
  50.     return manager;
  51.   },
  52.  
  53.   get mainLibrary() {
  54.     return this.manager.mainLibrary;
  55.   },
  56.  
  57.   get webLibrary() {
  58.     var webLibraryGUID = Cc["@mozilla.org/preferences-service;1"].
  59.                          getService(Ci.nsIPrefBranch).
  60.                          getCharPref("songbird.library.web");
  61.     var webLibrary = this.manager.getLibrary(webLibraryGUID);
  62.     delete webLibraryGUID;
  63.  
  64.     if (webLibrary) {
  65.       // Succeeded in getting the web library, don't ever do this again.
  66.       this.__defineGetter__("webLibrary", function() {
  67.         return webLibrary;
  68.       });
  69.     }
  70.  
  71.     return webLibrary;
  72.   },
  73.  
  74.   getMediaListByGUID: function(aLibraryGUID, aMediaListGUID) {
  75.     var mediaList = (aLibraryGUID instanceof Ci.sbILibrary) ?
  76.                     aLibraryGUID :
  77.                     this.manager.getLibrary( aLibraryGUID );
  78.  
  79.     // Are we loading the root library or a media list within it?
  80.     if (aMediaListGUID && aLibraryGUID != aMediaListGUID) {
  81.       mediaList = mediaList.getMediaItem( aMediaListGUID );
  82.     }
  83.     return mediaList;
  84.   },
  85.  
  86.   _standardFilterConstraint: null,
  87.   get standardFilterConstraint() {
  88.     if (!this._standardFilterConstraint) {
  89.       this._standardFilterConstraint = this.createConstraint([
  90.         [
  91.           [SBProperties.isList, ["0"]]
  92.         ],
  93.         [
  94.           [SBProperties.hidden, ["0"]]
  95.         ]
  96.       ]);
  97.     }
  98.     return this._standardFilterConstraint;
  99.   },
  100.  
  101.   createConstraint: function(aObject) {
  102.     var builder = Cc["@songbirdnest.com/Songbird/Library/ConstraintBuilder;1"]
  103.                     .createInstance(Ci.sbILibraryConstraintBuilder);
  104.     aObject.forEach(function(aGroup, aIndex) {
  105.       aGroup.forEach(function(aPair) {
  106.         var property = aPair[0];
  107.         var values = aPair[1];
  108.         var enumerator = {
  109.           a: values,
  110.           i: 0,
  111.           hasMore: function() {
  112.             return this.i < this.a.length;
  113.           },
  114.           getNext: function() {
  115.             return this.a[this.i++];
  116.           }
  117.         };
  118.         builder.includeList(property, enumerator);
  119.       });
  120.       if (aIndex < aObject.length - 1) {
  121.         builder.intersect();
  122.       }
  123.     });
  124.     return builder.get();
  125.   },
  126.  
  127.   createStandardSearchConstraint: function(aSearchString) {
  128.     if (aSearchString == "" || !aSearchString)
  129.       return null;
  130.     var builder = Cc["@songbirdnest.com/Songbird/Library/ConstraintBuilder;1"]
  131.                     .createInstance(Ci.sbILibraryConstraintBuilder);
  132.     var a = aSearchString.split(" ");
  133.     var first = true;
  134.     for (var i = 0; i < a.length; i++) {
  135.       if (a[i] && a[i] != "") {
  136.         if (!first) {
  137.           builder.intersect();
  138.         }
  139.         builder.include(SBProperties.artistName, a[i]);
  140.         builder.include(SBProperties.albumName, a[i]);
  141.         builder.include(SBProperties.trackName, a[i]);
  142.         first = false;
  143.       }
  144.     }
  145.     return builder.get();
  146.   },
  147.  
  148.   /**
  149.    * \brief Create a new view for the param medialist.  This is most often the
  150.    *        function you want for making views.  mediaList.createView() can be
  151.    *        used directly, but then the mediaListView.filterConstraint should be
  152.    *        carefully considered and likely set manually.
  153.    *
  154.    * \param aMediaList - The medialist for which the view should be created
  155.    * \param aSearchString - OPTIONAL - If present this string contains the
  156.    *                        values used to constrain the filter. aSearchString
  157.    *                        will be split, space-delimited, into an array
  158.    *                        of values passed to the new view's filter.
  159.    *                        A text search filter will only be constrained by
  160.    *                        the first item in this array, however.
  161.    *
  162.    * \return - A medialist view for aMediaList with any special sorting the
  163.    *           list may have and the standard filter constraints, which can be
  164.    *           further refined by aSearchString.
  165.    */
  166.   createStandardMediaListView: function(aMediaList, aSearchString) {
  167.     var mediaList = aMediaList.QueryInterface(Ci.sbIMediaList);
  168.     var mediaListView = mediaList.createView();
  169.  
  170.     // Get the sort for this list by parsing the list's column spec.  Then hit
  171.     // the property manager to see if there is a special sort profile for this
  172.     // ID
  173.     var parser = new ColumnSpecParser(aMediaList, null);
  174.     if (parser.sortID) {
  175.       var propertyArray =
  176.         Cc["@songbirdnest.com/Songbird/Properties/MutablePropertyArray;1"].
  177.         createInstance(Ci.sbIMutablePropertyArray);
  178.       propertyArray.strict = false;
  179.       propertyArray.appendProperty(parser.sortID,
  180.           (parser.sortIsAscending ? "a" : "d"));
  181.       mediaListView.setSort(propertyArray);
  182.     }
  183.  
  184.     // By default, we never want to show lists and hidden
  185.     // things in the playlist
  186.     mediaListView.filterConstraint = LibraryUtils.standardFilterConstraint;
  187.  
  188.     // Set up a standard search filter.
  189.     // It can always be replaced later.
  190.     var filter = mediaListView.cascadeFilterSet;
  191.     filter.appendSearch(["*"], 1);
  192.  
  193.     if (aSearchString) {
  194.       // Set the search
  195.       var searchArray = aSearchString.split(" ");
  196.       filter.set(0, searchArray, searchArray.length);
  197.     } else {
  198.       // Or not.
  199.       filter.set(0, [], 0);
  200.     }
  201.  
  202.     return mediaListView;
  203.   },
  204.  
  205.   createConstrainedMediaListView: function(aMediaList, aConstraint,
  206.                                            aSearchString) {
  207.     var mediaListView = this.createStandardMediaListView(aMediaList,
  208.                                                          aSearchString);
  209.  
  210.     // Take the existing filter constraint and intersect it with the
  211.     // additional constraint we're about to apply
  212.     var constraintBuilder =
  213.           Cc["@songbirdnest.com/Songbird/Library/ConstraintBuilder;1"]
  214.             .createInstance(Ci.sbILibraryConstraintBuilder);
  215.     constraintBuilder.includeConstraint(mediaListView.filterConstraint);
  216.     constraintBuilder.intersect();
  217.     constraintBuilder.include(aConstraint[0], aConstraint[1]);
  218.     mediaListView.filterConstraint = constraintBuilder.get();
  219.  
  220.     return mediaListView;
  221.   },
  222.  
  223.   /**
  224.    * \brief Determine if a url is a media tab url.
  225.    * \param aURL url to check.
  226.    * \param aBrowser an sbtabbrowser object (optional, for mediaTab test).
  227.    * \return true if aURL is a media tab url, false if not.
  228.    */
  229.   isMediaTabURL: function(aURL, aBrowser) {
  230.     if (!aURL) {
  231.       return Components.results.NS_ERROR_INVALID_ARG;
  232.     }
  233.     if (aBrowser && !aBrowser.mediaTab) {
  234.       // no media tab, can't be a media tab url
  235.       return false;
  236.     }
  237.     var url = aURL;
  238.     if (aURL instanceof Components.interfaces.nsIURI) {
  239.       url = aURL.spec;
  240.     }
  241.     if (!(/chrome:\/\//.test(url))) {
  242.       // not chrome
  243.       return false;
  244.     }
  245.  
  246.     const PREF_FIRSTRUN_URL = "songbird.url.firstrunpage";
  247.     const Application = Cc["@mozilla.org/fuel/application;1"].getService();
  248.     var firstRunURL = Application.prefs.getValue(PREF_FIRSTRUN_URL, null);
  249.     if (url == firstRunURL) {
  250.       // first run url, sure this can be a media tab
  251.       return true;
  252.     }
  253.     var service =
  254.       Components.classes['@songbirdnest.com/servicepane/service;1']
  255.       .getService(Components.interfaces.sbIServicePaneService);
  256.     var node = service.getNodeForURL(url,
  257.                                      Ci.sbIServicePaneService.URL_MATCH_PREFIX);
  258.     if (!node) return false;
  259.     return true;
  260.   },
  261.  
  262.   /**
  263.    * \brief Find an item in the main library whose guid matches the
  264.    *        originItemGuid of the param aMediaItem and return it, or null if
  265.    *        not found.
  266.    * \param aMediaItem Retrieve an item from the mainLibrary whose guid
  267.    *                   matches the originItemGuid of this mediaitem
  268.    * \return The mediaitem from the mainLibrary whose guid matches the
  269.    *         originItemGuid of aMediaItem.  null if none was found.
  270.    */
  271.   getMainLibraryOriginItem: function(aMediaItem) {
  272.     var originGUID = aMediaItem.getProperty(SBProperties.originItemGuid);
  273.     if (!originGUID) {
  274.       return null;
  275.     }
  276.     try {
  277.       var foundItem = this.mainLibrary.getMediaItem(originGUID);
  278.       return foundItem;
  279.     }
  280.     catch (e) {
  281.       // item couldn't be found, return null
  282.       return null;
  283.     }
  284.   }
  285.  
  286. }
  287.  
  288.  
  289.  
  290. /**
  291.  * \class LibraryUtils.BatchHelper
  292.  * \brief Helper object for monitoring the state of
  293.  *        batch library operations.
  294.  */
  295. LibraryUtils.BatchHelper = function() {
  296.   this._depth = 0;
  297. }
  298.  
  299. LibraryUtils.BatchHelper.prototype.begin =
  300. function BatchHelper_begin()
  301. {
  302.   this._depth++;
  303. }
  304.  
  305. LibraryUtils.BatchHelper.prototype.end =
  306. function BatchHelper_end()
  307. {
  308.   this._depth--;
  309.   if (this._depth < 0) {
  310.     throw new Error("Invalid batch depth!");
  311.   }
  312. }
  313.  
  314. LibraryUtils.BatchHelper.prototype.depth =
  315. function BatchHelper_depth()
  316. {
  317.   return this._depth;
  318. }
  319.  
  320. LibraryUtils.BatchHelper.prototype.isActive =
  321. function BatchHelper_isActive()
  322. {
  323.   return this._depth > 0;
  324. }
  325.  
  326.  
  327.  
  328. /**
  329.  * \class LibraryUtils.MultiBatchHelper
  330.  * \brief Helper object for monitoring the state of
  331.  *        batch operations in multiple libraries
  332.  */
  333. LibraryUtils.MultiBatchHelper = function() {
  334.   this._libraries = {};
  335. }
  336.  
  337. LibraryUtils.MultiBatchHelper.prototype.get =
  338. function MultiBatchHelper_get(aLibrary)
  339. {
  340.   var batch = this._libraries[aLibrary.guid];
  341.   if (!batch) {
  342.     batch = new LibraryUtils.BatchHelper();
  343.     this._libraries[aLibrary.guid] = batch;
  344.   }
  345.   return batch;
  346. }
  347.  
  348. LibraryUtils.MultiBatchHelper.prototype.begin =
  349. function MultiBatchHelper_begin(aLibrary)
  350. {
  351.   var batch = this.get(aLibrary);
  352.   batch.begin();
  353. }
  354.  
  355. LibraryUtils.MultiBatchHelper.prototype.end =
  356. function MultiBatchHelper_end(aLibrary)
  357. {
  358.   var batch = this.get(aLibrary);
  359.   batch.end();
  360. }
  361.  
  362. LibraryUtils.MultiBatchHelper.prototype.depth =
  363. function MultiBatchHelper_depth(aLibrary)
  364. {
  365.   var batch = this.get(aLibrary);
  366.   return batch.depth();
  367. }
  368.  
  369. LibraryUtils.MultiBatchHelper.prototype.isActive =
  370. function MultiBatchHelper_isActive(aLibrary)
  371. {
  372.   var batch = this.get(aLibrary);
  373.   return batch.isActive();
  374. }
  375.  
  376.  
  377.  
  378.  
  379. /**
  380.  * \class LibraryUtils.RemovalMonitor
  381.  * \brief Helps track removal/deletion of medialists.
  382.  * \param aCallback An object with an onMediaListRemoved function.
  383.  */
  384. LibraryUtils.RemovalMonitor = function(aCallback) {
  385.   //dump("RemovalMonitor: RemovalMonitor()\n");
  386.  
  387.   if (!aCallback || !aCallback.onMediaListRemoved) {
  388.     throw new Error("RemovalMonitor() requires a callback object");
  389.   }
  390.  
  391.   this._callback = aCallback;
  392. }
  393.  
  394. LibraryUtils.RemovalMonitor.prototype = {
  395.  
  396.   // An object with an onMediaListRemoved function
  397.   _callback: null,
  398.  
  399.   // MediaList GUID to monitor for removal
  400.   _targetGUID: null,
  401.  
  402.   // Library that owns the target MediaList
  403.   _library: null,
  404.  
  405.   _libraryManager: null,
  406.   _batchHelper: null,
  407.  
  408.   // Flag to indicate that the target item
  409.   // was deleted in a batch operation
  410.   _removedInBatch: false,
  411.  
  412.  
  413.   /**
  414.    * Watch for removal of the given sbIMediaList.
  415.    * Pass null to stop listening.
  416.    */
  417.   setMediaList:  function RemovalMonitor_setMediaList(aMediaList) {
  418.     //dump("RemovalMonitor: RemovalMonitor.setMediaList()\n");
  419.     this._removedInBatch = false;
  420.  
  421.     // If passed a medialist, hook up listeners
  422.     if (aMediaList instanceof Ci.sbIMediaList) {
  423.  
  424.       // Listen to the library if we aren't doing so already
  425.       if (aMediaList.library != this._library) {
  426.         if (this._library && this._library.guid != this._targetGUID) {
  427.           this._library.removeListener(this);
  428.         }
  429.  
  430.         this._library = aMediaList.library
  431.  
  432.         // If this is a list within a library, then
  433.         // we need to listen for clear/remove in the
  434.         // library
  435.         if (!(aMediaList instanceof Ci.sbILibrary)) {
  436.           this._batchHelper = new LibraryUtils.BatchHelper();
  437.  
  438.           var flags = Ci.sbIMediaList.LISTENER_FLAGS_BATCHBEGIN |
  439.                       Ci.sbIMediaList.LISTENER_FLAGS_BATCHEND |
  440.                       Ci.sbIMediaList.LISTENER_FLAGS_BEFOREITEMREMOVED |
  441.                       Ci.sbIMediaList.LISTENER_FLAGS_AFTERITEMREMOVED |
  442.                       Ci.sbIMediaList.LISTENER_FLAGS_LISTCLEARED;
  443.  
  444.           this._library.addListener(this, false, flags, null);
  445.         }
  446.       }
  447.  
  448.       if (!this._libraryManager) {
  449.         this._libraryManager = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  450.                                 .getService(Ci.sbILibraryManager);
  451.         this._libraryManager.addListener(this);
  452.       }
  453.  
  454.       // Remember which medialist we are supposed to watch
  455.       this._targetGUID = aMediaList.guid;
  456.  
  457.  
  458.     // If set to null, shut down any listeners
  459.     } else {
  460.       if (this._libraryManager) {
  461.         this._libraryManager.removeListener(this);
  462.         this._libraryManager = null;
  463.       }
  464.  
  465.       if (this._library) {
  466.         this._library.removeListener(this);
  467.         this._library = null;
  468.       }
  469.       this._batchHelper = null;
  470.       this._targetGUID = null;
  471.     }
  472.   },
  473.  
  474.  
  475.   /**
  476.    * Notifies the listener that the list is about to be removed
  477.    */
  478.   _onBeforeMediaListRemoved: function RemovalMonitor_onBeforeMediaListRemoved()
  479.   {
  480.     // Notify
  481.     this._callback.onBeforeMediaListRemoved();
  482.   },
  483.  
  484.  
  485.   /**
  486.    * Notifies the listener that the list has been removed,
  487.    * and then stops monitoring
  488.    */
  489.   _onMediaListRemoved: function RemovalMonitor_onMediaListRemoved() {
  490.     //dump("RemovalMonitor: RemovalMonitor.onMediaListRemoved()\n");
  491.  
  492.     // Our list has been removed. Stop tracking.
  493.     this.setMediaList(null);
  494.  
  495.     // Notify
  496.     this._callback.onMediaListRemoved();
  497.   },
  498.  
  499.  
  500.   /**
  501.    * \sa sbIMediaListListener
  502.    */
  503.   onItemAdded: function(aMediaList, aMediaItem, aIndex) { return true; },
  504.   onItemUpdated: function(aMediaList, aMediaItem, aProperties) { return true },
  505.   onItemMoved: function(aMediaList, aFromIndex, aToIndex) { return true },
  506.   onBeforeItemRemoved: function (aMediaList, aMediaItem, aIndex) {
  507.     // Do no more if in a batch
  508.     if (this._batchHelper.isActive()) {
  509.       if (aMediaItem.guid == this._targetGUID) {
  510.         this._removedInBatch = true;
  511.       }
  512.       return true;
  513.     }
  514.  
  515.     // If our list is about to be removed, notify
  516.     if (aMediaItem.guid == this._targetGUID) {
  517.       this._onBeforeMediaListRemoved();
  518.     }
  519.  
  520.     return false;
  521.   },
  522.   onAfterItemRemoved: function RemovalMonitor_onAfterItemRemoved(aMediaList,
  523.                                                                  aMediaItem,
  524.                                                                  aIndex)
  525.   {
  526.     //dump("RemovalMonitor: RemovalMonitor.onAfterItemRemoved()\n");
  527.  
  528.     // Do no more if in a batch
  529.     if (this._batchHelper.isActive()) {
  530.       if (aMediaItem.guid == this._targetGUID) {
  531.         this._removedInBatch = true;
  532.       }
  533.       return true;
  534.     }
  535.  
  536.     // If our list was removed, notify
  537.     if (aMediaItem.guid == this._targetGUID) {
  538.       this._onMediaListRemoved();
  539.     }
  540.  
  541.     return false;
  542.   },
  543.   onBeforeListCleared: function RemovalMonitor_onBeforeListCleared
  544.                                   (aMediaList,
  545.                                    aExcludeLists)
  546.   {
  547.     //dump("RemovalMonitor: RemovalMonitor.onBeforeListCleared()\n");
  548.  
  549.     return true;
  550.   },
  551.   onListCleared: function RemovalMonitor_onListCleared(aMediaList,
  552.                                                        aExcludeLists)
  553.   {
  554.     //dump("RemovalMonitor: RemovalMonitor.onListCleared()\n");
  555.  
  556.     // Do no more if in a batch
  557.     if (this._batchHelper.isActive()) {
  558.       this._removedInBatch = true;
  559.       return true;
  560.     }
  561.  
  562.     // The current media list must have been removed, so notify
  563.     this._onMediaListRemoved();
  564.  
  565.     return false;
  566.   },
  567.  
  568.   onBatchBegin: function RemovalMonitor_onBatchBegin(aMediaList)
  569.   {
  570.     this._batchHelper.begin();
  571.   },
  572.   onBatchEnd: function RemovalMonitor_onBatchEnd(aMediaList)
  573.   {
  574.     //dump("RemovalMonitor: RemovalMonitor.onBatchEnd()\n");
  575.  
  576.     this._batchHelper.end();
  577.     // If the batch is still in progress do nothing
  578.     if (this._batchHelper.isActive()) {
  579.       return;
  580.     }
  581.  
  582.     var removed = false;
  583.  
  584.     // If we know our target was removed during the batch, notify
  585.     if (this._removedInBatch) {
  586.       removed = true;
  587.  
  588.     // If we don't know for sure, we need to check
  589.     } else if (this._targetGUID != this._library.guid) {
  590.  
  591.       // Check if our media list was removed
  592.       try {
  593.         this._library.getMediaItem(this._targetGUID);
  594.       } catch (e) {
  595.         removed = true;
  596.       }
  597.     }
  598.  
  599.     this._removedInBatch = false;
  600.  
  601.     if (removed) {
  602.       this._onMediaListRemoved();
  603.     }
  604.   },
  605.  
  606.   /**
  607.    * \sa sbILibraryManagerListener
  608.    */
  609.   onLibraryRegistered: function(aLibrary) {},
  610.   onLibraryUnregistered: function RemovalMonitor_onLibraryUnregistered(aLibrary)
  611.   {
  612.     //dump("RemovalMonitor: RemovalMonitor.onLibraryUnregistered()\n");
  613.     // If the current library was unregistered, notify
  614.     if (this._library && this._library.equals(aLibrary)) {
  615.       this._onMediaListRemoved();
  616.     }
  617.   },
  618.  
  619.  
  620.   /**
  621.    * \sa nsISupports
  622.    */
  623.   QueryInterface: function RemovalMonitor_QueryInterface(aIID) {
  624.     if (!aIID.equals(Components.interfaces.nsISupports) &&
  625.         !aIID.equals(Components.interfaces.sbILibraryManagerListener) &&
  626.         !aIID.equals(Components.interfaces.sbIMediaListListener)) {
  627.       throw Components.results.NS_ERROR_NO_INTERFACE;
  628.     }
  629.     return this;
  630.   }
  631. }
  632.  
  633. /**
  634.  * \class LibraryUtils.MediaListEnumeratorToArray
  635.  * \brief Enumerates items in a media list and stores them in an array.
  636.  */
  637. LibraryUtils.MediaListEnumeratorToArray = function() {
  638. }
  639.  
  640. LibraryUtils.MediaListEnumeratorToArray.prototype = {
  641.   //
  642.   // Media list enumeration array listener fields.
  643.   //
  644.   //   array                    Enumeration array.
  645.   //
  646.  
  647.   array: null,
  648.  
  649.  
  650.   /**
  651.    * \brief Called when enumeration is about to begin.
  652.    *
  653.    * \param aMediaList - The media list that is being enumerated.
  654.    *
  655.    * \return true to begin enumeration, false to cancel.
  656.    */
  657.  
  658.   onEnumerationBegin: function DSW_MLEAL_onEnumerationBegin(aMediaList) {
  659.     // Initialize the enumeration array.
  660.     this.array = [];
  661.     return Ci.sbIMediaListEnumerationListener.CONTINUE;
  662.   },
  663.  
  664.  
  665.   /**
  666.    * \brief Called once for each item in the enumeration.
  667.    *
  668.    * \param aMediaList - The media list that is being enumerated.
  669.    * \param aMediaItem - The media item.
  670.    *
  671.    * \return true to continue enumeration, false to cancel.
  672.    */
  673.  
  674.   onEnumeratedItem: function DSW_MLEAL_onEnumeratedItem(aMediaList,
  675.                                                         aMediaItem) {
  676.     // Add the item to the enumeration array.
  677.     this.array.push(aMediaItem);
  678.     return Ci.sbIMediaListEnumerationListener.CONTINUE;
  679.   },
  680.  
  681.  
  682.   /**
  683.    * \brief Called when enumeration has completed.
  684.    *
  685.    * \param aMediaList - The media list that is being enumerated.
  686.    * \param aStatusCode - A code to determine if the enumeration was successful.
  687.    */
  688.  
  689.   onEnumerationEnd: function DSW_MLEAL_onEnumerationEnd(aMediaList,
  690.                                                         aStatusCode) {
  691.   }
  692. }
  693.  
  694. /**
  695.  * \class LibraryUtils.GlobalMediaListListener
  696.  * \brief Attaches a listener to all currently existing libraries
  697.  *        and lists in the system, and monitors the new playlists
  698.  *        and libraries in order to automatically attach the
  699.  *        listener whenever they are created.
  700.  *        You may also specify a library to restrict the listeners
  701.  *        to that library and its playlists' events instead of
  702.  *        listening for all events in all libraries and lists.
  703.  */
  704. LibraryUtils.GlobalMediaListListener = function(aListener,
  705.                                                 aOwnsWeak,
  706.                                                 aFlags,
  707.                                                 aPropFilter,
  708.                                                 aOnlyThisLibrary) {
  709.   if (aFlags === undefined) {
  710.     aFlags = Components.interfaces.sbIMediaList.LISTENER_FLAGS_ALL;
  711.   }
  712.  
  713.   this.listener = aListener;
  714.   this.ownsWeak = aOwnsWeak;
  715.   this.propFilter = aPropFilter;
  716.   this.listenerFlags = aFlags;
  717.  
  718.   this.libraryManager =
  719.     Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  720.       .getService(Ci.sbILibraryManager);
  721.  
  722.   this.listListener = {
  723.     cb: this,
  724.     onitemadded_skipbatch : false,
  725.     onitemadded_stack     : [],
  726.     onafteritemremoved_skipbatch : false,
  727.     onafteritemremoved_stack     : [],
  728.     batchcount : 0,
  729.     onItemAdded: function(aMediaList, aMediaItem, aIndex) {
  730.       if (aMediaItem instanceof Ci.sbIMediaList)
  731.         this.cb.addMediaListListener(aMediaItem);
  732.       if (this.batchcount > 0 && this.onitemadded_skipbatch)
  733.         return Ci.sbIMediaListEnumerationListener.CONTINUE;
  734.       if (this.cb.listener.onItemAdded(aMediaList, aMediaItem, aIndex) !=
  735.           Ci.sbIMediaListEnumerationListener.CONTINUE) {
  736.         this.onitemadded_skipbatch = true;
  737.       }
  738.       return Ci.sbIMediaListEnumerationListener.CONTINUE;
  739.     },
  740.     onBeforeItemRemoved: function(aMediaList, aMediaItem, aIndex) {
  741.       return this.cb.listener.onBeforeItemRemoved(aMediaList, aMediaItem, aIndex);
  742.     },
  743.     onAfterItemRemoved: function(aMediaList, aMediaItem, aIndex) {
  744.       if (aMediaItem instanceof Ci.sbIMediaList)
  745.         this.cb.removeMediaListListener(aMediaItem);
  746.       if (this.onafteritemremoved_skipbatch)
  747.         return Ci.sbIMediaListEnumerationListener.CONTINUE;
  748.       if (this.cb.listener.onAfterItemRemoved(aMediaList, aMediaItem, aIndex) !=
  749.         Ci.sbIMediaListEnumerationListener.CONTINUE) {
  750.         this.onafteritemremoved_skipbatch = true;
  751.       }
  752.       return Ci.sbIMediaListEnumerationListener.CONTINUE;
  753.     },
  754.     onItemUpdated: function(aMediaList, aMediaItem, aProperties) {
  755.       return this.cb.listener.onItemUpdated(aMediaList, aMediaItem, aProperties);
  756.     },
  757.     onItemMoved: function(aMediaList, aFromIndex, aToIndex) {
  758.       return this.cb.listener.onItemMoved(aMediaList, aFromIndex, aToIndex);
  759.     },
  760.     onBeforeListCleared: function(aMediaList, aExcludeLists) {
  761.       return this.cb.listener.onBeforeListCleared(aMediaList, aExcludeLists);
  762.     },
  763.     onListCleared: function(aMediaList, aExcludeLists) {
  764.       return this.cb.listener.onListCleared(aMediaList, aExcludeLists);
  765.     },
  766.     onBatchBegin: function(aMediaList) {
  767.       ++this.batchcount;
  768.       this.onitemadded_stack.push(this.onitemadded_skipbatch);
  769.       this.onitemadded_skipbatch = false;
  770.       this.onafteritemremoved_stack.push(this.onafteritemremoved_skipbatch);
  771.       this.onafteritemremoved_skipbatch = false;
  772.       return this.cb.listener.onBatchBegin(aMediaList);
  773.     },
  774.     onBatchEnd: function(aMediaList) {
  775.       --this.batchcount;
  776.       this.onitemadded_skipbatch = this.onitemadded_stack.pop();
  777.       this.onafteritemremoved_skipbatch = this.onafteritemremoved_stack.pop();
  778.       return this.cb.listener.onBatchEnd(aMediaList);
  779.     },
  780.     QueryInterface: function(iid) {
  781.       if (iid.equals(Components.interfaces.sbIMediaListListener) ||
  782.           iid.equals(Components.interfaces.nsISupports))
  783.         return this;
  784.       throw Components.results.NS_ERROR_NO_INTERFACE;
  785.     }
  786.   };
  787.  
  788.  
  789.   var enumListener = {
  790.     cb: this,
  791.     onEnumerationBegin: function(aMediaList) { },
  792.     onEnumerationEnd: function(aMediaList) { },
  793.     onEnumeratedItem: function(aMediaList, aMediaItem) {
  794.       this.cb.addMediaListListener(aMediaItem);
  795.       return Components.interfaces.sbIMediaListEnumerationListener.CONTINUE;
  796.     },
  797.     QueryInterface: function(iid) {
  798.       if (iid.equals(Components.interfaces.sbIMediaListEnumerationListener) ||
  799.           iid.equals(Components.interfaces.nsISupports))
  800.         return this;
  801.       throw Components.results.NS_ERROR_NO_INTERFACE;
  802.     }
  803.   };
  804.  
  805.   var libs = this.libraryManager.getLibraries();
  806.   while (libs.hasMoreElements()) {
  807.     var library = libs.getNext();
  808.     if (aOnlyThisLibrary &&
  809.         library != aOnlyThisLibrary)
  810.       continue;
  811.     enumListener.onEnumeratedItem(library, library);
  812.     library.
  813.       enumerateItemsByProperty(
  814.         SBProperties.isList,
  815.         "1",
  816.         enumListener,
  817.         Components.interfaces.sbIMediaList.ENUMERATIONTYPE_LOCKING);
  818.   }
  819.  
  820.   if (!aOnlyThisLibrary) {
  821.     this.managerListener = {
  822.       cb: this,
  823.       onLibraryRegistered: function(aLibrary) {
  824.         this.cb.addMediaListListener(aLibrary);
  825.         this.cb.listener.onItemAdded(null, aLibrary, 0);
  826.       },
  827.       onLibraryUnregistered: function(aLibrary) {
  828.         this.cb.listener.onBeforeItemRemoved(null, aLibrary, 0);
  829.         this.cb.removeMediaListListener(aLibrary);
  830.         this.cb.listener.onAfterItemRemoved(null, aLibrary, 0);
  831.       },
  832.       QueryInterface: function(iid) {
  833.         if (iid.equals(Components.interfaces.sbILibraryManagerListener) ||
  834.             iid.equals(Components.interfaces.nsISupports))
  835.           return this;
  836.         throw Components.results.NS_ERROR_NO_INTERFACE;
  837.       }
  838.     };
  839.  
  840.     this.libraryManager.addListener(this.managerListener);
  841.   }
  842. }
  843.  
  844. LibraryUtils.GlobalMediaListListener.prototype = {
  845.   listener        : null,
  846.   listento        : [],
  847.   managerListener : null,
  848.   listListener    : null,
  849.   ownsWeak        : null,
  850.   propFilter      : null,
  851.   listenerFlags   : 0,
  852.  
  853.   shutdown: function() {
  854.     for (var i=0;i<this.listento.length;i++)
  855.       this.listento[i].removeListener(this.listListener);
  856.  
  857.     this.listento = [];
  858.     this.listListener = null;
  859.  
  860.     this.listener = null;
  861.     this.propFilter = null;
  862.  
  863.     if (this.managerListener)
  864.       this.libraryManager.removeListener(this.managerListener);
  865.     this.managerListener = null;
  866.  
  867.     this.libraryManager = null;
  868.   },
  869.  
  870.   addMediaListListener: function(aList) {
  871.     if (this.listento.indexOf(aList) >= 0)
  872.       return;
  873.     aList.addListener(this.listListener,
  874.                       this.ownsWeak,
  875.                       this.listenerFlags,
  876.                       this.propFilter);
  877.     this.listento.push(aList);
  878.   },
  879.  
  880.   removeMediaListListener: function(aList) {
  881.     aList.removeListener(this.listListener);
  882.     var p = this.listento.indexOf(aList);
  883.     if (p >= 0) this.listento.splice(p, 1);
  884.   }
  885. }
  886.  
  887. /**
  888.  * This function is a big ugly hack until we get x-mtp channel working
  889.  * We're punting for now and making mtp item editable, but they aren't
  890.  * as far as the track editor is concerned.
  891.  * presence of the device ID property means we're dealing with an MTP
  892.  * device
  893.  * TODO: XXX Remove this function when x-mtp channel lands and replace
  894.  * it's calls with item.userEditable
  895.  */
  896. LibraryUtils.canEditMetadata = function (aItem) {
  897.   var editable = aItem.userEditable;
  898.   if (editable) {
  899.     try {
  900.       var contentSrc = aItem.contentSrc;
  901.       if (contentSrc.scheme == "x-mtp")
  902.         editable = false;
  903.     } catch (e) {
  904.       // Errors means it's not x-mtp and edtible
  905.     }
  906.   }
  907.   return editable;
  908. }
  909.