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 / sbLibraryServicePaneService.js < prev    next >
Text File  |  2007-12-21  |  53KB  |  1,644 lines

  1. /** vim: ts=2 sw=2 expandtab
  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.  
  27. /**
  28.  * \file sbLibraryServicePane.js
  29.  */
  30.  
  31. const Cc = Components.classes;
  32. const Ci = Components.interfaces;
  33. const Cr = Components.results;
  34.  
  35. Components.utils.import("resource://app/components/sbProperties.jsm");
  36. Components.utils.import("resource://app/components/sbLibraryUtils.jsm");
  37.  
  38. const CONTRACTID = "@songbirdnest.com/servicepane/library;1";
  39. const ROOTNODE = "SB:Bookmarks";
  40.  
  41. const PROP_ISLIST = "http://songbirdnest.com/data/1.0#isList";
  42. const PROP_ISHIDDEN = "http://songbirdnest.com/data/1.0#hidden";
  43.  
  44. const URN_PREFIX_ITEM = 'urn:item:';
  45. const URN_PREFIX_LIBRARY = 'urn:library:';
  46.  
  47. // TODO: Remove this
  48. const URL_PLAYLIST_DISPLAY = "chrome://songbird/content/xul/sbLibraryPage.xul?"
  49.  
  50. const LSP = 'http://songbirdnest.com/rdf/library-servicepane#';
  51. const SP='http://songbirdnest.com/rdf/servicepane#';
  52.  
  53.  
  54. const TYPE_X_SB_TRANSFER_MEDIA_ITEM = "application/x-sb-transfer-media-item";
  55. const TYPE_X_SB_TRANSFER_MEDIA_LIST = "application/x-sb-transfer-media-list";
  56. const TYPE_X_SB_TRANSFER_MEDIA_ITEMS = "application/x-sb-transfer-media-items";
  57.  
  58. /**
  59.  * Given the arguments var of a function, dump the
  60.  * name of the function and the parameters provided
  61.  */
  62. function logcall(parentArgs) {
  63.   dump("\n");
  64.   dump(parentArgs.callee.name + "(");
  65.   for (var i = 0; i < parentArgs.length; i++) {
  66.     dump(parentArgs[i]);
  67.     if (i < parentArgs.length - 1) {
  68.       dump(', ');
  69.     }
  70.   }
  71.   dump(")\n");
  72. }
  73.  
  74.  
  75.  
  76. /**
  77.  * /class sbLibraryServicePane
  78.  * /brief Provides library related nodes for the service pane
  79.  * \sa sbIServicePaneService sbILibraryServicePaneService
  80.  */
  81. function sbLibraryServicePane() {
  82.   this._servicePane = null;
  83.   this._libraryManager = null;
  84.   this._lastShortcuts = null;
  85.   this._lastMenuitems = null;
  86.  
  87.   // use the default stringbundle to translate tree nodes
  88.   this.stringbundle = null;
  89.  
  90.   this._batch = new BatchHelper();
  91.   this._refreshPending = false;
  92. }
  93. sbLibraryServicePane.prototype.QueryInterface =
  94. function sbLibraryServicePane_QueryInterface(iid) {
  95.   if (!iid.equals(Ci.nsISupports) &&
  96.     !iid.equals(Ci.nsIObserver) &&
  97.     !iid.equals(Ci.sbIServicePaneModule) &&
  98.     !iid.equals(Ci.sbILibraryServicePaneService) &&
  99.     !iid.equals(Ci.sbILibraryManagerListener) &&
  100.     !iid.equals(Ci.sbIMediaListListener)) {
  101.     throw Components.results.NS_ERROR_NO_INTERFACE;
  102.   }
  103.   return this;
  104. }
  105.  
  106.  
  107. //////////////////////////
  108. // sbIServicePaneModule //
  109. //////////////////////////
  110.  
  111. sbLibraryServicePane.prototype.servicePaneInit =
  112. function sbLibraryServicePane_servicePaneInit(sps) {
  113.   //logcall(arguments);
  114.  
  115.   // keep track of the service pane service
  116.   this._servicePane = sps;
  117.  
  118.   // Before initializing, hide all existing library nodes.
  119.   // This way we only show things when we are notified
  120.   // that they actually exist.
  121.   this._hideLibraryNodes(this._servicePane.root);
  122.  
  123.   // register for notification that the library manager is initialized
  124.   var obs = Cc["@mozilla.org/observer-service;1"].
  125.             getService(Ci.nsIObserverService);
  126.   obs.addObserver(this, "songbird-library-manager-ready", false);
  127.   obs.addObserver(this, "songbird-library-manager-before-shutdown", false);
  128.  
  129.   // get the library manager
  130.   this._initLibraryManager();
  131. }
  132.  
  133. sbLibraryServicePane.prototype.shutdown =
  134. function sbLibraryServicePane_shutdown() {
  135.   this._removeAllLibraries();
  136. }
  137.  
  138. sbLibraryServicePane.prototype.fillContextMenu =
  139. function sbLibraryServicePane_fillContextMenu(aNode, aContextMenu, aParentWindow) {
  140.  
  141.   // the playlists folder and the local library node get the "New Foo..." items
  142.   if (aNode.id == 'SB:Playlists' ||
  143.       aNode.getAttributeNS(LSP, 'ListCustomType') == 'local') {
  144.     this.fillNewItemMenu(aNode, aContextMenu, aParentWindow);
  145.   }
  146.  
  147.   var list = this.getLibraryResourceForNode(aNode);
  148.   if (list) {
  149.     this._appendCommands(aContextMenu, list, aParentWindow);
  150.  
  151.     // Add menu items for a smart media list
  152.     if (list instanceof Ci.sbILocalDatabaseSmartMediaList) {
  153.       this._appendMenuItem(aContextMenu, "Properties", function(event) { //XXX todo: localize
  154.         var watcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
  155.                         .getService(Ci.nsIWindowWatcher);
  156.         watcher.openWindow(null,
  157.                           "chrome://songbird/content/xul/smartPlaylist.xul",
  158.                           "_blank",
  159.                           "chrome,dialog=yes",
  160.                           list);
  161.       });
  162.     }
  163.  
  164.     // Add menu items for a dynamic media list
  165.     if (list.getProperty("http://songbirdnest.com/data/1.0#isSubscription") == "1") {
  166.       this._appendMenuItem(aContextMenu, "Properties", function(event) { //XXX todo: localize
  167.  
  168.         var params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
  169.         params.appendElement(list, false);
  170.  
  171.         var watcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
  172.                         .getService(Ci.nsIWindowWatcher);
  173.         watcher.openWindow(null,
  174.                            "chrome://songbird/content/xul/subscribe.xul",
  175.                            "_blank",
  176.                            "chrome,dialog=yes",
  177.                            params);
  178.       });
  179.       this._appendMenuItem(aContextMenu, "Update", function(event) { //XXX todo: localize
  180.         var dps = Cc["@songbirdnest.com/Songbird/Library/LocalDatabase/DynamicPlaylistService;1"]
  181.                     .getService(Ci.sbILocalDatabaseDynamicPlaylistService);
  182.         dps.updateNow(list);
  183.       });
  184.     }
  185.   }
  186. }
  187.  
  188. sbLibraryServicePane.prototype.fillNewItemMenu =
  189. function sbLibraryServicePane_fillNewItemMenu(aNode, aContextMenu, aParentWindow) {
  190.   var sbSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
  191.   var stringBundle = sbSvc.createBundle("chrome://songbird/locale/songbird.properties");
  192.  
  193.   function add(id, label, accesskey, oncommand) {
  194.     var menuitem = aContextMenu.ownerDocument.createElement('menuitem');
  195.     menuitem.setAttribute('id', id);
  196.     menuitem.setAttribute('class', 'menuitem-iconic');
  197.     menuitem.setAttribute('label', stringBundle.GetStringFromName(label));
  198.     menuitem.setAttribute('accesskey', stringBundle.GetStringFromName(accesskey));
  199.     menuitem.setAttribute('oncommand', oncommand);
  200.     aContextMenu.appendChild(menuitem);
  201.   }
  202.  
  203.   add('menuitem_file_new', 'menu.file.new', 'menu.file.new.accesskey', 'doMenu("menuitem_file_new")');
  204.   //XXX Mook: disabled; see bug 4001
  205.   //add('file.smart', 'menu.file.smart', 'menu.file.smart.accesskey', 'doMenu("file.smart")');
  206.   add('menuitem_file_remote', 'menu.file.remote', 'menu.file.remote.accesskey', 'doMenu("menuitem_file_remote")');
  207. }
  208.  
  209. sbLibraryServicePane.prototype.onSelectionChanged =
  210. function sbLibraryServicePane_onSelectionChanged(aNode, aContainer, aParentWindow) {
  211.   this._destroyShortcuts(aContainer, aParentWindow);
  212.   var list;
  213.   if (aNode) list = this.getLibraryResourceForNode(aNode);
  214.   if (list) this._createShortcuts(list, aContainer, aParentWindow);
  215. }
  216.  
  217. sbLibraryServicePane.prototype._createShortcuts =
  218. function sbLibraryServicePane__createShortcuts(aList, aContainer, aWindow) {
  219.   var shortcuts = aWindow.document.createElement("sb-commands-shortcuts");
  220.   shortcuts.setAttribute("id", "playlist-commands-shortcuts");
  221.   shortcuts.setAttribute("commandtype", "medialist");
  222.   shortcuts.setAttribute("bind", aList.library.guid + ';' + aList.guid);
  223.   aContainer.appendChild(shortcuts);
  224.   this._lastShortcuts = shortcuts;
  225. }
  226.  
  227. sbLibraryServicePane.prototype._destroyShortcuts =
  228. function sbLibraryServicePane__destroyShortcuts(aContainer, aWindow) {
  229.   if (this._lastShortcuts) {
  230.     this._lastShortcuts.destroy();
  231.     aContainer.removeChild(this._lastShortcuts);
  232.     this._lastShortcuts = null;
  233.   }
  234. }
  235.  
  236. sbLibraryServicePane.prototype._getMediaListForDrop =
  237. function sbLibraryServicePane__getMediaListForDrop(aNode, aDragSession, aOrientation) {
  238.   dump('_getMediaListForDrop('+aNode+', '+aDragSession+', '+aOrientation+')\n');
  239.   // work out what the drop would target and return an sbIMediaList to
  240.   // represent that target, or null if the drop is not allowed
  241.  
  242.   // work out what's being dropped
  243.   var dropList = aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_LIST);
  244.  
  245.   // if it's not a list and not items, then we don't know or care what happens
  246.   // next
  247.   if (!dropList &&
  248.       !aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEM) &&
  249.       !aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEMS)) {
  250.     return null;
  251.   }
  252.  
  253.   dump('dropList='+dropList+'\n');
  254.  
  255.   // work out where its going
  256.   var container = aNode;
  257.   var dropOnto = true;
  258.   if (aOrientation != 0) {
  259.     // if we're dropping before/after we're dropping into the parent
  260.     container = aNode.parentNode;
  261.     dropOnto = false;
  262.   }
  263.  
  264.   dump('container='+container+' ('+container.id+')\n');
  265.   dump('dropOnto='+dropOnto+'\n');
  266.  
  267.   // work out what library resource is associated with the container node
  268.   var containerResource = this.getLibraryResourceForNode(container);
  269.  
  270.   dump('containerResource='+containerResource+'\n');
  271.  
  272.   // if there's no associated library then the target is assumed to be the
  273.   // default library
  274.   if (containerResource == null) {
  275.     containerResource = this._libraryManager.mainLibrary;
  276.   }
  277.  
  278.   // is the target a library
  279.   var isLibrary = (containerResource instanceof Ci.sbILibrary);
  280.  
  281.   dump('isLibrary='+isLibrary+'\n');
  282.  
  283.   // The Rules:
  284.   //  * (non-playlist) items can be dropped on top of playlists or libraries
  285.   //  * playlists can be dropped next to other playlists in the same container
  286.  
  287.   if (!dropList && dropOnto) {
  288.     return containerResource;
  289.   }
  290.   if (dropList && isLibrary) {
  291.     var draggedItem = this._getDndData(aDragSession,
  292.                                        TYPE_X_SB_TRANSFER_MEDIA_LIST,
  293.                                        Ci.sbISingleListTransferContext);
  294.     if (!draggedItem) {
  295.       return null;
  296.     }
  297.     var draggedContainer = this.getNodeForLibraryResource(draggedItem.list);
  298.     if (draggedContainer == container) {
  299.       return containerResource;
  300.     }
  301.   }
  302.  
  303.   return null;
  304. }
  305.  
  306. sbLibraryServicePane.prototype.canDrop =
  307. function sbLibraryServicePane_canDrop(aNode, aDragSession, aOrientation) {
  308.   dump('\n\n\ncanDrop:\n');
  309.  
  310.   var list = this._getMediaListForDrop(aNode, aDragSession, aOrientation);
  311.  
  312.   if (list) {
  313.     dump('I CAN HAS DORP\n');
  314.     // XXX Mook: hack for bug 4760 to do special handling for the download
  315.     // playlist.  This will need to be expanded later to use IDLs on the
  316.     // list so that things like extensions can do this too.
  317.     var customType = list.getProperty(SBProperties.customType);
  318.     if (customType == "download") {
  319.       var IOS = Cc["@mozilla.org/network/io-service;1"]
  320.                   .getService(Ci.nsIIOService);
  321.  
  322.       function canDownload(aMediaItem) {
  323.         var contentSpec = aMediaItem.getProperty(SBProperties.contentURL);
  324.         var contentURL = IOS.newURI(contentSpec, null, null);
  325.         switch(contentURL.scheme) {
  326.           case "http":
  327.           case "https":
  328.           case "ftp":
  329.             // these are safe to download
  330.             dump(<>[{contentURL.spec} accept] </>);
  331.             return true;
  332.         }
  333.         dump(<>[{contentURL.spec} DENY] </>);
  334.         return false;
  335.       }
  336.  
  337.       if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEMS)) {
  338.         var context = this._getDndData(aDragSession,
  339.                                        TYPE_X_SB_TRANSFER_MEDIA_ITEMS,
  340.                                        Ci.sbIMultipleItemTransferContext);
  341.         var items = context.items;
  342.         // we must remember to reset the context before we exit, so that when we
  343.         // actually need the items in onDrop we can get them again!
  344.         var count = 0;
  345.         while (items.hasMoreElements()) {
  346.           var item = items.getNext();
  347.           item.QueryInterface(Ci.sbIIndexedMediaItem);
  348.           if (!canDownload(item.mediaItem)) {
  349.             context.reset();
  350.             return false;
  351.           }
  352.           ++count;
  353.         }
  354.         if (count < 1) {
  355.           // umm, where's that list of items?
  356.           context.reset();
  357.           return false;
  358.         }
  359.         // all items in the list are downloadable
  360.         context.reset();
  361.         return true;
  362.       } else if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEM)) {
  363.         var context = this._getDndData(aDragSession,
  364.                                        TYPE_X_SB_TRANSFER_MEDIA_ITEM,
  365.                                        Ci.sbISingleItemTransferContext);
  366.         return canDownload(context.item);
  367.       } else {
  368.         // huh? _getMediaListForDrop should have said no
  369.         return false;
  370.       }
  371.     }
  372.     return true;
  373.   } else {
  374.     dump('NO DROP FOR YOU!\n');
  375.     return false;
  376.   }
  377. }
  378. sbLibraryServicePane.prototype._getDndData =
  379. function sbLibraryServicePane__getDndData(aDragSession, aDataType, aInterface) {
  380.   // create an nsITransferable
  381.   var transferable = Components.classes["@mozilla.org/widget/transferable;1"].
  382.       createInstance(Components.interfaces.nsITransferable);
  383.   // specify what kind of data we want it to contain
  384.   transferable.addDataFlavor(aDataType);
  385.   // ask the drag session to fill the transferable with that data
  386.   aDragSession.getData(transferable, 0);
  387.   // get the data from the transferable
  388.   var data = {};
  389.   var dataLength = {};
  390.   transferable.getTransferData(aDataType, data, dataLength);
  391.   // it's always a string. always.
  392.   data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
  393.   data = data.toString();
  394.  
  395.   // get the object from the dnd source tracker
  396.   var dnd = Components.classes["@songbirdnest.com/Songbird/DndSourceTracker;1"]
  397.       .getService(Components.interfaces.sbIDndSourceTracker);
  398.   return dnd.getSource(data).QueryInterface(aInterface);
  399. }
  400.  
  401. sbLibraryServicePane.prototype.onDrop =
  402. function sbLibraryServicePane_onDrop(aNode, aDragSession, aOrientation) {
  403.   dump('\n\n\nonDrop:\n');
  404.  
  405.   // where are we dropping?
  406.   var targetList = this._getMediaListForDrop(aNode, aDragSession, aOrientation);
  407.   if (!targetList) {
  408.     // don't know how to drop here
  409.     return;
  410.   }
  411.  
  412.   var targetListIsLibrary = (targetList instanceof Ci.sbILibrary);
  413.  
  414.   var metrics = Components.classes["@songbirdnest.com/Songbird/Metrics;1"]
  415.                     .createInstance(Components.interfaces.sbIMetrics);
  416.  
  417.   var targettype = targetList.getProperty(SBProperties.customType);
  418.   var totype = targetList.library.getProperty(SBProperties.customType);
  419.  
  420.   if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_LIST)) {
  421.     dump('media list dropped\n');
  422.  
  423.     var context = this._getDndData(aDragSession,
  424.         TYPE_X_SB_TRANSFER_MEDIA_LIST, Ci.sbISingleListTransferContext);
  425.     var list = context.list;
  426.  
  427.     // Metrics!
  428.     var fromtype = list.library.getProperty(SBProperties.customType);
  429.     metrics.metricsAdd("app.servicepane.copy", fromtype, totype, list.length);
  430.  
  431.     if (targetListIsLibrary) {
  432.       // want to copy the list and the contents
  433.       if (targetList == list.library) {
  434.         dump('this list is already in this library\n');
  435.         return;
  436.       }
  437.  
  438.       // create a copy of the list
  439.       var newlist = targetList.copyMediaList('simple', list);
  440.  
  441.       // find the node that was created
  442.       var newnode = this._servicePane.getNode(this._itemURN(newlist));
  443.       if (aOrientation < 0) {
  444.         aNode.parentNode.insertBefore(newnode, aNode);
  445.       } else if (aNode.nextSibling) {
  446.         aNode.parentNode.insertBefore(newnode, aNode.nextSibling);
  447.       } else {
  448.         aNode.parentNode.appendChild(newnode);
  449.       }
  450.  
  451.       // FIXME: handle the creation of the node in the pane correctly
  452.     } else {
  453.       if (context.list == targetList) {
  454.         // uh oh - you can't drop a list onto itself
  455.         dump("you can't drop a list onto itself\n");
  456.         return;
  457.       }
  458.       // just add the contents
  459.       targetList.addAll(list);
  460.     }
  461.     dump('added\n');
  462.   } else if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEMS)) {
  463.     dump('media items dropped\n');
  464.  
  465.     var context = this._getDndData(aDragSession,
  466.         TYPE_X_SB_TRANSFER_MEDIA_ITEMS, Ci.sbIMultipleItemTransferContext);
  467.  
  468.     var items = context.items;
  469.  
  470.     // Metrics!
  471.     var fromtype = context.source.library.getProperty(SBProperties.customType);
  472.     //Components.utils.reportError(fromtype + " - " + totype );
  473.     metrics.metricsAdd("app.servicepane.copy", fromtype, totype, context.count);
  474.  
  475.     targetList.runInBatchMode(function() {
  476.       while (items.hasMoreElements()) {
  477.         var item = items.getNext().mediaItem;
  478.         item.setProperty(SBProperties.downloadStatusTarget,
  479.                          item.library.guid + "," + item.guid);
  480.         targetList.add(item);
  481.       }
  482.     });
  483.  
  484.     dump('added\n');
  485.  
  486.   } else if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEM)) {
  487.     dump('media item dropped\n');
  488.  
  489.     var context = this._getDndData(aDragSession,
  490.         TYPE_X_SB_TRANSFER_MEDIA_ITEM, Ci.sbISingleItemTransferContext);
  491.  
  492.     var item = context.item;
  493.     item.setProperty(SBProperties.downloadStatusTarget,
  494.                      item.library.guid + "," + item.guid);
  495.     targetList.add(item);
  496.  
  497.     // Metrics!
  498.     var fromtype = context.source.library.getProperty(SBProperties.customType);
  499.     metrics.metricsAdd("app.servicepane.copy", fromtype, totype, 1);
  500.  
  501.     dump('added\n');
  502.   }
  503. }
  504. sbLibraryServicePane.prototype._nodeIsLibrary =
  505. function sbLibraryServicePane__nodeIsLibrary(aNode) {
  506.   return aNode.getAttributeNS(LSP, "LibraryGUID") ==
  507.       aNode.getAttributeNS(LSP, "ListGUID");
  508. }
  509. sbLibraryServicePane.prototype.onDragGesture =
  510. function sbLibraryServicePane_onDragGesture(aNode, aTransferable) {
  511.   if (this._nodeIsLibrary(aNode)) {
  512.     // a library isn't dragable
  513.     return false;
  514.   }
  515.  
  516.   // get the list and create the source context
  517.   var list = this._getItemForURN(aNode.id);
  518.   var context = {
  519.     source: list.library,
  520.     count: 1,
  521.     list: list,
  522.     QueryInterface: function(iid) {
  523.       if (iid.equals(Components.interfaces.sbISingleListTransferContext) ||
  524.           iid.equals(Components.interfaces.nsISupports)) {
  525.         return this;
  526.       }
  527.       throw Components.results.NS_NOINTERFACE;
  528.     }
  529.   }
  530.  
  531.   // register the source context
  532.   var dnd = Components.classes['@songbirdnest.com/Songbird/DndSourceTracker;1']
  533.       .getService(Components.interfaces.sbIDndSourceTracker);
  534.   dnd.reset();
  535.   var handle = dnd.registerSource(context);
  536.  
  537.   // attach the source context to the transferable
  538.   aTransferable.addDataFlavor(TYPE_X_SB_TRANSFER_MEDIA_LIST);
  539.   var text = Components.classes["@mozilla.org/supports-string;1"].
  540.      createInstance(Components.interfaces.nsISupportsString);
  541.   text.data = handle;
  542.   aTransferable.setTransferData(TYPE_X_SB_TRANSFER_MEDIA_LIST, text,
  543.                                 text.data.length*2);
  544.  
  545.   return true;
  546. }
  547.  
  548.  
  549. /**
  550.  * Called when the user has attempted to rename a library/medialist node
  551.  */
  552. sbLibraryServicePane.prototype.onRename =
  553. function sbLibraryServicePane_onRename(aNode, aNewName) {
  554.   //logcall(arguments);
  555.   if (aNode && aNewName) {
  556.     var libraryResource = this.getLibraryResourceForNode(aNode);
  557.     libraryResource.name = aNewName;
  558.   }
  559. }
  560.  
  561.  
  562. //////////////////////////////////
  563. // sbILibraryServicePaneService //
  564. //////////////////////////////////
  565.  
  566.  
  567. /* \brief Suggest a library for creating a new media list
  568.  *
  569.  * \param aMediaListType string identifying a media list type, eg "simple"
  570.  * \param aNode A service pane node to provide context for new list creation
  571.  * \return a library, or null if this service can't suggest anything based on
  572.  *         the given context and type.
  573.  */
  574. sbLibraryServicePane.prototype.suggestLibraryForNewList =
  575. function sbLibraryServicePane_suggestLibraryForNewList(aMediaListType, aNode) {
  576.   //logcall(arguments);
  577.  
  578.   // Must provide a media list type
  579.   if (!aMediaListType) {
  580.     throw Components.results.NS_ERROR_INVALID_ARG;
  581.   }
  582.  
  583.   // Make sure we are fully initialized
  584.   if (!this._libraryManager || !this._servicePane) {
  585.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  586.   }
  587.  
  588.   // Move up the tree looking for libraries that support the
  589.   // given media list type.
  590.   while (aNode && aNode != this._servicePane.root) {
  591.  
  592.     // If this node is visible and belongs to the library
  593.     // service pane service...
  594.     if (aNode.contractid == CONTRACTID && !aNode.hidden) {
  595.       // If this is a playlist and the playlist belongs
  596.       // to a library that supports the given type,
  597.       // then suggest that library
  598.       var mediaItem = this._getItemForURN(aNode.id);
  599.       if (mediaItem && mediaItem instanceof Ci.sbIMediaList &&
  600.           this._doesLibrarySupportListType(mediaItem.library, aMediaListType))
  601.       {
  602.         return mediaItem.library;
  603.       }
  604.  
  605.       // If this is a library that supports the given type,
  606.       // then suggest the library
  607.       var library = this._getLibraryForURN(aNode.id);
  608.       if (library && library instanceof Ci.sbILibrary &&
  609.           this._doesLibrarySupportListType(library, aMediaListType))
  610.       {
  611.         return library;
  612.       }
  613.     }
  614.  
  615.     // Move up the tree
  616.     aNode = aNode.parentNode;
  617.   } // end of while
  618.  
  619.   // If the main library supports the given type, then return that
  620.   if (this._doesLibrarySupportListType(this._libraryManager.mainLibrary,
  621.                                        aMediaListType))
  622.   {
  623.     return this._libraryManager.mainLibrary;
  624.   }
  625.  
  626.   // Oh well, out of luck
  627.   return null;
  628. }
  629.  
  630. sbLibraryServicePane.prototype.createNodeForLibrary =
  631. function sbLibraryServicePane_createNodeForLibrary(aLibrary) {
  632.   if(aLibrary instanceof Ci.sbILibrary) {
  633.     return this._libraryAdded(aLibrary);
  634.   }
  635.  
  636.   return null;
  637. }
  638.  
  639. /* \brief Attempt to get a service pane node for the given library resource
  640.  *
  641.  * \param aResource an sbIMediaItem, sbIMediaItem, or sbILibrary
  642.  * \return a service pane node that represents the given resource, if one exists
  643.  */
  644. sbLibraryServicePane.prototype.getNodeForLibraryResource =
  645. function sbLibraryServicePane_getNodeForLibraryResource(aResource) {
  646.   //logcall(arguments);
  647.  
  648.   // Must be initialized
  649.   if (!this._libraryManager || !this._servicePane) {
  650.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  651.   }
  652.  
  653.   var node;
  654.  
  655.   // If this is a library, make a library node
  656.   if (aResource instanceof Ci.sbILibrary) {
  657.     node = this._servicePane.getNode(this._libraryURN(aResource));
  658.  
  659.   // If this is a mediaitem, make a mediaitem node
  660.   } else if (aResource instanceof Ci.sbIMediaItem) {
  661.     node = this._servicePane.getNode(this._itemURN(aResource));
  662.  
  663.   // Else we don't know what to do, so
  664.   // the arg must be invalid
  665.   } else {
  666.     throw Components.results.NS_ERROR_INVALID_ARG;
  667.   }
  668.  
  669.   return node;
  670. }
  671.  
  672.  
  673. /* \brief Attempt to get a library resource for the given service pane node.
  674.  *
  675.  * Note that there is no guarantee that hidden service pane nodes
  676.  * will have corresponding library resources
  677.  *
  678.  * \param aNode
  679.  * \return a sbIMediaItem, sbIMediaItem, sbILibrary, or null
  680.  */
  681. sbLibraryServicePane.prototype.getLibraryResourceForNode =
  682. function sbLibraryServicePane_getLibraryResourceForNode(aNode) {
  683.   //logcall(arguments);
  684.  
  685.   // Must provide a node
  686.   if (!(aNode instanceof Ci.sbIServicePaneNode)) {
  687.     throw Components.results.NS_ERROR_INVALID_ARG;
  688.   }
  689.   // Must be initialized
  690.   if (!this._libraryManager || !this._servicePane) {
  691.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  692.   }
  693.  
  694.   // If the node does not belong to us, then we aren't
  695.   // going to find a resource
  696.   if (aNode.contractid != CONTRACTID) {
  697.     return null;
  698.   }
  699.  
  700.   // Attempt to get a resource from the id of the given node
  701.   var resource = this._getItemForURN(aNode.id);
  702.   if (!resource) {
  703.     resource = this._getLibraryForURN(aNode.id);
  704.   }
  705.  
  706.   return resource;
  707. }
  708.  
  709.  
  710. /////////////////////
  711. // Private Methods //
  712. /////////////////////
  713.  
  714.  
  715. /**
  716.  * Return true if the given library supports the given list type
  717.  */
  718. sbLibraryServicePane.prototype._doesLibrarySupportListType =
  719. function sbLibraryServicePane__doesLibrarySupportListType(aLibrary, aListType) {
  720.   //logcall(arguments);
  721.  
  722.   // If the transfer policy indicates read only media lists, the library does
  723.   // not support adding media lists of any type
  724.   // XXXerik less than SUPER HACK to keep new playlists from being added to
  725.   // device libraries.  This uses a hacked up policy system that will be
  726.   // replaced by a real one.
  727.   var transferPolicy = aLibrary.getProperty(SBProperties.transferPolicy);
  728.   if (transferPolicy && transferPolicy.match(/readOnlyMediaLists/)) {
  729.     return false;
  730.   }
  731.  
  732.   // XXXben SUPER HACK to keep new playlists from being added to the web
  733.   //        library. We should really fix this with our policy system.
  734.   if (aLibrary.equals(LibraryUtils.webLibrary)) {
  735.     return false;
  736.   }
  737.  
  738.   var types = aLibrary.mediaListTypes;
  739.   while (types.hasMore()) {
  740.     if(aListType == types.getNext())  {
  741.       return true;
  742.     }
  743.   }
  744.   return false;
  745. }
  746.  
  747.  
  748. /**
  749.  * Hide this node and any nodes below it that belong to
  750.  * the library service pane service
  751.  */
  752. sbLibraryServicePane.prototype._hideLibraryNodes =
  753. function sbLibraryServicePane__hideLibraryNodes(aNode) {
  754.   //logcall(arguments);
  755.  
  756.   // If this is one of our nodes, hide it
  757.   if (aNode.contractid == CONTRACTID) {
  758.     aNode.hidden = true;
  759.   }
  760.  
  761.   // If this is a container, descend into all children
  762.   if (aNode.isContainer) {
  763.     var children = aNode.childNodes;
  764.     while (children.hasMoreElements()) {
  765.       var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  766.       this._hideLibraryNodes(child);
  767.     }
  768.   }
  769. }
  770.  
  771.  
  772. /**
  773.  * Add all registered libraries to the service pane
  774.  */
  775. sbLibraryServicePane.prototype._addAllLibraries =
  776. function sbLibraryServicePane__addAllLibraries() {
  777.   //logcall(arguments);
  778.   var libraries = this._libraryManager.getLibraries();
  779.   while (libraries.hasMoreElements()) {
  780.     var library = libraries.getNext();
  781.     this._libraryAdded(library);
  782.   }
  783. }
  784.  
  785. /**
  786.  * Remove all registered libraries from the service pane
  787.  */
  788. sbLibraryServicePane.prototype._removeAllLibraries =
  789. function sbLibraryServicePane__removeAllLibraries() {
  790.   //logcall(arguments);
  791.   var libraries = this._libraryManager.getLibraries();
  792.   while (libraries.hasMoreElements()) {
  793.     var library = libraries.getNext();
  794.     this._libraryRemoved(library);
  795.   }
  796. }
  797.  
  798. /**
  799. * Add all media lists found in the given library
  800.  */
  801. sbLibraryServicePane.prototype._processListsInLibrary =
  802. function sbLibraryServicePane__processListsInLibrary(aLibrary) {
  803.   //logcall(arguments);
  804.  
  805.   // Listener to receive enumerated items and store then in an array
  806.   var listener = {
  807.     items: [],
  808.     onEnumerationBegin: function() { return true; },
  809.     onEnumerationEnd: function() {return true; },
  810.     onEnumeratedItem: function(list, item) {
  811.       this.items.push(item);
  812.       return true;
  813.     }
  814.   };
  815.  
  816.   // Enumerate all lists in this library
  817.   aLibrary.enumerateItemsByProperty(PROP_ISLIST, "1",
  818.                                     listener );
  819.  
  820.   // Make sure we have a node for each list
  821.   for (var i = 0; i < listener.items.length; i++) {
  822.     this._ensureMediaListNodeExists(listener.items[i]);
  823.   }
  824. }
  825.  
  826.  
  827. /**
  828.  * The given library has been added.  Show it in the service pane.
  829.  */
  830. sbLibraryServicePane.prototype._libraryAdded =
  831. function sbLibraryServicePane__libraryAdded(aLibrary) {
  832.   //logcall(arguments);
  833.   var node = this._ensureLibraryNodeExists(aLibrary);
  834.  
  835.   // Listen to changes in the library so that we can display new playlists
  836.   var filter = SBProperties.createArray([[SBProperties.mediaListName, null]]);
  837.   aLibrary.addListener(this,
  838.                        false,
  839.                        Ci.sbIMediaList.LISTENER_FLAGS_ALL &
  840.                          ~Ci.sbIMediaList.LISTENER_FLAGS_AFTERITEMREMOVED,
  841.                        filter);
  842.  
  843.   this._processListsInLibrary(aLibrary);
  844.  
  845.   return node;
  846. }
  847.  
  848.  
  849. /**
  850.  * The given library has been removed.  Just hide the contents
  851.  * rather than deleting so that if it is ever reattached
  852.  * we will remember any ordering (drag-drop) information
  853.  */
  854. sbLibraryServicePane.prototype._libraryRemoved =
  855. function sbLibraryServicePane__libraryRemoved(aLibrary) {
  856.   //logcall(arguments);
  857.  
  858.   // Find the node for this library
  859.   var id = this._libraryURN(aLibrary);
  860.   var node = this._servicePane.getNode(id);
  861.  
  862.   // Hide this node and everything below it
  863.   if (node) {
  864.     this._hideLibraryNodes(node);
  865.   }
  866.  
  867.   aLibrary.removeListener(this);
  868. }
  869.  
  870. sbLibraryServicePane.prototype._refreshLibraryNodes =
  871. function sbLibraryServicePane__refreshLibraryNodes(aLibrary) {
  872.   var id = this._libraryURN(aLibrary);
  873.   var node = this._servicePane.getNode(id);
  874.   this._hideLibraryNodes(node);
  875.   this._ensureLibraryNodeExists(aLibrary);
  876.   this._processListsInLibrary(aLibrary);
  877. }
  878.  
  879. /**
  880.  * The given media list has been added. Show it in the service pane.
  881.  */
  882. sbLibraryServicePane.prototype._playlistAdded =
  883. function sbLibraryServicePane__playlistAdded(aMediaList) {
  884.   //logcall(arguments);
  885.   this._ensureMediaListNodeExists(aMediaList);
  886. }
  887.  
  888.  
  889. /**
  890.  * The given media list has been removed. Delete the node, as it
  891.  * is unlikely that the same playlist will come back again.
  892.  */
  893. sbLibraryServicePane.prototype._playlistRemoved =
  894. function sbLibraryServicePane__playlistRemoved(aMediaList) {
  895.   //logcall(arguments);
  896.  
  897.   var id = this._itemURN(aMediaList);
  898.   var node = this._servicePane.getNode(id);
  899.   if (node) {
  900.     this._servicePane.removeNode(node);
  901.   }
  902. }
  903.  
  904.  
  905. /**
  906.  * The given media list has been updated.
  907.  * The name and other properties may have changed.
  908.  */
  909. sbLibraryServicePane.prototype._mediaListUpdated =
  910. function sbLibraryServicePane__mediaListUpdated(aMediaList) {
  911.   //logcall(arguments);
  912.   if (aMediaList instanceof Ci.sbILibrary) {
  913.     this._ensureLibraryNodeExists(aMediaList);
  914.   } else if (aMediaList instanceof Ci.sbIMediaList) {
  915.     this._ensureMediaListNodeExists(aMediaList);
  916.   }
  917. }
  918.  
  919.  
  920. /**
  921.  * Get a service pane identifier for the given media item
  922.  */
  923. sbLibraryServicePane.prototype._itemURN =
  924. function sbLibraryServicePane__itemURN(aMediaItem) {
  925.   return URN_PREFIX_ITEM + aMediaItem.guid;
  926. }
  927.  
  928.  
  929. /**
  930.  * Get a service pane identifier for the given library
  931.  */
  932. sbLibraryServicePane.prototype._libraryURN =
  933. function sbLibraryServicePane__libraryURN(aLibrary) {
  934.   return URN_PREFIX_LIBRARY + aLibrary.guid;
  935. }
  936.  
  937.  
  938. /**
  939.  * Given a resource id, attempt to extract the GUID of a media item.
  940.  */
  941. sbLibraryServicePane.prototype._getItemGUIDForURN =
  942. function sbLibraryServicePane__getItemGUIDForURN(aID) {
  943.   //logcall(arguments);
  944.   var index = aID.indexOf(URN_PREFIX_ITEM);
  945.   if (index >= 0) {
  946.     return aID.slice(URN_PREFIX_ITEM.length);
  947.   }
  948.   return null;
  949. }
  950.  
  951.  
  952. /**
  953.  * Given a resource id, attempt to extract the GUID of a library.
  954.  */
  955. sbLibraryServicePane.prototype._getLibraryGUIDForURN =
  956. function sbLibraryServicePane__getLibraryGUIDForURN(aID) {
  957.   //logcall(arguments);
  958.   var index = aID.indexOf(URN_PREFIX_LIBRARY);
  959.   if (index >= 0) {
  960.     return aID.slice(URN_PREFIX_LIBRARY.length);
  961.   }
  962.   return null;
  963. }
  964.  
  965.  
  966. /**
  967.  * Given a resource id, attempt to get an sbIMediaItem.
  968.  * This is the inverse of _itemURN
  969.  */
  970. sbLibraryServicePane.prototype._getItemForURN =
  971. function sbLibraryServicePane__getItemForURN(aID) {
  972.   //logcall(arguments);
  973.   var guid = this._getItemGUIDForURN(aID);
  974.   if (guid) {
  975.     var node = this._servicePane.getNode(aID);
  976.     var libraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  977.     if (libraryGUID) {
  978.       try {
  979.         var library = this._libraryManager.getLibrary(libraryGUID);
  980.         return library.getMediaItem(guid);
  981.       } catch (e) {
  982.         dump("sbLibraryServicePane__getItemForURN: error trying to get medialist " +
  983.              guid + " from library " + libraryGUID + "\n");
  984.       }
  985.     }
  986.  
  987.     // URNs of visible nodes in the servicetree should always refer
  988.     // to an existing media item...
  989.     dump("sbLibraryServicePane__getItemForURN: could not find a mediaItem " +
  990.          "for URN " + aID + ". The service pane must be out of sync with " +
  991.          "the libraries!\n");
  992.   }
  993.   return null;
  994. }
  995.  
  996.  
  997. /**
  998.  * Given a resource id, attempt to get an sbILibrary.
  999.  * This is the inverse of _libraryURN
  1000.  */
  1001. sbLibraryServicePane.prototype._getLibraryForURN =
  1002. function sbLibraryServicePane__getLibraryForURN(aID) {
  1003.   //logcall(arguments);
  1004.   var guid = this._getLibraryGUIDForURN(aID);
  1005.   if (guid) {
  1006.     return this._libraryManager.getLibrary(guid);
  1007.   }
  1008.   return null;
  1009. }
  1010.  
  1011.  
  1012. /**
  1013.  * Make a URL for the given library resource.
  1014.  * Loading this URL should display the resource in a playlist.
  1015.  */
  1016. sbLibraryServicePane.prototype._getDisplayURL =
  1017. function sbLibraryServicePane__getDisplayURL(aResource) {
  1018.   //logcall(arguments);
  1019.  
  1020.   // Should really ask someone else... but for now just hardcode
  1021.   var url = URL_PLAYLIST_DISPLAY;
  1022.   if (aResource instanceof Ci.sbILibrary) {
  1023.     url += "library,"
  1024.   }
  1025.   url += aResource.guid;
  1026.   if (aResource instanceof Ci.sbIMediaList &&
  1027.       !(aResource instanceof Ci.sbILibrary))
  1028.   {
  1029.     url += "," + aResource.library.guid;
  1030.   }
  1031.   return url;
  1032. }
  1033.  
  1034.  
  1035.  
  1036. /**
  1037.  * Get the service pane node for the given library,
  1038.  * creating one if none exists.
  1039.  */
  1040. sbLibraryServicePane.prototype._ensureLibraryNodeExists =
  1041. function sbLibraryServicePane__ensureLibraryNodeExists(aLibrary) {
  1042.   //logcall(arguments);
  1043.  
  1044.   // Get the Node.
  1045.   var id = this._libraryURN(aLibrary);
  1046.   var node = this._servicePane.getNode(id);
  1047.   var newnode = false;
  1048.   if (!node) {
  1049.     // Create the node
  1050.     node = this._servicePane.addNode(id, this._servicePane.root, true);
  1051.     newnode = true;
  1052.   }
  1053.  
  1054.   var customType = aLibrary.getProperty(SBProperties.customType);
  1055.  
  1056.   // Refresh the information just in case it is supposed to change
  1057.   node.name = aLibrary.name;
  1058.   node.url = this._getDisplayURL(aLibrary);
  1059.   node.contractid = CONTRACTID;
  1060.   node.editable = false;
  1061.   node.hidden = aLibrary.getProperty(SBProperties.hidden) == "1";
  1062.  
  1063.   if (aLibrary == this._libraryManager.mainLibrary) {
  1064.     // the main library uses a separate Playlists folder
  1065.     this._ensurePlaylistFolderExists();
  1066.     // Set the weight of the main library
  1067.     node.setAttributeNS(SP, 'Weight', -4);
  1068.   } if (customType == 'web') {
  1069.     // Set the weight of the web library
  1070.     node.setAttributeNS(SP, 'Weight', 5);
  1071.   } else {
  1072.     // other libraries store the playlists under them
  1073.     node.dndAcceptIn = 'text/x-sb-playlist-'+aLibrary.guid;
  1074.   }
  1075.   // Set properties for styling purposes
  1076.   this._mergeProperties(node,
  1077.                         ["library",
  1078.                          "libraryguid-" + aLibrary.guid,
  1079.                          aLibrary.type,
  1080.                          customType]);
  1081.   // Save the type of media list so that we can group by type
  1082.   node.setAttributeNS(LSP, "ListType", aLibrary.type)
  1083.   // Save the guid of the library
  1084.   node.setAttributeNS(LSP, "LibraryGUID", aLibrary.guid);
  1085.   // and save it as the list guid
  1086.   node.setAttributeNS(LSP, "ListGUID", aLibrary.guid);
  1087.   // Save the customType for use by metrics.
  1088.   node.setAttributeNS(LSP, "ListCustomType", customType);
  1089.   // Save the customType for use by metrics.
  1090.   node.setAttributeNS(LSP, "LibraryCustomType", customType);
  1091.  
  1092.   if (newnode) {
  1093.     // Position the node in the tree
  1094.     this._insertLibraryNode(node, aLibrary);
  1095.   }
  1096.  
  1097.   return node;
  1098. }
  1099.  
  1100.  
  1101. /**
  1102.  * Get the service pane node for the given media list,
  1103.  * creating one if none exists.
  1104.  */
  1105. sbLibraryServicePane.prototype._ensureMediaListNodeExists =
  1106. function sbLibraryServicePane__ensureMediaListNodeExists(aMediaList) {
  1107.   //logcall(arguments);
  1108.  
  1109.   var id = this._itemURN(aMediaList);
  1110.   var node = this._servicePane.getNode(id);
  1111.   var newnode = false;
  1112.   if (!node) {
  1113.     // Create the node
  1114.     // NOTE: it's a container for drag and drop purposes only.
  1115.     node = this._servicePane.addNode(id, this._servicePane.root, true);
  1116.     newnode = true;
  1117.   }
  1118.  
  1119.   var customType = aMediaList.getProperty(SBProperties.customType);
  1120.   var libCustomType = aMediaList.library.getProperty(SBProperties.customType);
  1121.  
  1122.   // Refresh the information just in case it is supposed to change
  1123.   node.name = aMediaList.name;
  1124.   node.url = this._getDisplayURL(aMediaList);
  1125.   node.contractid = CONTRACTID;
  1126.   if (customType == 'download') {
  1127.     // the download media list isn't editable
  1128.     node.editable = false;
  1129.     // set the weight of the downloads list
  1130.     node.setAttributeNS(SP, 'Weight', -3);
  1131.   } else {
  1132.     // the rest are
  1133.     node.editable = true;
  1134.   }
  1135.   // Set properties for styling purposes
  1136.   if (aMediaList.getProperty("http://songbirdnest.com/data/1.0#isSubscription") == "1") {
  1137.     this._mergeProperties(node, ["medialist", "medialisttype-dynamic"]);
  1138.   } else {
  1139.     this._mergeProperties(node, ["medialist medialisttype-" + aMediaList.type]);
  1140.   }
  1141.   // Add the customType to the properties to encourage people to set it for CSS
  1142.   this._mergeProperties(node, [customType]);
  1143.   // Save the type of media list so that we can group by type
  1144.   node.setAttributeNS(LSP, "ListType", aMediaList.type);
  1145.   // Save the guid of the library that owns this media list
  1146.   node.setAttributeNS(LSP, "LibraryGUID", aMediaList.library.guid);
  1147.   // and the guid of this list
  1148.   node.setAttributeNS(LSP, "ListGUID", aMediaList.guid);
  1149.   // Save the parent library custom type for this list.
  1150.   node.setAttributeNS(LSP, "LibraryCustomType", libCustomType);
  1151.   // Save the list customType for use by metrics.
  1152.   node.setAttributeNS(LSP, "ListCustomType", customType);
  1153.  
  1154.   if (aMediaList.library == this._libraryManager.mainLibrary) {
  1155.     // a playlist in the main library is considered a toplevel node
  1156.     if (customType == 'download') {
  1157.       // unless its the download playlist
  1158.       node.dndDragTypes = '';
  1159.       node.dndAcceptNear = '';
  1160.     } else {
  1161.       node.dndDragTypes = 'text/x-sb-playlist';
  1162.       node.dndAcceptNear = 'text/x-sb-playlist';
  1163.     }
  1164.   } else {
  1165.     // playlists in other libraries can only go into their libraries' nodes
  1166.     node.dndDragTypes = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1167.     node.dndAcceptNear = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1168.   }
  1169.  
  1170.  
  1171.   if (newnode) {
  1172.     // Place the node in the tree
  1173.     this._insertMediaListNode(node, aMediaList);
  1174.   }
  1175.  
  1176.  
  1177.   // Get hidden state from list, since we hide all list nodes on startup
  1178.   node.hidden = aMediaList.getProperty(PROP_ISHIDDEN) == "1";
  1179.  
  1180.   return node;
  1181. }
  1182.  
  1183. /**
  1184.  * Get the service pane node for the Playlists folder (which contains all
  1185.  * the playlists in the main library).
  1186.  */
  1187. sbLibraryServicePane.prototype._ensurePlaylistFolderExists =
  1188. function sbLibraryServicePane__ensurePlaylistFolderExists() {
  1189.   var fnode = this._servicePane.getNode('SB:Playlists');
  1190.   if (!fnode) {
  1191.     // make sure it exists
  1192.     var fnode = this._servicePane.addNode('SB:Playlists',
  1193.         this._servicePane.root, true);
  1194.   }
  1195.   fnode.name = '&servicesource.playlists';
  1196.   this._mergeProperties(fnode, ["folder", "Playlists"]);
  1197.   fnode.hidden = false;
  1198.   fnode.contractid = CONTRACTID;
  1199.   fnode.dndAcceptIn = 'text/x-sb-playlist';
  1200.   fnode.editable = false;
  1201.   fnode.setAttributeNS(SP, 'Weight', 3);
  1202.   return fnode;
  1203. }
  1204.  
  1205.  
  1206. /**
  1207.  * Logic to determine where a library node should appear
  1208.  * in the service pane tree
  1209.  */
  1210. sbLibraryServicePane.prototype._insertLibraryNode =
  1211. function sbLibraryServicePane__insertLibraryNode(aNode, aLibrary) {
  1212.   //logcall(arguments);
  1213.  
  1214.   // If this is the main library it should go at
  1215.   // the top of the list
  1216.   if (aLibrary == this._libraryManager.mainLibrary) {
  1217.     if (this._servicePane.root.firstChild &&
  1218.         this._servicePane.root.firstChild.id != aNode.id)
  1219.     {
  1220.       this._servicePane.root.insertBefore(aNode,
  1221.               this._servicePane.root.firstChild);
  1222.     }
  1223.   // Otherwise, add above the first non-library item?
  1224.   } else {
  1225.     this._insertAfterLastOfSameType(aNode, this._servicePane.root);
  1226.   }
  1227. }
  1228.  
  1229.  
  1230. /**
  1231.  * Logic to determine where a media list node should appear
  1232.  * in the service pane tree
  1233.  */
  1234. sbLibraryServicePane.prototype._insertMediaListNode =
  1235. function sbLibraryServicePane__insertMediaListNode(aNode, aMediaList) {
  1236.   //logcall(arguments);
  1237.  
  1238.   // If it is a main library media list, it belongs in the "Playlists" folder
  1239.   if (aMediaList.library == this._libraryManager.mainLibrary)
  1240.   {
  1241.     // unless it's the download playlist
  1242.     if (aNode.getAttributeNS(LSP, 'ListCustomType') == 'download') {
  1243.       // FIXME: put it right after the library
  1244.       this._servicePane.root.appendChild(aNode);
  1245.     } else {
  1246.       var folder = this._ensurePlaylistFolderExists();
  1247.       folder.appendChild(aNode);
  1248.     }
  1249.   }
  1250.   // If it is a secondary library playlist, it should be
  1251.   // added as a child of that library
  1252.   else
  1253.   {
  1254.     // Find the parent libary in the tree
  1255.     var parentLibraryGUID = aMediaList.library.guid;
  1256.     var parentLibraryNode;
  1257.     var children = this._servicePane.root.childNodes;
  1258.     while (children.hasMoreElements()) {
  1259.       var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1260.       if (child.contractid == CONTRACTID &&
  1261.           this._getLibraryGUIDForURN(child.id) == parentLibraryGUID)
  1262.       {
  1263.         parentLibraryNode = child;
  1264.         break;
  1265.       }
  1266.     }
  1267.  
  1268.     // Insert after last of same type as child of
  1269.     // this library
  1270.     if (parentLibraryNode && parentLibraryNode.isContainer) {
  1271.       this._insertAfterLastOfSameType(aNode, parentLibraryNode);
  1272.     } else {
  1273.       dump("sbLibraryServicePane__insertMediaListNode: ");
  1274.       dump("could not add media list to parent library ");
  1275.       if (parentLibraryNode)
  1276.           dump(parentLibraryNode.name + "\n");
  1277.       else
  1278.           dump("\n");
  1279.       this._servicePane.root.appendChild(aNode);
  1280.     }
  1281.   }
  1282. }
  1283.  
  1284.  
  1285.  
  1286. /**
  1287.  * Inserts the given node under the given parent and attempts to keep
  1288.  * children grouped by type.  The node is inserted at the end of the list
  1289.  * or next to the last child of the same type as the given node.
  1290.  */
  1291. sbLibraryServicePane.prototype._insertAfterLastOfSameType =
  1292. function sbLibraryServicePane__insertAfterLastOfSameType(aNode, aParent) {
  1293.   //logcall(arguments);
  1294.  
  1295.   if (!aParent.isContainer) {
  1296.     dump("sbLibraryServicePane__insertAfterLastOfSameType: ");
  1297.     dump("cannot insert under non-container node.\n");
  1298.     return;
  1299.   }
  1300.   aParent.isOpen = true;
  1301.  
  1302.   // Insert after last of same type before hitting
  1303.   // non-library related items
  1304.   var children = aParent.childNodes;
  1305.   var nodeType = aNode.getAttributeNS(LSP, "ListType");
  1306.   var lastOfSameType;
  1307.   var inserted = false;
  1308.   while (children.hasMoreElements()) {
  1309.     var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1310.  
  1311.     // If this node belongs to the library service pane, and
  1312.     // is not the node we are trying to insert, then check
  1313.     // to see if this is an insertion point candidate
  1314.     if (child.contractid == CONTRACTID && child.id != aNode.id) {
  1315.  
  1316.       // Keep track of last node similar to this one, preferring
  1317.       // nodes that are exactly the same type as this one
  1318.       if (nodeType == child.getAttributeNS(LSP, "ListType") ||
  1319.           lastOfSameType == null ||
  1320.           (lastOfSameType != null &&
  1321.              lastOfSameType.getAttributeNS(LSP, "ListType") != nodeType))
  1322.       {
  1323.         lastOfSameType = child;
  1324.       }
  1325.     }
  1326.   } // end of while
  1327.  
  1328.   // Insert the new list after the last of the same type
  1329.   // or at the end of the list
  1330.   if (lastOfSameType && lastOfSameType.nextSibling) {
  1331.     if (lastOfSameType.nextSibling.id != aNode.id) {
  1332.       aParent.insertBefore(aNode, lastOfSameType.nextSibling);
  1333.     }
  1334.   } else {
  1335.     aParent.appendChild(aNode);
  1336.   }
  1337. }
  1338.  
  1339. sbLibraryServicePane.prototype._appendMenuItem =
  1340. function sbLibraryServicePane__appendMenuItem(aContextMenu, aLabel, aCallback) {
  1341.   var item = aContextMenu.ownerDocument.createElement("menuitem");
  1342.   item.setAttribute("label", aLabel);
  1343.   item.addEventListener("command", aCallback, false);
  1344.   aContextMenu.appendChild(item);
  1345. }
  1346.  
  1347. sbLibraryServicePane.prototype._appendCommands =
  1348. function sbLibraryServicePane__appendCommands(aContextMenu, aList, aParentWindow) {
  1349.   if (this._lastMenuitems && this._lastMenuitems.destroy) {
  1350.     var pnode = this._lastMenuitems.parentNode;
  1351.     this._lastMenuitems.destroy();
  1352.     this._lastMenuitems = null;
  1353.   }
  1354.   var itemBuilder = aContextMenu.ownerDocument.createElement("sb-commands-menuitems");
  1355.   itemBuilder.setAttribute("id", "playlist-commands");
  1356.   itemBuilder.setAttribute("commandtype", "medialist");
  1357.   itemBuilder.setAttribute("bind", aList.library.guid + ';' + aList.guid);
  1358.   aContextMenu.appendChild(itemBuilder);
  1359.   this._lastMenuitems = itemBuilder;
  1360. }
  1361.  
  1362. /**
  1363.  * This function is a recursive helper for onListCleared (below) that will
  1364.  * remove all the playlist nodes for a given library.
  1365.  */
  1366. sbLibraryServicePane.prototype._removeListNodesForLibrary =
  1367. function sbLibraryServicePane__removeListNodesForLibrary(aStartNode, aLibraryGUID) {
  1368.  
  1369.   var node = aStartNode.firstChild;
  1370.  
  1371.   while (node) {
  1372.  
  1373.     if (node.isContainer) {
  1374.       this._removeListNodesForLibrary(node, aLibraryGUID);
  1375.     }
  1376.  
  1377.     var nextSibling = node.nextSibling;
  1378.  
  1379.     if (this._getItemGUIDForURN(node.id)) {
  1380.       var nodeLibraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  1381.       if (nodeLibraryGUID == aLibraryGUID) {
  1382.         this._servicePane.removeNode(node);
  1383.       }
  1384.     }
  1385.  
  1386.     node = nextSibling;
  1387.   }
  1388. }
  1389.  
  1390. sbLibraryServicePane.prototype._mergeProperties =
  1391. function sbLibraryServicePane__mergeProperties(aNode, aList) {
  1392.  
  1393.   var o = {};
  1394.   var a = aNode.properties?aNode.properties.split(" "):[];
  1395.   a.forEach(function(prop) {
  1396.     o[prop] = 1;
  1397.   });
  1398.  
  1399.   aList.forEach(function(prop) {
  1400.     o[prop] = 1;
  1401.   });
  1402.  
  1403.   var retval = [];
  1404.   for (var prop in o) {
  1405.     retval.push(prop);
  1406.   }
  1407.   aNode.properties = retval.join(" ");
  1408. }
  1409.  
  1410. ///////////////////////////////
  1411. // sbILibraryManagerListener //
  1412. ///////////////////////////////
  1413.  
  1414. sbLibraryServicePane.prototype.onLibraryRegistered =
  1415. function sbLibraryServicePane_onLibraryRegistered(aLibrary) {
  1416.   //logcall(arguments);
  1417.   this._libraryAdded(aLibrary);
  1418. }
  1419. sbLibraryServicePane.prototype.onLibraryUnregistered =
  1420. function sbLibraryServicePane_onLibraryUnregistered(aLibrary) {
  1421.   //logcall(arguments);
  1422.   this._libraryRemoved(aLibrary);
  1423. }
  1424.  
  1425. //////////////////////////
  1426. // sbIMediaListListener //
  1427. //////////////////////////
  1428.  
  1429. sbLibraryServicePane.prototype.onItemAdded =
  1430. function sbLibraryServicePane_onItemAdded(aMediaList, aMediaItem) {
  1431.   //logcall(arguments);
  1432.   if (this._batch.isActive()) {
  1433.     // We are going to refresh all the nodes once we exit the batch so
  1434.     // we don't need any more of these notifications
  1435.     this._refreshPending = true;
  1436.     return true;
  1437.   }
  1438.   else {
  1439.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1440.     if (isList) {
  1441.       this._playlistAdded(aMediaItem);
  1442.     }
  1443.     return false;
  1444.   }
  1445. }
  1446. sbLibraryServicePane.prototype.onBeforeItemRemoved =
  1447. function sbLibraryServicePane_onBeforeItemRemoved(aMediaList, aMediaItem) {
  1448.   //logcall(arguments);
  1449.   if (this._batch.isActive()) {
  1450.     // We are going to refresh all the nodes once we exit the batch so
  1451.     // we don't need any more of these notifications
  1452.     this._refreshPending = true;
  1453.     return true;
  1454.   }
  1455.   else {
  1456.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1457.     if (isList) {
  1458.       this._playlistRemoved(aMediaItem);
  1459.     }
  1460.     return false;
  1461.   }
  1462. }
  1463. sbLibraryServicePane.prototype.onAfterItemRemoved =
  1464. function sbLibraryServicePane_onAfterItemRemoved(aMediaList, aMediaItem) {
  1465. }
  1466. sbLibraryServicePane.prototype.onItemUpdated =
  1467. function sbLibraryServicePane_onItemUpdated(aMediaList,
  1468.                                             aMediaItem,
  1469.                                             aProperties) {
  1470.   if (this._batch.isActive()) {
  1471.     // We are going to refresh all the nodes once we exit the batch so
  1472.     // we don't need any more of these notifications
  1473.     this._refreshPending = true;
  1474.     return true;
  1475.   }
  1476.   else {
  1477.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1478.     if (isList) {
  1479.       this._mediaListUpdated(aMediaItem);
  1480.     }
  1481.     return false;
  1482.   }
  1483. }
  1484. sbLibraryServicePane.prototype.onListCleared =
  1485. function sbLibraryServicePane_onListCleared(aMediaList) {
  1486.   if (this._batch.isActive()) {
  1487.     // We are going to refresh all the nodes once we exit the batch so
  1488.     // we don't need any more of these notifications
  1489.     this._refreshPending = true;
  1490.     return true;
  1491.   }
  1492.   else {
  1493.     if (aMediaList instanceof Ci.sbILibrary) {
  1494.       var libraryGUID = aMediaList.guid;
  1495.  
  1496.       var node = this._servicePane.root;
  1497.       this._removeListNodesForLibrary(node, libraryGUID);
  1498.     }
  1499.     return false;
  1500.   }
  1501. }
  1502. sbLibraryServicePane.prototype.onBatchBegin =
  1503. function sbLibraryServicePane_onBatchBegin(aMediaList) {
  1504.   //logcall(arguments);
  1505.   this._batch.begin();
  1506. }
  1507. sbLibraryServicePane.prototype.onBatchEnd =
  1508. function sbLibraryServicePane_onBatchEnd(aMediaList) {
  1509.   //logcall(arguments);
  1510.   this._batch.end();
  1511.   if (!this._batch.isActive() && this._refreshPending) {
  1512.     this._refreshLibraryNodes(aMediaList);
  1513.   }
  1514.  
  1515. }
  1516.  
  1517.  
  1518. sbLibraryServicePane.prototype._initLibraryManager =
  1519. function sbLibraryServicePane__initLibraryManager() {
  1520.   // get the library manager
  1521.   this._libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  1522.                            .getService(Ci.sbILibraryManager);
  1523.  
  1524.   // register for notifications so that we can keep the service pane
  1525.   // in sync with the the libraries
  1526.   this._libraryManager.addListener(this);
  1527.  
  1528.   this._addAllLibraries();
  1529. }
  1530. /////////////////
  1531. // nsIObserver //
  1532. /////////////////
  1533.  
  1534. sbLibraryServicePane.prototype.observe =
  1535. function sbLibraryServicePane_observe(subject, topic, data) {
  1536.  
  1537.   var obs = Cc["@mozilla.org/observer-service;1"]
  1538.               .getService(Ci.nsIObserverService);
  1539.  
  1540.   if (topic == "songbird-library-manager-ready") {
  1541.     obs.removeObserver(this, "songbird-library-manager-ready");
  1542.  
  1543.     if (!this._libraryManager) {
  1544.       this._initLibraryManager();
  1545.     }
  1546.   }
  1547.   else if (topic == "songbird-library-manager-before-shutdown") {
  1548.     obs.removeObserver(this, "songbird-library-manager-before-shutdown");
  1549.  
  1550.     var libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  1551.                            .getService(Ci.sbILibraryManager);
  1552.     libraryManager.removeListener(this);
  1553.   }
  1554. }
  1555.  
  1556. ///////////
  1557. // XPCOM //
  1558. ///////////
  1559.  
  1560. /**
  1561.  * /brief XPCOM initialization code
  1562.  */
  1563. function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
  1564.   return function (comMgr, fileSpec) {
  1565.     return {
  1566.       registerSelf : function (compMgr, fileSpec, location, type) {
  1567.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1568.         compMgr.registerFactoryLocation(CID,
  1569.                         CLASSNAME,
  1570.                         CONTRACTID,
  1571.                         fileSpec,
  1572.                         location,
  1573.                         type);
  1574.         if (CATEGORIES && CATEGORIES.length) {
  1575.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1576.               .getService(Ci.nsICategoryManager);
  1577.           for (var i=0; i<CATEGORIES.length; i++) {
  1578.             var e = CATEGORIES[i];
  1579.             catman.addCategoryEntry(e.category, e.entry, e.value,
  1580.               true, true);
  1581.           }
  1582.         }
  1583.       },
  1584.  
  1585.       getClassObject : function (compMgr, cid, iid) {
  1586.         if (!cid.equals(CID)) {
  1587.           throw Cr.NS_ERROR_NO_INTERFACE;
  1588.         }
  1589.  
  1590.         if (!iid.equals(Ci.nsIFactory)) {
  1591.           throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1592.         }
  1593.  
  1594.         return this._factory;
  1595.       },
  1596.  
  1597.       _factory : {
  1598.         createInstance : function (outer, iid) {
  1599.           if (outer != null) {
  1600.             throw Cr.NS_ERROR_NO_AGGREGATION;
  1601.           }
  1602.           return (new CONSTRUCTOR()).QueryInterface(iid);
  1603.         }
  1604.       },
  1605.  
  1606.       unregisterSelf : function (compMgr, location, type) {
  1607.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1608.         compMgr.unregisterFactoryLocation(CID, location);
  1609.         if (CATEGORIES && CATEGORIES.length) {
  1610.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1611.               .getService(Ci.nsICategoryManager);
  1612.           for (var i=0; i<CATEGORIES.length; i++) {
  1613.             var e = CATEGORIES[i];
  1614.             catman.deleteCategoryEntry(e.category, e.entry, true);
  1615.           }
  1616.         }
  1617.       },
  1618.  
  1619.       canUnload : function (compMgr) {
  1620.         return true;
  1621.       },
  1622.  
  1623.       QueryInterface : function (iid) {
  1624.         if ( !iid.equals(Ci.nsIModule) ||
  1625.              !iid.equals(Ci.nsISupports) )
  1626.           throw Cr.NS_ERROR_NO_INTERFACE;
  1627.         return this;
  1628.       }
  1629.  
  1630.     };
  1631.   }
  1632. }
  1633.  
  1634. var NSGetModule = makeGetModule (
  1635.   sbLibraryServicePane,
  1636.   Components.ID("{64ec2154-3733-4862-af3f-9f2335b14821}"),
  1637.   "Songbird Library Service Pane Service",
  1638.   CONTRACTID,
  1639.   [{
  1640.     category: 'service-pane',
  1641.     entry: '0library', // we want this to load first
  1642.     value: CONTRACTID
  1643.   }]);
  1644.