home *** CD-ROM | disk | FTP | other *** search
/ PC World 2007 December / PCWorld_2007-12_cd.bin / audio-video / songbird / Songbird_0.3_windows-i686.exe / components / sbLibraryServicePaneService.js < prev    next >
Text File  |  2007-10-27  |  53KB  |  1,640 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-2007 POTI, Inc.
  8. // http://songbirdnest.com
  9. //
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. //
  13. // Software distributed under the License is distributed
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
  15. // express or implied. See the GPL for the specific language
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc.,
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. //
  23. // END SONGBIRD GPL
  24. //
  25.  */
  26.  
  27. /**
  28.  * \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("http://songbirdnest.com/data/1.0#customType");
  472.     //Components.utils.reportError(fromtype + " - " + totype );
  473.     metrics.metricsAdd("app.servicepane.copy", fromtype, totype, context.count);
  474.     
  475.     var itemEnumerator = {
  476.       hasMoreElements: function() { return items.hasMoreElements(); },
  477.       getNext: function() {
  478.         var item = items.getNext().QueryInterface(Ci.sbIIndexedMediaItem);
  479.         return item.mediaItem;
  480.       },
  481.       QueryInterface: function(iid) {
  482.         if (iid.equals(Ci.nsISupports) ||
  483.             iid.equals(Ci.nsISimpleEnumerator)) {
  484.           return this;
  485.         }
  486.         throw Cr.NS_ERROR_NO_INTERFACE;
  487.       }
  488.     };
  489.  
  490.     if (targettype == "download") {
  491.       // XXX Mook: Special handling for dragging to download list, bug 4760
  492.       Cc["@songbirdnest.com/Songbird/DownloadDeviceHelper;1"]
  493.         .getService(Ci.sbIDownloadDeviceHelper)
  494.         .downloadSome(itemEnumerator);
  495.     } else {
  496.       // add all the items to the target list
  497.       targetList.beginUpdateBatch();
  498.       targetList.addSome(itemEnumerator);
  499.       targetList.endUpdateBatch();
  500.     }
  501.     
  502.     dump('added\n');
  503.  
  504.   } else if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEM)) {
  505.     dump('media item dropped\n');
  506.  
  507.     var context = this._getDndData(aDragSession,
  508.         TYPE_X_SB_TRANSFER_MEDIA_ITEM, Ci.sbISingleItemTransferContext);
  509.     
  510.     // add the item
  511.     if (targettype == "download") {
  512.       // XXX Mook: Special handling for dragging to download list, bug 4760
  513.       Cc["@songbirdnest.com/Songbird/DownloadDeviceHelper;1"]
  514.         .getService(Ci.sbIDownloadDeviceHelper)
  515.         .downloadItem(context.item);
  516.     } else {
  517.       targetList.add(context.item);
  518.     }
  519.  
  520.     // Metrics!
  521.     var fromtype = context.source.library.getProperty(SBProperties.customType);
  522.     metrics.metricsAdd("app.servicepane.copy", fromtype, totype, 1);
  523.     
  524.     dump('added\n');
  525.   }
  526. }
  527. sbLibraryServicePane.prototype._nodeIsLibrary =
  528. function sbLibraryServicePane__nodeIsLibrary(aNode) {
  529.   return aNode.getAttributeNS(LSP, "LibraryGUID") ==
  530.       aNode.getAttributeNS(LSP, "ListGUID");
  531. }
  532. sbLibraryServicePane.prototype.onDragGesture =
  533. function sbLibraryServicePane_onDragGesture(aNode, aTransferable) {
  534.   if (this._nodeIsLibrary(aNode)) {
  535.     // a library isn't dragable
  536.     return false;
  537.   }
  538.   
  539.   // get the list and create the source context
  540.   var list = this._getItemForURN(aNode.id);
  541.   var context = {
  542.     source: list.library,
  543.     count: 1,
  544.     list: list,
  545.     QueryInterface: function(iid) {
  546.       if (iid.equals(Components.interfaces.sbISingleListTransferContext) ||
  547.           iid.equals(Components.interfaces.nsISupports)) {
  548.         return this;
  549.       }
  550.       throw Components.results.NS_NOINTERFACE;
  551.     }
  552.   }
  553.  
  554.   // register the source context
  555.   var dnd = Components.classes['@songbirdnest.com/Songbird/DndSourceTracker;1']
  556.       .getService(Components.interfaces.sbIDndSourceTracker);
  557.   dnd.reset();  
  558.   var handle = dnd.registerSource(context);
  559.   
  560.   // attach the source context to the transferable
  561.   aTransferable.addDataFlavor(TYPE_X_SB_TRANSFER_MEDIA_LIST);
  562.   var text = Components.classes["@mozilla.org/supports-string;1"].
  563.      createInstance(Components.interfaces.nsISupportsString);
  564.   text.data = handle;
  565.   aTransferable.setTransferData(TYPE_X_SB_TRANSFER_MEDIA_LIST, text,
  566.                                 text.data.length*2);
  567.   
  568.   return true;
  569. }
  570.  
  571.  
  572. /**
  573.  * Called when the user has attempted to rename a library/medialist node
  574.  */
  575. sbLibraryServicePane.prototype.onRename =
  576. function sbLibraryServicePane_onRename(aNode, aNewName) {
  577.   //logcall(arguments);
  578.   if (aNode && aNewName) {
  579.     var libraryResource = this.getLibraryResourceForNode(aNode);
  580.     libraryResource.name = aNewName;
  581.   }
  582. }
  583.  
  584.  
  585. //////////////////////////////////
  586. // sbILibraryServicePaneService //
  587. //////////////////////////////////
  588.  
  589.  
  590. /* \brief Suggest a library for creating a new media list
  591.  *
  592.  * \param aMediaListType string identifying a media list type, eg "simple"
  593.  * \param aNode A service pane node to provide context for new list creation
  594.  * \return a library, or null if this service can't suggest anything based on 
  595.  *         the given context and type.
  596.  */
  597. sbLibraryServicePane.prototype.suggestLibraryForNewList =
  598. function sbLibraryServicePane_suggestLibraryForNewList(aMediaListType, aNode) {
  599.   //logcall(arguments);
  600.   
  601.   // Must provide a media list type
  602.   if (!aMediaListType) {
  603.     throw Components.results.NS_ERROR_INVALID_ARG;
  604.   }
  605.   
  606.   // Make sure we are fully initialized
  607.   if (!this._libraryManager || !this._servicePane) {
  608.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  609.   }
  610.   
  611.   // Move up the tree looking for libraries that support the
  612.   // given media list type.
  613.   while (aNode && aNode != this._servicePane.root) {
  614.   
  615.     // If this node is visible and belongs to the library 
  616.     // service pane service...
  617.     if (aNode.contractid == CONTRACTID && !aNode.hidden) {
  618.       // If this is a playlist and the playlist belongs
  619.       // to a library that supports the given type, 
  620.       // then suggest that library
  621.       var mediaItem = this._getItemForURN(aNode.id);
  622.       if (mediaItem && mediaItem instanceof Ci.sbIMediaList &&
  623.           this._doesLibrarySupportListType(mediaItem.library, aMediaListType))
  624.       {
  625.         return mediaItem.library;
  626.       }
  627.       
  628.       // If this is a library that supports the given type, 
  629.       // then suggest the library
  630.       var library = this._getLibraryForURN(aNode.id);
  631.       if (library && library instanceof Ci.sbILibrary &&
  632.           this._doesLibrarySupportListType(library, aMediaListType)) 
  633.       {
  634.         return library;
  635.       }
  636.     }
  637.  
  638.     // Move up the tree
  639.     aNode = aNode.parentNode;
  640.   } // end of while
  641.  
  642.   // If the main library supports the given type, then return that
  643.   if (this._doesLibrarySupportListType(this._libraryManager.mainLibrary, 
  644.                                        aMediaListType)) 
  645.   {
  646.     return this._libraryManager.mainLibrary;
  647.   }
  648.   
  649.   // Oh well, out of luck
  650.   return null;
  651. }
  652.  
  653.  
  654. /* \brief Attempt to get a service pane node for the given library resource
  655.  *         
  656.  * \param aResource an sbIMediaItem, sbIMediaItem, or sbILibrary
  657.  * \return a service pane node that represents the given resource, if one exists
  658.  */
  659. sbLibraryServicePane.prototype.getNodeForLibraryResource =
  660. function sbLibraryServicePane_getNodeForLibraryResource(aResource) {
  661.   //logcall(arguments);
  662.   
  663.   // Must be initialized
  664.   if (!this._libraryManager || !this._servicePane) {
  665.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  666.   }  
  667.   
  668.   var node;
  669.   
  670.   // If this is a library, make a library node
  671.   if (aResource instanceof Ci.sbILibrary) {
  672.     node = this._servicePane.getNode(this._libraryURN(aResource));
  673.   
  674.   // If this is a mediaitem, make a mediaitem node
  675.   } else if (aResource instanceof Ci.sbIMediaItem) {
  676.     node = this._servicePane.getNode(this._itemURN(aResource));
  677.   
  678.   // Else we don't know what to do, so 
  679.   // the arg must be invalid
  680.   } else {
  681.     throw Components.results.NS_ERROR_INVALID_ARG;
  682.   }
  683.   
  684.   return node;  
  685. }
  686.  
  687.  
  688. /* \brief Attempt to get a library resource for the given service pane node.
  689.  *
  690.  * Note that there is no guarantee that hidden service pane nodes
  691.  * will have corresponding library resources
  692.  *
  693.  * \param aNode 
  694.  * \return a sbIMediaItem, sbIMediaItem, sbILibrary, or null
  695.  */
  696. sbLibraryServicePane.prototype.getLibraryResourceForNode =
  697. function sbLibraryServicePane_getLibraryResourceForNode(aNode) {
  698.   //logcall(arguments);
  699.   
  700.   // Must provide a node
  701.   if (!(aNode instanceof Ci.sbIServicePaneNode)) {
  702.     throw Components.results.NS_ERROR_INVALID_ARG;
  703.   }  
  704.   // Must be initialized
  705.   if (!this._libraryManager || !this._servicePane) {
  706.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  707.   }
  708.   
  709.   // If the node does not belong to us, then we aren't
  710.   // going to find a resource
  711.   if (aNode.contractid != CONTRACTID) {
  712.     return null;
  713.   }
  714.   
  715.   // Attempt to get a resource from the id of the given node
  716.   var resource = this._getItemForURN(aNode.id);
  717.   if (!resource) {
  718.     resource = this._getLibraryForURN(aNode.id);
  719.   }
  720.   
  721.   return resource;
  722. }
  723.  
  724.  
  725. /////////////////////
  726. // Private Methods //
  727. /////////////////////
  728.  
  729.  
  730. /**
  731.  * Return true if the given library supports the given list type
  732.  */
  733. sbLibraryServicePane.prototype._doesLibrarySupportListType =
  734. function sbLibraryServicePane__doesLibrarySupportListType(aLibrary, aListType) {
  735.   //logcall(arguments);
  736.   var types = aLibrary.mediaListTypes;
  737.   while (types.hasMore()) {
  738.     if(aListType == types.getNext())  {
  739.       return true;
  740.     }
  741.   }
  742.   return false;
  743. }
  744.  
  745.  
  746. /**
  747.  * Hide this node and any nodes below it that belong to
  748.  * the library service pane service
  749.  */
  750. sbLibraryServicePane.prototype._hideLibraryNodes =
  751. function sbLibraryServicePane__hideLibraryNodes(aNode) {
  752.   //logcall(arguments);
  753.   
  754.   // If this is one of our nodes, hide it
  755.   if (aNode.contractid == CONTRACTID) {
  756.     aNode.hidden = true;
  757.   }
  758.  
  759.   // If this is a container, descend into all children  
  760.   if (aNode.isContainer) {
  761.     var children = aNode.childNodes;
  762.     while (children.hasMoreElements()) {
  763.       var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  764.       this._hideLibraryNodes(child);
  765.     }
  766.   }
  767. }
  768.  
  769.  
  770. /**
  771.  * Add all registered libraries to the service pane
  772.  */
  773. sbLibraryServicePane.prototype._addAllLibraries =
  774. function sbLibraryServicePane__addAllLibraries() {
  775.   //logcall(arguments);
  776.   var libraries = this._libraryManager.getLibraries();
  777.   while (libraries.hasMoreElements()) {
  778.     var library = libraries.getNext();
  779.     this._libraryAdded(library);
  780.   }
  781. }
  782.  
  783. /**
  784.  * Remove all registered libraries from the service pane
  785.  */
  786. sbLibraryServicePane.prototype._removeAllLibraries =
  787. function sbLibraryServicePane__removeAllLibraries() {
  788.   //logcall(arguments);
  789.   var libraries = this._libraryManager.getLibraries();
  790.   while (libraries.hasMoreElements()) {
  791.     var library = libraries.getNext();
  792.     this._libraryRemoved(library);
  793.   }
  794. }
  795.  
  796. /**
  797. * Add all media lists found in the given library
  798.  */
  799. sbLibraryServicePane.prototype._processListsInLibrary =
  800. function sbLibraryServicePane__processListsInLibrary(aLibrary) {
  801.   //logcall(arguments);
  802.  
  803.   // Listener to receive enumerated items and store then in an array
  804.   var listener = {
  805.     items: [],
  806.     onEnumerationBegin: function() { return true; },
  807.     onEnumerationEnd: function() {return true; },
  808.     onEnumeratedItem: function(list, item) {
  809.       this.items.push(item);
  810.       return true;
  811.     }
  812.   };
  813.  
  814.   // Enumerate all lists in this library
  815.   aLibrary.enumerateItemsByProperty(PROP_ISLIST, "1",
  816.                                    listener,
  817.                                    Ci.sbIMediaList.ENUMERATIONTYPE_LOCKING);
  818.  
  819.   // Make sure we have a node for each list
  820.   for (var i = 0; i < listener.items.length; i++) {
  821.     this._ensureMediaListNodeExists(listener.items[i]);  
  822.   }
  823. }
  824.  
  825.  
  826. /**
  827.  * The given library has been added.  Show it in the service pane.
  828.  */
  829. sbLibraryServicePane.prototype._libraryAdded =
  830. function sbLibraryServicePane__libraryAdded(aLibrary) {
  831.   //logcall(arguments);  
  832.   this._ensureLibraryNodeExists(aLibrary);
  833.  
  834.   // Listen to changes in the library so that we can display new playlists
  835.   var filter = SBProperties.createArray([[SBProperties.mediaListName, null]]);
  836.   aLibrary.addListener(this,
  837.                        false,
  838.                        Ci.sbIMediaList.LISTENER_FLAGS_ALL &
  839.                          ~Ci.sbIMediaList.LISTENER_FLAGS_AFTERITEMREMOVED,
  840.                        filter);
  841.  
  842.   this._processListsInLibrary(aLibrary);
  843. }
  844.  
  845.  
  846. /**
  847.  * The given library has been removed.  Just hide the contents
  848.  * rather than deleting so that if it is ever reattached
  849.  * we will remember any ordering (drag-drop) information
  850.  */
  851. sbLibraryServicePane.prototype._libraryRemoved =
  852. function sbLibraryServicePane__libraryRemoved(aLibrary) {
  853.   //logcall(arguments);
  854.   
  855.   // Find the node for this library
  856.   var id = this._libraryURN(aLibrary);
  857.   var node = this._servicePane.getNode(id);
  858.   
  859.   // Hide this node and everything below it
  860.   if (node) {
  861.     this._hideLibraryNodes(node);
  862.   }
  863.  
  864.   aLibrary.removeListener(this);
  865. }
  866.  
  867. sbLibraryServicePane.prototype._refreshLibraryNodes =
  868. function sbLibraryServicePane__refreshLibraryNodes(aLibrary) {
  869.   var id = this._libraryURN(aLibrary);
  870.   var node = this._servicePane.getNode(id);
  871.   this._hideLibraryNodes(node);
  872.   this._ensureLibraryNodeExists(aLibrary);
  873.   this._processListsInLibrary(aLibrary);
  874. }
  875.  
  876. /** 
  877.  * The given media list has been added. Show it in the service pane.
  878.  */
  879. sbLibraryServicePane.prototype._playlistAdded =
  880. function sbLibraryServicePane__playlistAdded(aMediaList) {
  881.   //logcall(arguments);
  882.   this._ensureMediaListNodeExists(aMediaList);
  883. }
  884.  
  885.  
  886. /** 
  887.  * The given media list has been removed. Delete the node, as it 
  888.  * is unlikely that the same playlist will come back again.
  889.  */
  890. sbLibraryServicePane.prototype._playlistRemoved =
  891. function sbLibraryServicePane__playlistRemoved(aMediaList) {
  892.   //logcall(arguments);
  893.   
  894.   var id = this._itemURN(aMediaList);
  895.   var node = this._servicePane.getNode(id);
  896.   if (node) {
  897.     this._servicePane.removeNode(node);
  898.   }
  899. }
  900.  
  901.  
  902. /** 
  903.  * The given media list has been updated. 
  904.  * The name and other properties may have changed. 
  905.  */
  906. sbLibraryServicePane.prototype._mediaListUpdated =
  907. function sbLibraryServicePane__mediaListUpdated(aMediaList) {
  908.   //logcall(arguments);
  909.   if (aMediaList instanceof Ci.sbILibrary) {
  910.     this._ensureLibraryNodeExists(aMediaList);
  911.   } else if (aMediaList instanceof Ci.sbIMediaList) {
  912.     this._ensureMediaListNodeExists(aMediaList);
  913.   }
  914. }
  915.  
  916.  
  917. /**
  918.  * Get a service pane identifier for the given media item
  919.  */
  920. sbLibraryServicePane.prototype._itemURN =
  921. function sbLibraryServicePane__itemURN(aMediaItem) {
  922.   return URN_PREFIX_ITEM + aMediaItem.guid;
  923. }
  924.  
  925.  
  926. /**
  927.  * Get a service pane identifier for the given library
  928.  */
  929. sbLibraryServicePane.prototype._libraryURN =
  930. function sbLibraryServicePane__libraryURN(aLibrary) {
  931.   return URN_PREFIX_LIBRARY + aLibrary.guid;
  932. }
  933.  
  934.  
  935. /**
  936.  * Given a resource id, attempt to extract the GUID of a media item.
  937.  */
  938. sbLibraryServicePane.prototype._getItemGUIDForURN =
  939. function sbLibraryServicePane__getItemGUIDForURN(aID) {
  940.   //logcall(arguments);
  941.   var index = aID.indexOf(URN_PREFIX_ITEM);
  942.   if (index >= 0) {
  943.     return aID.slice(URN_PREFIX_ITEM.length);
  944.   }
  945.   return null;
  946. }
  947.  
  948.  
  949. /**
  950.  * Given a resource id, attempt to extract the GUID of a library.
  951.  */
  952. sbLibraryServicePane.prototype._getLibraryGUIDForURN =
  953. function sbLibraryServicePane__getLibraryGUIDForURN(aID) {
  954.   //logcall(arguments);
  955.   var index = aID.indexOf(URN_PREFIX_LIBRARY);
  956.   if (index >= 0) {
  957.     return aID.slice(URN_PREFIX_LIBRARY.length);
  958.   }
  959.   return null;
  960. }
  961.  
  962.  
  963. /**
  964.  * Given a resource id, attempt to get an sbIMediaItem.
  965.  * This is the inverse of _itemURN
  966.  */
  967. sbLibraryServicePane.prototype._getItemForURN =
  968. function sbLibraryServicePane__getItemForURN(aID) {
  969.   //logcall(arguments);
  970.   var guid = this._getItemGUIDForURN(aID);
  971.   if (guid) {
  972.     var node = this._servicePane.getNode(aID);
  973.     var libraryGUID = node.getAttributeNS(LSP, "LibraryGUID");      
  974.     if (libraryGUID) {
  975.       try {
  976.         var library = this._libraryManager.getLibrary(libraryGUID);
  977.         return library.getMediaItem(guid);
  978.       } catch (e) {
  979.         dump("sbLibraryServicePane__getItemForURN: error trying to get medialist " +
  980.              guid + " from library " + libraryGUID + "\n");
  981.       }
  982.     }
  983.     
  984.     // URNs of visible nodes in the servicetree should always refer
  985.     // to an existing media item...
  986.     dump("sbLibraryServicePane__getItemForURN: could not find a mediaItem " +
  987.          "for URN " + aID + ". The service pane must be out of sync with " + 
  988.          "the libraries!\n");
  989.   }
  990.   return null;
  991. }
  992.  
  993.  
  994. /**
  995.  * Given a resource id, attempt to get an sbILibrary.
  996.  * This is the inverse of _libraryURN
  997.  */
  998. sbLibraryServicePane.prototype._getLibraryForURN =
  999. function sbLibraryServicePane__getLibraryForURN(aID) {
  1000.   //logcall(arguments);
  1001.   var guid = this._getLibraryGUIDForURN(aID);
  1002.   if (guid) {
  1003.     return this._libraryManager.getLibrary(guid);
  1004.   }
  1005.   return null;
  1006. }
  1007.  
  1008.  
  1009. /**
  1010.  * Make a URL for the given library resource.  
  1011.  * Loading this URL should display the resource in a playlist.
  1012.  */
  1013. sbLibraryServicePane.prototype._getDisplayURL =
  1014. function sbLibraryServicePane__getDisplayURL(aResource) {
  1015.   //logcall(arguments);
  1016.   
  1017.   // Should really ask someone else... but for now just hardcode
  1018.   var url = URL_PLAYLIST_DISPLAY;
  1019.   if (aResource instanceof Ci.sbILibrary) {
  1020.     url += "library,"
  1021.   }
  1022.   url += aResource.guid;
  1023.   if (aResource instanceof Ci.sbIMediaList && 
  1024.       !(aResource instanceof Ci.sbILibrary))
  1025.   {
  1026.     url += "," + aResource.library.guid;
  1027.   }
  1028.   return url;
  1029. }
  1030.  
  1031.  
  1032.  
  1033. /**
  1034.  * Get the service pane node for the given library,
  1035.  * creating one if none exists.
  1036.  */
  1037. sbLibraryServicePane.prototype._ensureLibraryNodeExists =
  1038. function sbLibraryServicePane__ensureLibraryNodeExists(aLibrary) {
  1039.   //logcall(arguments);
  1040.  
  1041.   // Get the Node.
  1042.   var id = this._libraryURN(aLibrary);
  1043.   var node = this._servicePane.getNode(id);
  1044.   var newnode = false;
  1045.   if (!node) {
  1046.     // Create the node
  1047.     node = this._servicePane.addNode(id, this._servicePane.root, true);
  1048.     newnode = true;
  1049.   }
  1050.   
  1051.   var customType = aLibrary.getProperty(SBProperties.customType);
  1052.   
  1053.   // Refresh the information just in case it is supposed to change
  1054.   node.name = aLibrary.name;  
  1055.   node.url = this._getDisplayURL(aLibrary);
  1056.   node.contractid = CONTRACTID;
  1057.   node.editable = false;
  1058.   node.hidden = aLibrary.getProperty(SBProperties.hidden) == "1";
  1059.  
  1060.   if (aLibrary == this._libraryManager.mainLibrary) {
  1061.     // the main library uses a separate Playlists folder
  1062.     this._ensurePlaylistFolderExists();
  1063.     // Set the weight of the main library
  1064.     node.setAttributeNS(SP, 'Weight', -4);
  1065.   } if (customType == 'web') {
  1066.     // Set the weight of the web library
  1067.     node.setAttributeNS(SP, 'Weight', 5);
  1068.   } else {
  1069.     // other libraries store the playlists under them
  1070.     node.dndAcceptIn = 'text/x-sb-playlist-'+aLibrary.guid;
  1071.   }
  1072.   // Set properties for styling purposes
  1073.   this._mergeProperties(node,
  1074.                         ["library",
  1075.                          "libraryguid-" + aLibrary.guid,
  1076.                          aLibrary.type,
  1077.                          customType]);
  1078.   // Save the type of media list so that we can group by type
  1079.   node.setAttributeNS(LSP, "ListType", aLibrary.type)
  1080.   // Save the guid of the library
  1081.   node.setAttributeNS(LSP, "LibraryGUID", aLibrary.guid);
  1082.   // and save it as the list guid
  1083.   node.setAttributeNS(LSP, "ListGUID", aLibrary.guid);
  1084.   // Save the customType for use by metrics.
  1085.   node.setAttributeNS(LSP, "ListCustomType", customType);
  1086.   // Save the customType for use by metrics.
  1087.   node.setAttributeNS(LSP, "LibraryCustomType", customType);
  1088.  
  1089.   if (newnode) {
  1090.     // Position the node in the tree
  1091.     this._insertLibraryNode(node, aLibrary);
  1092.   }
  1093.     
  1094.   return node;
  1095. }
  1096.  
  1097.  
  1098. /**
  1099.  * Get the service pane node for the given media list,
  1100.  * creating one if none exists.
  1101.  */ 
  1102. sbLibraryServicePane.prototype._ensureMediaListNodeExists =
  1103. function sbLibraryServicePane__ensureMediaListNodeExists(aMediaList) {
  1104.   //logcall(arguments);
  1105.  
  1106.   var id = this._itemURN(aMediaList);
  1107.   var node = this._servicePane.getNode(id);
  1108.   var newnode = false;
  1109.   if (!node) {
  1110.     // Create the node
  1111.     // NOTE: it's a container for drag and drop purposes only.
  1112.     node = this._servicePane.addNode(id, this._servicePane.root, true);
  1113.     newnode = true;
  1114.   }
  1115.   
  1116.   var customType = aMediaList.getProperty(SBProperties.customType);
  1117.   var libCustomType = aMediaList.library.getProperty(SBProperties.customType);
  1118.   
  1119.   // Refresh the information just in case it is supposed to change
  1120.   node.name = aMediaList.name;  
  1121.   node.url = this._getDisplayURL(aMediaList);
  1122.   node.contractid = CONTRACTID;
  1123.   if (customType == 'download') {
  1124.     // the download media list isn't editable
  1125.     node.editable = false;
  1126.     // set the weight of the downloads list
  1127.     node.setAttributeNS(SP, 'Weight', -3);
  1128.   } else {
  1129.     // the rest are
  1130.     node.editable = true;
  1131.   }
  1132.   // Set properties for styling purposes
  1133.   if (aMediaList.getProperty("http://songbirdnest.com/data/1.0#isSubscription") == "1") {
  1134.     this._mergeProperties(node, ["medialist", "medialisttype-dynamic"]);
  1135.   } else {
  1136.     this._mergeProperties(node, ["medialist medialisttype-" + aMediaList.type]);
  1137.   }
  1138.   // Add the customType to the properties to encourage people to set it for CSS
  1139.   this._mergeProperties(node, [customType]);
  1140.   // Save the type of media list so that we can group by type
  1141.   node.setAttributeNS(LSP, "ListType", aMediaList.type);
  1142.   // Save the guid of the library that owns this media list
  1143.   node.setAttributeNS(LSP, "LibraryGUID", aMediaList.library.guid);
  1144.   // and the guid of this list
  1145.   node.setAttributeNS(LSP, "ListGUID", aMediaList.guid);
  1146.   // Save the parent library custom type for this list.
  1147.   node.setAttributeNS(LSP, "LibraryCustomType", libCustomType);
  1148.   // Save the list customType for use by metrics.
  1149.   node.setAttributeNS(LSP, "ListCustomType", customType);
  1150.  
  1151.   if (aMediaList.library == this._libraryManager.mainLibrary) {
  1152.     // a playlist in the main library is considered a toplevel node
  1153.     if (customType == 'download') {
  1154.       // unless its the download playlist
  1155.       node.dndDragTypes = '';
  1156.       node.dndAcceptNear = '';
  1157.     } else {
  1158.       node.dndDragTypes = 'text/x-sb-playlist';
  1159.       node.dndAcceptNear = 'text/x-sb-playlist';
  1160.     }
  1161.   } else {
  1162.     // playlists in other libraries can only go into their libraries' nodes
  1163.     node.dndDragTypes = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1164.     node.dndAcceptNear = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1165.   }
  1166.  
  1167.  
  1168.   if (newnode) {
  1169.     // Place the node in the tree
  1170.     this._insertMediaListNode(node, aMediaList);
  1171.   }
  1172.   
  1173.   
  1174.   // Get hidden state from list, since we hide all list nodes on startup
  1175.   node.hidden = aMediaList.getProperty(PROP_ISHIDDEN) == "1";
  1176.       
  1177.   return node;
  1178. }
  1179.  
  1180. /**
  1181.  * Get the service pane node for the Playlists folder (which contains all
  1182.  * the playlists in the main library).
  1183.  */
  1184. sbLibraryServicePane.prototype._ensurePlaylistFolderExists =
  1185. function sbLibraryServicePane__ensurePlaylistFolderExists() {
  1186.   var fnode = this._servicePane.getNode('SB:Playlists');
  1187.   if (!fnode) {
  1188.     // make sure it exists
  1189.     var fnode = this._servicePane.addNode('SB:Playlists', 
  1190.         this._servicePane.root, true);
  1191.   }
  1192.   fnode.name = '&servicesource.playlists';
  1193.   this._mergeProperties(fnode, ["folder", "Playlists"]);
  1194.   fnode.hidden = false;
  1195.   fnode.contractid = CONTRACTID;
  1196.   fnode.dndAcceptIn = 'text/x-sb-playlist';
  1197.   fnode.editable = false;
  1198.   fnode.setAttributeNS(SP, 'Weight', 3);
  1199.   return fnode;
  1200. }
  1201.  
  1202.  
  1203. /**
  1204.  * Logic to determine where a library node should appear
  1205.  * in the service pane tree
  1206.  */ 
  1207. sbLibraryServicePane.prototype._insertLibraryNode =
  1208. function sbLibraryServicePane__insertLibraryNode(aNode, aLibrary) {
  1209.   //logcall(arguments);
  1210.   
  1211.   // If this is the main library it should go at
  1212.   // the top of the list
  1213.   if (aLibrary == this._libraryManager.mainLibrary) {
  1214.     if (this._servicePane.root.firstChild && 
  1215.         this._servicePane.root.firstChild.id != aNode.id) 
  1216.     {
  1217.       this._servicePane.root.insertBefore(aNode, 
  1218.               this._servicePane.root.firstChild);    
  1219.     }
  1220.   // Otherwise, add above the first non-library item?
  1221.   } else {
  1222.     this._insertAfterLastOfSameType(aNode, this._servicePane.root);
  1223.   }
  1224. }
  1225.  
  1226.  
  1227. /**
  1228.  * Logic to determine where a media list node should appear
  1229.  * in the service pane tree
  1230.  */ 
  1231. sbLibraryServicePane.prototype._insertMediaListNode =
  1232. function sbLibraryServicePane__insertMediaListNode(aNode, aMediaList) {
  1233.   //logcall(arguments);
  1234.  
  1235.   // If it is a main library media list, it belongs in the "Playlists" folder
  1236.   if (aMediaList.library == this._libraryManager.mainLibrary) 
  1237.   {
  1238.     // unless it's the download playlist
  1239.     if (aNode.getAttributeNS(LSP, 'ListCustomType') == 'download') {
  1240.       // FIXME: put it right after the library
  1241.       this._servicePane.root.appendChild(aNode);
  1242.     } else {
  1243.       var folder = this._ensurePlaylistFolderExists();
  1244.       folder.appendChild(aNode);
  1245.     }
  1246.   } 
  1247.   // If it is a secondary library playlist, it should be
  1248.   // added as a child of that library
  1249.   else 
  1250.   {
  1251.     // Find the parent libary in the tree
  1252.     var parentLibraryGUID = aMediaList.library.guid;
  1253.     var parentLibraryNode;
  1254.     var children = this._servicePane.root.childNodes;
  1255.     while (children.hasMoreElements()) {
  1256.       var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1257.       if (child.contractid == CONTRACTID && 
  1258.           this._getLibraryGUIDForURN(child.id) == parentLibraryGUID)
  1259.       {
  1260.         parentLibraryNode = child;
  1261.         break;
  1262.       }
  1263.     }
  1264.     
  1265.     // Insert after last of same type as child of
  1266.     // this library
  1267.     if (parentLibraryNode && parentLibraryNode.isContainer) {
  1268.       this._insertAfterLastOfSameType(aNode, parentLibraryNode);
  1269.     } else {
  1270.       dump("sbLibraryServicePane__insertMediaListNode: ");
  1271.       dump("could not add media list to parent library ");
  1272.       dump(parentLibraryNode.name + "\n");
  1273.       this._servicePane.root.appendChild(aNode);
  1274.     }
  1275.   }
  1276. }
  1277.  
  1278.  
  1279.  
  1280. /**
  1281.  * Inserts the given node under the given parent and attempts to keep
  1282.  * children grouped by type.  The node is inserted at the end of the list
  1283.  * or next to the last child of the same type as the given node.
  1284.  */ 
  1285. sbLibraryServicePane.prototype._insertAfterLastOfSameType =
  1286. function sbLibraryServicePane__insertAfterLastOfSameType(aNode, aParent) {
  1287.   //logcall(arguments);
  1288.     
  1289.   if (!aParent.isContainer) {
  1290.     dump("sbLibraryServicePane__insertAfterLastOfSameType: ");
  1291.     dump("cannot insert under non-container node.\n");
  1292.     return;
  1293.   }      
  1294.   aParent.isOpen = true;
  1295.   
  1296.   // Insert after last of same type before hitting 
  1297.   // non-library related items
  1298.   var children = aParent.childNodes;
  1299.   var nodeType = aNode.getAttributeNS(LSP, "ListType");
  1300.   var lastOfSameType;
  1301.   var inserted = false;
  1302.   while (children.hasMoreElements()) {
  1303.     var child = children.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1304.     
  1305.     // If this node belongs to the library service pane, and 
  1306.     // is not the node we are trying to insert, then check
  1307.     // to see if this is an insertion point candidate
  1308.     if (child.contractid == CONTRACTID && child.id != aNode.id) {
  1309.     
  1310.       // Keep track of last node similar to this one, preferring
  1311.       // nodes that are exactly the same type as this one
  1312.       if (nodeType == child.getAttributeNS(LSP, "ListType") ||
  1313.           lastOfSameType == null ||
  1314.           (lastOfSameType != null && 
  1315.              lastOfSameType.getAttributeNS(LSP, "ListType") != nodeType))
  1316.       {
  1317.         lastOfSameType = child;
  1318.       } 
  1319.     }     
  1320.   } // end of while
  1321.   
  1322.   // Insert the new list after the last of the same type
  1323.   // or at the end of the list
  1324.   if (lastOfSameType && lastOfSameType.nextSibling) {
  1325.     if (lastOfSameType.nextSibling.id != aNode.id) {
  1326.       aParent.insertBefore(aNode, lastOfSameType.nextSibling);
  1327.     }
  1328.   } else {
  1329.     aParent.appendChild(aNode);
  1330.   }
  1331. }
  1332.  
  1333. sbLibraryServicePane.prototype._appendMenuItem =
  1334. function sbLibraryServicePane__appendMenuItem(aContextMenu, aLabel, aCallback) {
  1335.   var item = aContextMenu.ownerDocument.createElement("menuitem");
  1336.   item.setAttribute("label", aLabel);
  1337.   item.addEventListener("command", aCallback, false);
  1338.   aContextMenu.appendChild(item);
  1339. }
  1340.  
  1341. sbLibraryServicePane.prototype._appendCommands =
  1342. function sbLibraryServicePane__appendCommands(aContextMenu, aList, aParentWindow) {
  1343.   if (this._lastMenuitems && this._lastMenuitems.destroy) {
  1344.     var pnode = this._lastMenuitems.parentNode;
  1345.     this._lastMenuitems.destroy();
  1346.     this._lastMenuitems = null;
  1347.   }
  1348.   var itemBuilder = aContextMenu.ownerDocument.createElement("sb-commands-menuitems");
  1349.   itemBuilder.setAttribute("id", "playlist-commands");
  1350.   itemBuilder.setAttribute("commandtype", "medialist");
  1351.   itemBuilder.setAttribute("bind", aList.library.guid + ';' + aList.guid);
  1352.   aContextMenu.appendChild(itemBuilder);
  1353.   this._lastMenuitems = itemBuilder;
  1354. }
  1355.  
  1356. /**
  1357.  * This function is a recursive helper for onListCleared (below) that will
  1358.  * remove all the playlist nodes for a given library.
  1359.  */
  1360. sbLibraryServicePane.prototype._removeListNodesForLibrary =
  1361. function sbLibraryServicePane__removeListNodesForLibrary(aStartNode, aLibraryGUID) {
  1362.  
  1363.   var node = aStartNode.firstChild;
  1364.   
  1365.   while (node) {
  1366.   
  1367.     if (node.isContainer) {
  1368.       this._removeListNodesForLibrary(node, aLibraryGUID);
  1369.     }
  1370.     
  1371.     var nextSibling = node.nextSibling;
  1372.     
  1373.     if (this._getItemGUIDForURN(node.id)) {
  1374.       var nodeLibraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  1375.       if (nodeLibraryGUID == aLibraryGUID) {
  1376.         this._servicePane.removeNode(node);
  1377.       }
  1378.     }
  1379.     
  1380.     node = nextSibling;
  1381.   }
  1382. }
  1383.  
  1384. sbLibraryServicePane.prototype._mergeProperties =
  1385. function sbLibraryServicePane__mergeProperties(aNode, aList) {
  1386.  
  1387.   var o = {};
  1388.   var a = aNode.properties?aNode.properties.split(" "):[];
  1389.   a.forEach(function(prop) {
  1390.     o[prop] = 1;
  1391.   });
  1392.  
  1393.   aList.forEach(function(prop) {
  1394.     o[prop] = 1;
  1395.   });
  1396.  
  1397.   var retval = [];
  1398.   for (var prop in o) {
  1399.     retval.push(prop);
  1400.   }
  1401.   aNode.properties = retval.join(" ");
  1402. }
  1403.  
  1404. ///////////////////////////////
  1405. // sbILibraryManagerListener //
  1406. ///////////////////////////////
  1407.  
  1408. sbLibraryServicePane.prototype.onLibraryRegistered =
  1409. function sbLibraryServicePane_onLibraryRegistered(aLibrary) {
  1410.   //logcall(arguments);
  1411.   this._libraryAdded(aLibrary);
  1412. }
  1413. sbLibraryServicePane.prototype.onLibraryUnregistered =
  1414. function sbLibraryServicePane_onLibraryUnregistered(aLibrary) {
  1415.   //logcall(arguments);
  1416.   this._libraryRemoved(aLibrary);
  1417. }
  1418.  
  1419. //////////////////////////
  1420. // sbIMediaListListener //
  1421. //////////////////////////
  1422.  
  1423. sbLibraryServicePane.prototype.onItemAdded =
  1424. function sbLibraryServicePane_onItemAdded(aMediaList, aMediaItem) {
  1425.   //logcall(arguments);
  1426.   if (this._batch.isActive()) {
  1427.     // We are going to refresh all the nodes once we exit the batch so
  1428.     // we don't need any more of these notifications
  1429.     this._refreshPending = true;
  1430.     return true;
  1431.   }
  1432.   else {
  1433.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1434.     if (isList) {
  1435.       this._playlistAdded(aMediaItem);
  1436.     }
  1437.     return false;
  1438.   }
  1439. }
  1440. sbLibraryServicePane.prototype.onBeforeItemRemoved =
  1441. function sbLibraryServicePane_onBeforeItemRemoved(aMediaList, aMediaItem) {
  1442.   //logcall(arguments);
  1443.   if (this._batch.isActive()) {
  1444.     // We are going to refresh all the nodes once we exit the batch so
  1445.     // we don't need any more of these notifications
  1446.     this._refreshPending = true;
  1447.     return true;
  1448.   }
  1449.   else {
  1450.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1451.     if (isList) {
  1452.       this._playlistRemoved(aMediaItem);
  1453.     }
  1454.     return false;
  1455.   }
  1456. }
  1457. sbLibraryServicePane.prototype.onAfterItemRemoved =
  1458. function sbLibraryServicePane_onAfterItemRemoved(aMediaList, aMediaItem) {
  1459. }
  1460. sbLibraryServicePane.prototype.onItemUpdated =
  1461. function sbLibraryServicePane_onItemUpdated(aMediaList,
  1462.                                             aMediaItem,
  1463.                                             aProperties) {
  1464.   if (this._batch.isActive()) {
  1465.     // We are going to refresh all the nodes once we exit the batch so
  1466.     // we don't need any more of these notifications
  1467.     this._refreshPending = true;
  1468.     return true;
  1469.   }
  1470.   else {
  1471.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  1472.     if (isList) {
  1473.       this._mediaListUpdated(aMediaItem);
  1474.     }
  1475.     return false;
  1476.   }
  1477. }
  1478. sbLibraryServicePane.prototype.onListCleared =
  1479. function sbLibraryServicePane_onListCleared(aMediaList) {
  1480.   if (this._batch.isActive()) {
  1481.     // We are going to refresh all the nodes once we exit the batch so
  1482.     // we don't need any more of these notifications
  1483.     this._refreshPending = true;
  1484.     return true;
  1485.   }
  1486.   else {
  1487.     if (aMediaList instanceof Ci.sbILibrary) {
  1488.       var libraryGUID = aMediaList.guid;
  1489.       
  1490.       var node = this._servicePane.root;
  1491.       this._removeListNodesForLibrary(node, libraryGUID);
  1492.     }
  1493.     return false;
  1494.   }
  1495. }
  1496. sbLibraryServicePane.prototype.onBatchBegin =
  1497. function sbLibraryServicePane_onBatchBegin(aMediaList) {
  1498.   //logcall(arguments);
  1499.   this._batch.begin();
  1500. }
  1501. sbLibraryServicePane.prototype.onBatchEnd =
  1502. function sbLibraryServicePane_onBatchEnd(aMediaList) {
  1503.   //logcall(arguments);
  1504.   this._batch.end();
  1505.   if (!this._batch.isActive() && this._refreshPending) {
  1506.     this._refreshLibraryNodes(aMediaList);
  1507.   }
  1508.  
  1509. }
  1510.  
  1511.  
  1512. sbLibraryServicePane.prototype._initLibraryManager =
  1513. function sbLibraryServicePane__initLibraryManager() {
  1514.   // get the library manager
  1515.   this._libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  1516.                            .getService(Ci.sbILibraryManager);
  1517.  
  1518.   // register for notifications so that we can keep the service pane
  1519.   // in sync with the the libraries
  1520.   this._libraryManager.addListener(this);
  1521.  
  1522.   this._addAllLibraries();
  1523. }
  1524. /////////////////
  1525. // nsIObserver //
  1526. /////////////////
  1527.  
  1528. sbLibraryServicePane.prototype.observe = 
  1529. function sbLibraryServicePane_observe(subject, topic, data) {
  1530.  
  1531.   var obs = Cc["@mozilla.org/observer-service;1"]
  1532.               .getService(Ci.nsIObserverService);
  1533.  
  1534.   if (topic == "songbird-library-manager-ready") {
  1535.     obs.removeObserver(this, "songbird-library-manager-ready");
  1536.  
  1537.     if (!this._libraryManager) {
  1538.       this._initLibraryManager();
  1539.     }
  1540.   }
  1541.   else if (topic == "songbird-library-manager-before-shutdown") {
  1542.     obs.removeObserver(this, "songbird-library-manager-before-shutdown");
  1543.  
  1544.     var libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  1545.                            .getService(Ci.sbILibraryManager);
  1546.     libraryManager.removeListener(this);
  1547.   }
  1548. }
  1549.  
  1550. ///////////
  1551. // XPCOM //
  1552. ///////////
  1553.  
  1554. /**
  1555.  * /brief XPCOM initialization code
  1556.  */
  1557. function makeGetModule(CONSTRUCTOR, CID, CLASSNAME, CONTRACTID, CATEGORIES) {
  1558.   return function (comMgr, fileSpec) {
  1559.     return {
  1560.       registerSelf : function (compMgr, fileSpec, location, type) {
  1561.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1562.         compMgr.registerFactoryLocation(CID,
  1563.                         CLASSNAME,
  1564.                         CONTRACTID,
  1565.                         fileSpec,
  1566.                         location,
  1567.                         type);
  1568.         if (CATEGORIES && CATEGORIES.length) {
  1569.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1570.               .getService(Ci.nsICategoryManager);
  1571.           for (var i=0; i<CATEGORIES.length; i++) {
  1572.             var e = CATEGORIES[i];
  1573.             catman.addCategoryEntry(e.category, e.entry, e.value, 
  1574.               true, true);
  1575.           }
  1576.         }
  1577.       },
  1578.  
  1579.       getClassObject : function (compMgr, cid, iid) {
  1580.         if (!cid.equals(CID)) {
  1581.           throw Cr.NS_ERROR_NO_INTERFACE;
  1582.         }
  1583.  
  1584.         if (!iid.equals(Ci.nsIFactory)) {
  1585.           throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1586.         }
  1587.  
  1588.         return this._factory;
  1589.       },
  1590.  
  1591.       _factory : {
  1592.         createInstance : function (outer, iid) {
  1593.           if (outer != null) {
  1594.             throw Cr.NS_ERROR_NO_AGGREGATION;
  1595.           }
  1596.           return (new CONSTRUCTOR()).QueryInterface(iid);
  1597.         }
  1598.       },
  1599.  
  1600.       unregisterSelf : function (compMgr, location, type) {
  1601.         compMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1602.         compMgr.unregisterFactoryLocation(CID, location);
  1603.         if (CATEGORIES && CATEGORIES.length) {
  1604.           var catman =  Cc["@mozilla.org/categorymanager;1"]
  1605.               .getService(Ci.nsICategoryManager);
  1606.           for (var i=0; i<CATEGORIES.length; i++) {
  1607.             var e = CATEGORIES[i];
  1608.             catman.deleteCategoryEntry(e.category, e.entry, true);
  1609.           }
  1610.         }
  1611.       },
  1612.  
  1613.       canUnload : function (compMgr) {
  1614.         return true;
  1615.       },
  1616.  
  1617.       QueryInterface : function (iid) {
  1618.         if ( !iid.equals(Ci.nsIModule) ||
  1619.              !iid.equals(Ci.nsISupports) )
  1620.           throw Cr.NS_ERROR_NO_INTERFACE;
  1621.         return this;
  1622.       }
  1623.  
  1624.     };
  1625.   }
  1626. }
  1627.  
  1628. var NSGetModule = makeGetModule (
  1629.   sbLibraryServicePane,
  1630.   Components.ID("{64ec2154-3733-4862-af3f-9f2335b14821}"),
  1631.   "Songbird Library Service Pane Service",
  1632.   CONTRACTID,
  1633.   [{
  1634.     category: 'service-pane',
  1635.     entry: '0library', // we want this to load first
  1636.     value: CONTRACTID
  1637.   }]);
  1638.  
  1639.  
  1640.