home *** CD-ROM | disk | FTP | other *** search
/ Freelog 100 / FreelogNo100-NovembreDecembre2010.iso / Multimedia / Songbird / Songbird_1.8.0-1800_windows-i686-msvc8.exe / components / sbLibraryServicePaneService.js < prev    next >
Text File  |  2010-08-30  |  74KB  |  2,187 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-2010 POTI, Inc.
  8.  * http://www.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.  * \file sbLibraryServicePane.js
  28.  */
  29.  
  30. const Cc = Components.classes;
  31. const Ci = Components.interfaces;
  32. const Cr = Components.results;
  33.  
  34. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  35.  
  36. Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
  37. Components.utils.import("resource://app/jsmodules/sbLibraryUtils.jsm");
  38. Components.utils.import("resource://app/jsmodules/ArrayConverter.jsm");
  39. Components.utils.import("resource://app/jsmodules/DropHelper.jsm");
  40. Components.utils.import("resource://app/jsmodules/StringUtils.jsm");
  41. Components.utils.import("resource://app/jsmodules/WindowUtils.jsm");
  42.  
  43. const CONTRACTID = "@songbirdnest.com/servicepane/library;1";
  44.  
  45. const URN_PREFIX_ITEM = 'urn:item:';
  46. const URN_PREFIX_LIBRARY = 'urn:library:';
  47.  
  48. const LSP = 'http://songbirdnest.com/rdf/library-servicepane#';
  49. const SP='http://songbirdnest.com/rdf/servicepane#';
  50.  
  51.  
  52. const TYPE_X_SB_TRANSFER_MEDIA_ITEM = "application/x-sb-transfer-media-item";
  53. const TYPE_X_SB_TRANSFER_MEDIA_LIST = "application/x-sb-transfer-media-list";
  54. const TYPE_X_SB_TRANSFER_MEDIA_ITEMS = "application/x-sb-transfer-media-items";
  55. const TYPE_X_SB_TRANSFER_DISABLE_DOWNLOAD = "application/x-sb-transfer-disable-download";
  56.  
  57. /**
  58.  * Given the arguments var of a function, dump the
  59.  * name of the function and the parameters provided
  60.  */
  61. function logcall(parentArgs) {
  62.   dump("\n");
  63.   dump(parentArgs.callee.name + "(");
  64.   for (var i = 0; i < parentArgs.length; i++) {
  65.     dump(parentArgs[i]);
  66.     if (i < parentArgs.length - 1) {
  67.       dump(', ');
  68.     }
  69.   }
  70.   dump(")\n");
  71. }
  72.  
  73. const DEBUG_MODE = false;
  74.  
  75. function LOG(s) {
  76.   if(DEBUG_MODE) {
  77.     dump(s + "\n");
  78.   }
  79. }
  80.  
  81. /**
  82.  * /class sbLibraryServicePane
  83.  * /brief Provides library related nodes for the service pane
  84.  * \sa sbIServicePaneService sbILibraryServicePaneService
  85.  */
  86. function sbLibraryServicePane() {
  87.   this._servicePane = null;
  88.   this._libraryManager = null;
  89.   this._lastShortcuts = null;
  90.   this._lastMenuitems = null;
  91.  
  92.   // use the default stringbundle to translate tree nodes
  93.   this.stringbundle = null;
  94.  
  95.   this._batch = {}; // we'll keep the batch counters in here
  96.   this._refreshPending = false;
  97.   
  98.   // for cleaning up
  99.   this._libraries = [];
  100. }
  101.  
  102. //////////////////////////
  103. // nsISupports / XPCOM  //
  104. //////////////////////////
  105.  
  106. sbLibraryServicePane.prototype.QueryInterface =
  107.   XPCOMUtils.generateQI([Ci.nsIObserver,
  108.                          Ci.sbIServicePaneModule,
  109.                          Ci.sbILibraryServicePaneService,
  110.                          Ci.sbILibraryManagerListener,
  111.                          Ci.sbIMediaListListener]);
  112.  
  113. sbLibraryServicePane.prototype.classID =
  114.   Components.ID("{64ec2154-3733-4862-af3f-9f2335b14821}");
  115. sbLibraryServicePane.prototype.classDescription =
  116.   "Songbird Library Service Pane Service";
  117. sbLibraryServicePane.prototype.contractID =
  118.   CONTRACTID;
  119. sbLibraryServicePane.prototype._xpcom_categories = [{
  120.   category: 'service-pane',
  121.   entry: '0library', // we want this to load first
  122.   value: CONTRACTID
  123. }];
  124. //////////////////////////
  125. // sbIServicePaneModule //
  126. //////////////////////////
  127.  
  128. sbLibraryServicePane.prototype.servicePaneInit =
  129. function sbLibraryServicePane_servicePaneInit(sps) {
  130.   //logcall(arguments);
  131.  
  132.   // keep track of the service pane service
  133.   this._servicePane = sps;
  134.  
  135.   // get the library manager
  136.   this._initLibraryManager();
  137. }
  138.  
  139. sbLibraryServicePane.prototype.shutdown =
  140. function sbLibraryServicePane_shutdown() {}
  141.  
  142. sbLibraryServicePane.prototype.fillContextMenu =
  143. function sbLibraryServicePane_fillContextMenu(aNode, aContextMenu, aParentWindow) {
  144.   var libraryMgr = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  145.                      .getService(Ci.sbILibraryManager);
  146.  
  147.   // the playlists folder and the local library node get the "New Foo..." items
  148.   if (aNode.id == 'SB:Playlists' ||
  149.       this._getLibraryGUIDForURN(aNode.id) == libraryMgr.mainLibrary.guid ||
  150.       aNode.getAttributeNS(LSP, 'ListCustomType') == 'local') {
  151.     this.fillNewItemMenu(aNode, aContextMenu, aParentWindow);
  152.   }
  153.  
  154.   var list = this.getLibraryResourceForNode(aNode);
  155.   if (list) {
  156.     this._appendCommands(aContextMenu, list, aParentWindow);
  157.  
  158.     // Add menu items for a smart media list
  159.     if (list instanceof Ci.sbILocalDatabaseSmartMediaList) {
  160.       if (list.userEditable) {
  161.         this._appendMenuItem(aContextMenu, SBString("command.smartpl.properties"), function(event) {
  162.           var watcher = Cc["@mozilla.org/embedcomp/window-watcher;1"]
  163.                           .getService(Ci.nsIWindowWatcher);
  164.           watcher.openWindow(aParentWindow,
  165.                             "chrome://songbird/content/xul/smartPlaylist.xul",
  166.                             "_blank",
  167.                             "chrome,dialog=yes,centerscreen,modal,titlebar=no",
  168.                             list);
  169.         });
  170.       }
  171.     }
  172.     // Append the media export context menuitem only if the user has exporting turned on.
  173.     var appPrefs = Cc["@mozilla.org/fuel/application;1"]
  174.                      .getService(Ci.fuelIApplication).prefs;
  175.     if (appPrefs.getValue("songbird.library_exporter.export_tracks", false)) {
  176.       // Add media export hook if this is the main library
  177.       if (list == libraryMgr.mainLibrary) {
  178.         var exportService = Cc["@songbirdnest.com/media-export-service;1"]
  179.                               .getService(Ci.sbIMediaExportService);
  180.  
  181.         var item = aContextMenu.ownerDocument.createElement("menuitem");
  182.         item.setAttribute("label", SBString("command.libraryexport"));
  183.         item.addEventListener(
  184.           "command",
  185.           function(event) {
  186.             var exportService = Cc["@songbirdnest.com/media-export-service;1"]
  187.                                   .getService(Ci.sbIMediaExportService);
  188.             exportService.exportSongbirdData(); 
  189.           },
  190.           false);
  191.  
  192.         // Disable the item if it doesn't have pending changes.
  193.         if (!exportService.hasPendingChanges) {
  194.           item.setAttribute("disabled", "true");
  195.         }
  196.  
  197.         aContextMenu.appendChild(item);
  198.       }
  199.     }
  200.  
  201.     // Add menu items for a dynamic media list
  202.     if (list.getProperty("http://songbirdnest.com/data/1.0#isSubscription") == "1") {
  203.       this._appendMenuItem(aContextMenu, SBString("command.subscription.properties"), function(event) {
  204.         var params = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"].createInstance(Ci.nsIMutableArray);
  205.         params.appendElement(list, false);
  206.  
  207.         WindowUtils.openModalDialog(null,
  208.                                     "chrome://songbird/content/xul/subscribe.xul",
  209.                                     "",
  210.                                     "chrome,modal=yes,centerscreen",
  211.                                     params,
  212.                                     null);
  213.       });
  214.       this._appendMenuItem(aContextMenu, "Update", function(event) { //XXX todo: localize
  215.         var dps = Cc["@songbirdnest.com/Songbird/Library/DynamicPlaylistService;1"]
  216.                     .getService(Ci.sbIDynamicPlaylistService);
  217.         dps.updateNow(list);
  218.       });
  219.     }
  220.   }
  221. }
  222.  
  223. sbLibraryServicePane.prototype.fillNewItemMenu =
  224. function sbLibraryServicePane_fillNewItemMenu(aNode, aContextMenu, aParentWindow) {
  225.   var sbSvc = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
  226.   var stringBundle = sbSvc.createBundle("chrome://songbird/locale/songbird.properties");
  227.  
  228.   function add(id, label, accesskey, oncommand, modifiers) {
  229.     var menuitem = aContextMenu.ownerDocument.createElement('menuitem');
  230.     menuitem.setAttribute('id', id);
  231.     menuitem.setAttribute('class', 'menuitem-iconic');
  232.     menuitem.setAttribute('label', stringBundle.GetStringFromName(label));
  233.     menuitem.setAttribute('accesskey', stringBundle.GetStringFromName(accesskey));
  234.     menuitem.setAttribute('oncommand', oncommand);
  235.     if (typeof(modifiers) != "undefined") {
  236.       menuitem.setAttribute('modifiers', modifiers);
  237.     }
  238.     aContextMenu.appendChild(menuitem);
  239.   }
  240.  
  241.   add('menuitem_file_new',
  242.       'menu.servicepane.file.new',
  243.       'menu.servicepane.file.new.accesskey',
  244.       'doMenu("menuitem_file_new", event)');
  245.   add('file.smart',
  246.       'menu.servicepane.file.smart',
  247.       'menu.servicepane.file.smart.accesskey',
  248.       'doMenu("menuitem_file_smart")',
  249.       "alt");
  250. }
  251.  
  252. sbLibraryServicePane.prototype.onSelectionChanged =
  253. function sbLibraryServicePane_onSelectionChanged(aNode, aContainer, aParentWindow) {
  254.   this._destroyShortcuts(aContainer, aParentWindow);
  255.   var list;
  256.   if (aNode) list = this.getLibraryResourceForNode(aNode);
  257.   if (list) this._createShortcuts(list, aContainer, aParentWindow);
  258. }
  259.  
  260. sbLibraryServicePane.prototype._createShortcuts =
  261. function sbLibraryServicePane__createShortcuts(aList, aContainer, aWindow) {
  262.   var shortcuts = aWindow.document.createElement("sb-commands-shortcuts");
  263.   shortcuts.setAttribute("id", "playlist-commands-shortcuts");
  264.   shortcuts.setAttribute("commandtype", "medialist");
  265.   shortcuts.setAttribute("bind", aList.library.guid + ';' + aList.guid);
  266.   aContainer.appendChild(shortcuts);
  267.   this._lastShortcuts = shortcuts;
  268. }
  269.  
  270. sbLibraryServicePane.prototype._destroyShortcuts =
  271. function sbLibraryServicePane__destroyShortcuts(aContainer, aWindow) {
  272.   if (this._lastShortcuts) {
  273.     this._lastShortcuts.destroy();
  274.     aContainer.removeChild(this._lastShortcuts);
  275.     this._lastShortcuts = null;
  276.   }
  277. }
  278.  
  279. sbLibraryServicePane.prototype._canDownloadDrop =
  280. function sbLibraryServicePane__canDownloadDrop(aDragSession) {
  281.   // bail out if the drag session does not contain internal items.
  282.   // We may eventually add a handler to add an external media URL drop on 
  283.   // the download playlist.
  284.   if (!InternalDropHandler.isSupported(aDragSession)) 
  285.     return false;
  286.    
  287.   var IOS = Cc["@mozilla.org/network/io-service;1"]
  288.               .getService(Ci.nsIIOService);
  289.  
  290.   function canDownload(aMediaItem) {
  291.     var contentSpec = aMediaItem.getProperty(SBProperties.contentURL);
  292.     var contentURL = IOS.newURI(contentSpec, null, null);
  293.     switch(contentURL.scheme) {
  294.       case "http":
  295.       case "https":
  296.       case "ftp":
  297.         // these are safe to download
  298.         return true;
  299.     }
  300.     return false;
  301.   }
  302.  
  303.   if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_ITEMS)) {
  304.     var context = DNDUtils.
  305.       getInternalTransferDataForFlavour(aDragSession,
  306.                                         TYPE_X_SB_TRANSFER_MEDIA_ITEMS,
  307.                                         Ci.sbIMediaItemsTransferContext);
  308.     var items = context.items;
  309.     // we must remember to reset the context before we exit, so that when we
  310.     // actually need the items in onDrop we can get them again!
  311.     var count = 0;    
  312.     var downloadable = true;
  313.     while (items.hasMoreElements() && downloadable) {
  314.       downloadable = canDownload(items.getNext());
  315.       ++count;
  316.     }
  317.     
  318.     // we can't download nothing.
  319.     if (count == 0) { downloadable = false; }
  320.     
  321.     // rewind the items list.
  322.     context.reset();
  323.     
  324.     return downloadable;
  325.   } else {
  326.     Components.utils.reportError("_getMediaListForDrop should have returned null");
  327.     return false;
  328.   }
  329. }
  330.  
  331. sbLibraryServicePane.prototype._getMediaListForDrop =
  332. function sbLibraryServicePane__getMediaListForDrop(aNode, aDragSession, aOrientation) {
  333.   // work out what the drop would target and return an sbIMediaList to
  334.   // represent that target, or null if the drop is not allowed
  335.  
  336.   // check if we support this drop at all
  337.   if (!InternalDropHandler.isSupported(aDragSession)&& 
  338.       !ExternalDropHandler.isSupported(aDragSession)) {
  339.     return null;
  340.   }
  341.  
  342.   // are we dropping a list ?
  343.   var dropList = aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_MEDIA_LIST);
  344.  
  345.   // work out where the drop items are going
  346.   var targetNode = aNode;
  347.   var dropOnto = true;
  348.   if (aOrientation != 0) {
  349.     dropOnto = false;
  350.   }
  351.  
  352.   // work out what library resource is associated with the target node 
  353.   var targetResource = this.getLibraryResourceForNode(targetNode);
  354.  
  355.   // check that the target list or library accepts drops
  356.   // TODO, i can't believe we don't have a property to check for that :/
  357.   // I'm told that the transfer policy will solve this, this might be a good
  358.   // spot for querying it in the future... (?)
  359.  
  360.   // is the target a library
  361.   var targetIsLibrary = (targetResource instanceof Ci.sbILibrary);
  362.  
  363.   // The Rules:
  364.   //  * non-playlist items can be dropped on top of playlists and libraries
  365.   //  * playlist items can be dropped on top of other libraries than their own
  366.   //  * playlists can be dropped next to other playlists, either as part of a
  367.   //      reordering, or as a playlist transfer to a different library, with a 
  368.   //      specific position to drop to
  369.  
  370.   if (dropOnto) {
  371.     if (!dropList) {
  372.       // we are dropping onto a target node, and this is not a playlist, 
  373.       // so return the target medialist that corresponds to the target node. 
  374.       return targetResource;
  375.     } else {
  376.       // we are dropping a list onto a target node, we can only accept this drop
  377.       // on a library, so if the target isnt one, refuse it
  378.       // note: if we ever want to support dropping playlists on playlists, this
  379.       // is the sport to add the new case
  380.       if (!targetIsLibrary) {
  381.         return null;
  382.       }
  383.         
  384.       // we can only accept a list drop on a library different than its own 
  385.       // (because its own library already has all of its tracks, so it makes no
  386.       // sense to allow it), so extract the medialist that we are dropping, 
  387.       // and check where it comes from
  388.       var draggedList = DNDUtils.
  389.         getInternalTransferDataForFlavour(aDragSession,
  390.                                           TYPE_X_SB_TRANSFER_MEDIA_LIST,
  391.                                           Ci.sbIMediaListTransferContext);
  392.       if (targetResource == draggedList.list.library) {
  393.         return null;
  394.       }
  395.         
  396.       // we are indeed dropping the playlist onto a different library, accept
  397.       // the drop
  398.       return targetResource;
  399.     }
  400.  
  401.   } else {
  402.  
  403.     // we are dropping in between two items
  404.     if (dropList) {
  405.       
  406.       // we are dropping a playlist.
  407.       
  408.       // if we are trying to insert the playlist between two toplevel nodes,
  409.       // refuse the drop
  410.       if (targetNode.parentNode.id == "SB:Root") {
  411.         return null;
  412.       }
  413.       
  414.       // we now know that this is either a reorder inside a single container 
  415.       // or a playlist transfer from one library to another, involving two
  416.       // different containers. 
  417.       
  418.       // to be able to discriminate between those two cases, we need to know
  419.       // where the playlist is going, as well as where it comes from. 
  420.       
  421.       // find where the playlist comes from
  422.       var draggedList = DNDUtils.
  423.         getInternalTransferDataForFlavour(aDragSession,
  424.                                           TYPE_X_SB_TRANSFER_MEDIA_LIST,
  425.                                           Ci.sbIMediaListTransferContext);
  426.       var fromResource = draggedList.library;
  427.       
  428.       var toResource = null;
  429.  
  430.       // finding where the playlist is going however is much more complicated,
  431.       // because we cannot rely on the fact that we can extract a library
  432.       // resource from the parent container for the target list: we could be
  433.       // dropping into a foreign container (eg. a device node), and the actual
  434.       // library node could be a sibling of the target node.
  435.       
  436.       // so we'll first try to get a resource from the parent node, in case we
  437.       // are dropping into a list of playlist that are children of their
  438.       // library
  439.       var parentNode = targetNode.parentNode;
  440.       if (parentNode) {
  441.         toResource = this.getLibraryResourceForNode(parentNode);
  442.       }
  443.       
  444.       // ... and if there was no parent for the target node, or the parent had 
  445.       // no library resource, we can still look around the drop target for 
  446.       // libraries and playlists nodes. if we find any of these, we can assume 
  447.       // that the resource targeted for drop is these item's library
  448.       
  449.       if (!toResource) {
  450.         var siblingNode = targetNode.previousSibling;
  451.         if (siblingNode) {
  452.           toResource = this.getLibraryResourceForNode(siblingNode);
  453.         }
  454.       }
  455.       if (!toResource) {
  456.         var siblingNode = targetNode.nextSibling;
  457.         if (siblingNode) {
  458.           toResource = this.getLibraryResourceForNode(siblingNode);
  459.         }
  460.       }
  461.         
  462.       // if we have not found what the destination library is, refuse the drop
  463.       if (!toResource) 
  464.         return null;
  465.         
  466.       // get the library for the resource we found
  467.       toResource = toResource.library;
  468.         
  469.       // if the source and destination library are the same, this is a reorder,
  470.       // we can actually pretend to refuse it, because the servicePaneService
  471.       // is going to accept it based on further rules (dndAcceptIn, dndAcceptNear)
  472.       // and because there is no actual drop handling to be performed, the nodes
  473.       // will be reordered in the tree and that is it.
  474.       if (toResource == fromResource) 
  475.         return null;
  476.       
  477.       // otherwise, this is a playlist transfer from one library to another,
  478.       // we should accept the drop, but at the condition that we are not
  479.       // dropping above a library, because we want to keep playlists either 
  480.       // below or as children of their library nodes. 
  481.       if (targetIsLibrary && aOrientation == 1) 
  482.         return null;
  483.       
  484.       // the destination seems correct, accept the drop, the handler will
  485.       // first copy the list to the new library, and then move the node
  486.       // to where it belongs  
  487.       return toResource;
  488.     }
  489.   }
  490.   
  491.   // default is refuse the drop
  492.   return null;
  493. }
  494.  
  495. sbLibraryServicePane.prototype._canDropReorder =
  496. function sbLibraryServicePane__canDropReorder(aNode, aDragSession, aOrientation) {
  497.   // see if we can handle the drag and drop based on node properties
  498.   let types = [];
  499.   if (aOrientation == 0) {
  500.     // drop in
  501.     if (aNode.dndAcceptIn) {
  502.       types = aNode.dndAcceptIn.split(',');
  503.     }
  504.   } else {
  505.     // drop near
  506.     if (aNode.dndAcceptNear) {
  507.       types = aNode.dndAcceptNear.split(',');
  508.     }
  509.   }
  510.   for each (let type in types) {
  511.     if (aDragSession.isDataFlavorSupported(type)) {
  512.       return type;
  513.     }
  514.   }
  515.   return null;
  516. }
  517.  
  518.  
  519. sbLibraryServicePane.prototype.canDrop =
  520. function sbLibraryServicePane_canDrop(aNode, aDragSession, aOrientation, aWindow) {
  521.   // see if we can handle the drag and drop based on node properties
  522.   if (this._canDropReorder(aNode, aDragSession, aOrientation)) {
  523.     return true;
  524.   }
  525.   // don't allow drop on read-only nodes
  526.   if (aNode.getAttributeNS(LSP, "ReadOnly") == "true")
  527.     return false;
  528.  
  529.   // if some of the items have disable download set then don't allow a drop 
  530.   // on the service pane
  531.   if (aDragSession.isDataFlavorSupported(TYPE_X_SB_TRANSFER_DISABLE_DOWNLOAD)) {
  532.     return false;
  533.   }
  534.  
  535.   var list = this._getMediaListForDrop(aNode, aDragSession, aOrientation);
  536.   if (list) {
  537.     
  538.     // check if the list is in a readonly library
  539.     if (!list.library.userEditable ||
  540.         !list.library.userEditableContent) {
  541.       // this is a list for a readonly library, can't drop
  542.       return false;
  543.     }
  544.     
  545.     // check if the list is itself readonly
  546.     if (!list.userEditable ||
  547.         !list.userEditableContent) {
  548.       // this list content is readonly, can't drop
  549.       return false;
  550.     }
  551.     
  552.     // XXX Mook: hack for bug 4760 to do special handling for the download
  553.     // playlist.  This will need to be expanded later to use IDLs on the
  554.     // list so that things like extensions can do this too.
  555.     var customType = list.getProperty(SBProperties.customType);
  556.     if (customType == "download") {
  557.       return this._canDownloadDrop(aDragSession);
  558.     }
  559.     // test whether the drop contains supported flavours
  560.     return InternalDropHandler.isSupported(aDragSession) ||
  561.            ExternalDropHandler.isSupported(aDragSession);
  562.   } else {
  563.     return false;
  564.   }
  565. }
  566.  
  567. sbLibraryServicePane.prototype.onDrop =
  568. function sbLibraryServicePane_onDrop(aNode, aDragSession, aOrientation, aWindow) {
  569.   // see if this is a reorder we can handle based on node properties
  570.   let type = this._canDropReorder(aNode, aDragSession, aOrientation);
  571.   if (type) {
  572.     // we're in business
  573.  
  574.     // do the dance to get our data out of the dnd system
  575.     // create an nsITransferable
  576.     let transferable = Cc["@mozilla.org/widget/transferable;1"]
  577.                          .createInstance(Ci.nsITransferable);
  578.     // specify what kind of data we want it to contain
  579.     transferable.addDataFlavor(type);
  580.     // ask the drag session to fill the transferable with that data
  581.     aDragSession.getData(transferable, 0);
  582.     // get the data from the transferable
  583.     let data = {};
  584.     transferable.getTransferData(type, data, {});
  585.     // it's always a string. always.
  586.     data = data.value.QueryInterface(Ci.nsISupportsString).data;
  587.  
  588.     // for drag and drop reordering the data is just the servicepane node id
  589.     let droppedNode = this._servicePane.getNode(data);
  590.  
  591.     // fail if we can't get the node or it is the node we are over
  592.     if (!droppedNode || aNode == droppedNode) {
  593.       return;
  594.     }
  595.  
  596.     if (aOrientation == 0) {
  597.       // drop into
  598.       aNode.appendChild(droppedNode);
  599.     } else if (aOrientation > 0) {
  600.       // drop after
  601.       aNode.parentNode.insertBefore(droppedNode, aNode.nextSibling);
  602.     } else {
  603.       // drop before
  604.       aNode.parentNode.insertBefore(droppedNode, aNode);
  605.     }
  606.     // work out what library resource is associated with the moved node 
  607.     var medialist = this.getLibraryResourceForNode(aNode);
  608.     
  609.     this._saveListsOrder(medialist.library, aNode.parentNode);
  610.     return;
  611.   }
  612.   
  613.   // don't allow drop on read-only nodes
  614.   if (aNode.getAttributeNS(LSP, "ReadOnly") == "true")
  615.     return;
  616.  
  617.   // where are we dropping?
  618.   var targetList = this._getMediaListForDrop(aNode, aDragSession, aOrientation);
  619.  
  620.   if (!targetList) {
  621.     // don't know how to drop here
  622.     return;
  623.   }
  624.   
  625.   // perform this test now because the incoming new node makes it unreliable
  626.   // to do in the onCopyMediaList callback
  627.   var isLastSibling = (aNode.nextSibling == null);
  628.   
  629.   var dropHandlerListener = {
  630.     libSPS: this,
  631.     onDropComplete: function(aTargetList,
  632.                              aImportedInLibrary,
  633.                              aDuplicates,
  634.                              aInsertedInMediaList,
  635.                              aOtherDropsHandled) { 
  636.       // show the standard report on the status bar
  637.       return true; 
  638.     },
  639.     onFirstMediaItem: function(aTargetList, aFirstMediaItem) {},
  640.     onCopyMediaList: function(aSourceList, aNewList) {
  641.       // find the node that was created
  642.       var newnode = 
  643.         this.libSPS._servicePane.getNode(this.libSPS._itemURN(aNewList));
  644.       // move the item to the right spot
  645.       switch (aOrientation) {
  646.         case -1:
  647.           aNode.parentNode.insertBefore(newnode, aNode);
  648.           break;
  649.         case 1:
  650.           if (!isLastSibling)
  651.             aNode.parentNode.insertBefore(newnode, aNode.nextSibling);
  652.           // else, the node has already been placed at the right spot by
  653.           // the LibraryServicePaneService
  654.           break;
  655.       }
  656.     }
  657.   };
  658.  
  659.   if (InternalDropHandler.isSupported(aDragSession)) {
  660.     // handle drop of internal items
  661.     InternalDropHandler.dropOnList(aWindow, 
  662.                                    aDragSession, 
  663.                                    targetList, 
  664.                                    -1, 
  665.                                    dropHandlerListener);
  666.  
  667.   } else if (ExternalDropHandler.isSupported(aDragSession)) {
  668.   
  669.     // handle drop of external items
  670.     ExternalDropHandler.dropOnList(aWindow, 
  671.                                    aDragSession, 
  672.                                    targetList, 
  673.                                    -1, 
  674.                                    dropHandlerListener);
  675.   }
  676. }
  677.  
  678. sbLibraryServicePane.prototype._saveListsOrder =
  679. function sbLibraryServicePane__saveListsOrder(library, nodesParent) {
  680.   var str = "";
  681.   var children = nodesParent.childNodes;
  682.   while (children.hasMoreElements()) {
  683.     var child = children.getNext();
  684.     var resource = this.getLibraryResourceForNode(child);
  685.     if (resource instanceof Components.interfaces.sbIMediaList) {
  686.       if (str != "")
  687.         str += ",";
  688.       str += resource.guid;
  689.     }
  690.   }
  691.   var appPrefs = Cc["@mozilla.org/fuel/application;1"]
  692.                    .getService(Ci.fuelIApplication).prefs;
  693.   appPrefs.setValue("songbird.library_listorder." + library.guid, str);
  694. }
  695.  
  696. sbLibraryServicePane.prototype._nodeIsLibrary =
  697. function sbLibraryServicePane__nodeIsLibrary(aNode) {
  698.   return aNode.getAttributeNS(LSP, "LibraryGUID") ==
  699.       aNode.getAttributeNS(LSP, "ListGUID");
  700. }
  701. sbLibraryServicePane.prototype.onDragGesture =
  702. function sbLibraryServicePane_onDragGesture(aNode, aTransferable) {
  703.   if (this._nodeIsLibrary(aNode)) {
  704.     // a library isn't dragable
  705.     return false;
  706.   }
  707.  
  708.   // get the list and create the source context
  709.   var list = this._getItemForURN(aNode.id);
  710.   var context = {
  711.     source: list.library,
  712.     count: 1,
  713.     list: list,
  714.     QueryInterface: XPCOMUtils.generateQI([Ci.sbIMediaListTransferContext])
  715.   };
  716.   
  717.   // register the source context
  718.   var dnd = Components.classes['@songbirdnest.com/Songbird/DndSourceTracker;1']
  719.       .getService(Components.interfaces.sbIDndSourceTracker);
  720.   dnd.reset();
  721.   var handle = dnd.registerSource(context);
  722.  
  723.   // attach the source context to the transferable
  724.   aTransferable.addDataFlavor(TYPE_X_SB_TRANSFER_MEDIA_LIST);
  725.   var text = Components.classes["@mozilla.org/supports-string;1"].
  726.      createInstance(Components.interfaces.nsISupportsString);
  727.   text.data = handle;
  728.   aTransferable.setTransferData(TYPE_X_SB_TRANSFER_MEDIA_LIST, text,
  729.                                 text.data.length*2);
  730.  
  731.   return true;
  732. }
  733.  
  734.  
  735. /**
  736.  * Called when the user is about to attempt to rename a library/medialist node
  737.  */
  738. sbLibraryServicePane.prototype.onBeforeRename =
  739. function sbLibraryServicePane_onBeforeRename(aNode) {
  740. }
  741.  
  742. /**
  743.  * Called when the user has attempted to rename a library/medialist node
  744.  */
  745. sbLibraryServicePane.prototype.onRename =
  746. function sbLibraryServicePane_onRename(aNode, aNewName) {
  747.   //logcall(arguments);
  748.   if (aNode && aNewName) {
  749.     var libraryResource = this.getLibraryResourceForNode(aNode);
  750.     libraryResource.name = aNewName;
  751.     aNode.name = aNewName;
  752.   }
  753. }
  754.  
  755.  
  756. //////////////////////////////////
  757. // sbILibraryServicePaneService //
  758. //////////////////////////////////
  759.  
  760.  
  761. /* \brief Suggest a library for creating a new media list
  762.  *
  763.  * \param aMediaListType string identifying a media list type, eg "simple"
  764.  * \param aNode A service pane node to provide context for new list creation
  765.  * \return a library, or null if this service can't suggest anything based on
  766.  *         the given context and type.
  767.  */
  768. sbLibraryServicePane.prototype.suggestLibraryForNewList =
  769. function sbLibraryServicePane_suggestLibraryForNewList(aMediaListType, aNode) {
  770.   //logcall(arguments);
  771.  
  772.   // Must provide a media list type
  773.   if (!aMediaListType) {
  774.     throw Components.results.NS_ERROR_INVALID_ARG;
  775.   }
  776.  
  777.   // Make sure we are fully initialized
  778.   if (!this._libraryManager || !this._servicePane) {
  779.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  780.   }
  781.  
  782.   // if no node was provided, then suggest the main library
  783.   if (!aNode) 
  784.     return this._libraryManager.mainLibrary;
  785.   
  786.   function checkNode(aNode, aLibServicePane) {
  787.     // If this node is visible and belongs to the library
  788.     // service pane service...
  789.     if (aNode.contractid == CONTRACTID && !aNode.hidden) {
  790.       // If this is a playlist and the playlist belongs
  791.       // to a library that supports the given type,
  792.       // then suggest that library
  793.       var mediaItem = aLibServicePane._getItemForURN(aNode.id);
  794.       if (mediaItem && mediaItem instanceof Ci.sbIMediaList &&
  795.           aLibServicePane._doesLibrarySupportListType(mediaItem.library, aMediaListType))
  796.       {
  797.         return mediaItem.library;
  798.       }
  799.  
  800.       // If this is a library that supports the given type,
  801.       // then suggest the library
  802.       var library = aLibServicePane._getLibraryForURN(aNode.id);
  803.       if (library && library instanceof Ci.sbILibrary &&
  804.           aLibServicePane._doesLibrarySupportListType(library, aMediaListType))
  805.       {
  806.         return library;
  807.       }
  808.     }
  809.  
  810.     return null;
  811.   }
  812.   
  813.   // first, check if the given node is useable as a library...
  814.   var lib = checkNode(aNode, this);
  815.   if (lib)
  816.     return lib;
  817.  
  818.   // check the children of the node (but not recursively) for a usable library
  819.   for (var child = aNode.firstChild; child; child = child.nextSibling) {
  820.     lib = checkNode(child, this);
  821.     if (lib)
  822.       return lib;
  823.   }
  824.   
  825.   // Move up the tree looking for libraries that support the
  826.   // given media list type.
  827.   aNode = aNode.parentNode;
  828.   while (aNode && aNode != this._servicePane.root) {
  829.  
  830.     lib = checkNode(aNode, this);
  831.     if (lib)
  832.       return lib;
  833.  
  834.     // Move up the tree
  835.     aNode = aNode.parentNode;
  836.   } // end of while
  837.  
  838.   // If the main library supports the given type, then return that
  839.   if (this._doesLibrarySupportListType(this._libraryManager.mainLibrary,
  840.                                        aMediaListType))
  841.   {
  842.     return this._libraryManager.mainLibrary;
  843.   }
  844.  
  845.   // Oh well, out of luck
  846.   return null;
  847. }
  848.  
  849. /* \brief Suggest a unique name for creating a new playlist
  850.  *
  851.  * \param aLibrary an sbILibrary.
  852.  * \return a unique playlist name.
  853.  */
  854. sbLibraryServicePane.prototype.suggestNameForNewPlaylist =
  855. function sbLibraryServicePane_suggestNameForNewPlaylist(aLibrary) {
  856.   // Give the playlist a default name
  857.   // TODO: Localization should be done internally
  858.   var name = SBString("playlist", "Playlist");
  859.   var length = name.length;
  860.  
  861.   if(aLibrary instanceof Ci.sbILibrary) {
  862.     // Build the existing IDs array
  863.     let listIDs = [];
  864.     let mediaLists = aLibrary.getItemsByProperty(SBProperties.isList, "1");
  865.     for (let i = 0; i < mediaLists.length; ++i) {
  866.       let mediaListName = mediaLists.queryElementAt(i, Ci.sbIMediaList).name;
  867.       if (mediaListName && mediaListName.substr(0, length) == name) {
  868.         if (mediaListName.length == length) {
  869.           listIDs.push(1);
  870.         }
  871.         else if (mediaListName.length > length + 1) {
  872.           listIDs.push(parseInt(mediaListName.substr(length + 1)));
  873.         }
  874.       }
  875.     }
  876.  
  877.     let id = 1;
  878.     while (1) {
  879.       // The id is available.
  880.       if (listIDs.indexOf(id) == -1)
  881.         break;
  882.  
  883.       ++id;
  884.     }
  885.  
  886.     if (id > 1)
  887.       name = SBFormattedString("playlist.sequence", [id]);
  888.   }
  889.  
  890.   return name;
  891. }
  892.  
  893. sbLibraryServicePane.prototype.createNodeForLibrary =
  894. function sbLibraryServicePane_createNodeForLibrary(aLibrary) {
  895.   if(aLibrary instanceof Ci.sbILibrary) {
  896.     return this._libraryAdded(aLibrary);
  897.   }
  898.  
  899.   return null;
  900. }
  901.  
  902. /**
  903.  * \brief Get the URN for a resource, to be used to look up the service pane node
  904.  * \param aResource to resource to get the service pane URN for
  905.  * \returns The expected URN for the resource
  906.  */
  907. sbLibraryServicePane.prototype._getURNForLibraryResource =
  908. function sbLibraryServicePane_getURNForLibraryResource(aResource) {
  909.   //logcall(arguments);
  910.  
  911.   // Must be initialized
  912.   if (!this._libraryManager || !this._servicePane) {
  913.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  914.   }
  915.  
  916.   // If this is a library, get the library URN
  917.   if (aResource instanceof Ci.sbILibrary) {
  918.     return this._libraryURN(aResource);
  919.  
  920.   // If this is a mediaitem, get an item urn
  921.   } else if (aResource instanceof Ci.sbIMediaItem) {
  922.     // Check if this is a storage list for an outer list
  923.     var outerListGuid = aResource.getProperty(SBProperties.outerGUID);
  924.     if (outerListGuid) {
  925.       var library = aResource.library;
  926.       var outerList = library.getMediaItem(outerListGuid);
  927.       if (outerList) {
  928.         aResource = outerList;
  929.       }
  930.     }
  931.     // cacluate the URN for the item
  932.     return this._itemURN(aResource);
  933.  
  934.   // Else we don't know what to do, so
  935.   // the arg must be invalid
  936.   } else {
  937.     throw Components.results.NS_ERROR_INVALID_ARG;
  938.   }
  939.  
  940.   return node;
  941. }
  942.  
  943. /* \brief Attempt to get a service pane node for the given library resource
  944.  *
  945.  * \param aResource an sbIMediaItem, sbIMediaItem, or sbILibrary
  946.  * \return a service pane node that represents the given resource, if one
  947.  *         exists. Note that in the case that more than one node related to a
  948.  *         given library exists, it is not specified which node will be
  949.  *         returned. However, nodes that are not hidden will be preferred.
  950.  */
  951. sbLibraryServicePane.prototype.getNodeForLibraryResource =
  952. function sbLibraryServicePane_getNodeForLibraryResource(aResource) {
  953.   //logcall(arguments);
  954.  
  955.   var urn = this._getURNForLibraryResource(aResource);
  956.  
  957.   if (aResource instanceof Ci.sbILibrary) {
  958.     // For backwards compatibility, prefer the audio/video node for libraries
  959.     // over the container (if any of them is visible)
  960.     for each (let type in ["audio", "video", "podcast"]) {
  961.       let constrainedURN = urn + ":constraint(" + type + ")";
  962.       let node = this._servicePane.getNode(constrainedURN);
  963.       if (node && !node.hidden) {
  964.         return node;
  965.       }
  966.     }
  967.   }
  968.  
  969.   // For playlists and libraries that don't have any visible children, return
  970.   // the main node - even if it is hidden
  971.   return this._servicePane.getNode(urn);
  972. }
  973.  
  974. /**
  975.  * \brief Return all service pane nodes related to the library resource
  976.  *        specified by aResource.
  977.  *
  978.  * \param aResource an sbIMediaItem, sbIMediaItem, or sbILibrary
  979.  * \return An nsIArray of service pane nodes representing the given resource.
  980.  *         Note that in the case that more than one node is related to a given
  981.  *         library, multiple nodes will be returned.
  982.  */
  983. sbLibraryServicePane.prototype.getNodesForLibraryResource =
  984. function sbLibraryServicePane_getNodesForLibraryResource(aResource) {
  985.   let nodeList = [];
  986.   let urn = this._getURNForLibraryResource(aResource);
  987.   let node = this._servicePane.getNode(urn);
  988.   if (node)
  989.     nodeList.push(node);
  990.   if (aResource instanceof Ci.sbILibrary) {
  991.     for each (let type in ["audio", "video", "podcast"]) {
  992.       let constrainedURN = urn + ":constraint(" + type + ")";
  993.       node = this._servicePane.getNode(constrainedURN);
  994.       if (node)
  995.         nodeList.push(node);
  996.     }
  997.   }
  998.  
  999.   return ArrayConverter.nsIArray(nodeList);
  1000. }
  1001.  
  1002. /**
  1003.  * \brief Attempt to get a service pane node for the given media list view
  1004.  *
  1005.  * \param aMediaListView the view for which to get the service pane node
  1006.  * \returns a service pane node that represents the given media list view
  1007.  */
  1008. sbLibraryServicePane.prototype.getNodeFromMediaListView =
  1009. function sbLibraryServicePane_getNodeFromMediaListView(aMediaListView) {
  1010.   //logcall(arguments);
  1011.  
  1012.   // get the base URN...
  1013.   var urn = this._getURNForLibraryResource(aMediaListView.mediaList);
  1014.  
  1015.   var values = this._getConstraintsValueArrayFromMediaListView(aMediaListView);
  1016.   for (i = 0; i < values.length; ++i) {
  1017.     urn += ":constraint(" + values[i] + ")";
  1018.   }
  1019.  
  1020.   return this._servicePane.getNode(urn);
  1021. }
  1022.  
  1023. /**
  1024.  * \brief Attempt to get the content type of service pane node for the given
  1025.  *        media list view.
  1026.  *
  1027.  * \param aMediaListView the view for which to get the content type.
  1028.  * \returns the content type of service pane node that represents the given
  1029.  *          media list view or null if the type information is not available
  1030.  *          for the node.
  1031.  */
  1032. sbLibraryServicePane.prototype.getNodeContentTypeFromMediaListView =
  1033. function sbLibraryServicePane_getNodeContentTypeFromMediaListView(
  1034.                                   aMediaListView) {
  1035.   var values = this._getConstraintsValueArrayFromMediaListView(aMediaListView);
  1036.  
  1037.   const K_TYPES = ["audio", "video", "podcast"];
  1038.  
  1039.   for (i = 0; i < values.length; ++i) {
  1040.     if (K_TYPES.indexOf(values[i]) > -1)
  1041.       return values[i];
  1042.   }
  1043.  
  1044.   return null;
  1045. }
  1046.  
  1047. /* \brief Attempt to get a library resource for the given service pane node.
  1048.  *
  1049.  * Note that there is no guarantee that hidden service pane nodes
  1050.  * will have corresponding library resources
  1051.  *
  1052.  * \param aNode
  1053.  * \return a sbIMediaItem, sbIMediaItem, sbILibrary, or null
  1054.  */
  1055. sbLibraryServicePane.prototype.getLibraryResourceForNode =
  1056. function sbLibraryServicePane_getLibraryResourceForNode(aNode) {
  1057.   //logcall(arguments);
  1058.  
  1059.   // Must provide a node
  1060.   if (!(aNode instanceof Ci.sbIServicePaneNode)) {
  1061.     throw Components.results.NS_ERROR_INVALID_ARG;
  1062.   }
  1063.   // Must be initialized
  1064.   if (!this._libraryManager || !this._servicePane) {
  1065.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  1066.   }
  1067.  
  1068.   // If the node does not belong to us, then we aren't
  1069.   // going to find a resource
  1070.   if (aNode.contractid != CONTRACTID) {
  1071.     return null;
  1072.   }
  1073.  
  1074.   // Attempt to get a resource from the id of the given node
  1075.   var resource = this._getItemForURN(aNode.id);
  1076.   if (!resource) {
  1077.     resource = this._getLibraryForURN(aNode.id);
  1078.   }
  1079.  
  1080.   return resource;
  1081. }
  1082.  
  1083.  
  1084. /* \brief Set node read-only property.
  1085.  *
  1086.  * \param aNode Node to set.
  1087.  * \param aReadOnly If true, node is read-only.
  1088.  */
  1089. sbLibraryServicePane.prototype.setNodeReadOnly =
  1090. function sbLibraryServicePane_setNodeReadOnly(aNode, aReadOnly) {
  1091.   if (aReadOnly) {
  1092.     aNode.editable = false;
  1093.     aNode.setAttributeNS(LSP, "ReadOnly", "true");
  1094.   } else {
  1095.     aNode.editable = true;
  1096.     aNode.setAttributeNS(LSP, "ReadOnly", "false");
  1097.   }
  1098. }
  1099.  
  1100.  
  1101. /////////////////////
  1102. // Private Methods //
  1103. /////////////////////
  1104.  
  1105. sbLibraryServicePane.prototype._getConstraintsValueArrayFromMediaListView =
  1106. function sbLibraryServicePane__getConstraintsValueArrayFromMediaListView(
  1107.                                    aMediaListView) {
  1108.   var values = [];
  1109.  
  1110.   if ((aMediaListView instanceof Ci.sbIFilterableMediaListView) &&
  1111.       aMediaListView.filterConstraint)
  1112.   {
  1113.     // stash off the properties involved in the standard constraints,
  1114.     // so that we don't look at them
  1115.     var standardConstraintProperties = {};
  1116.     const standardConstraint = LibraryUtils.standardFilterConstraint;
  1117.     for (let group in ArrayConverter.JSEnum(standardConstraint.groups)) {
  1118.       group.QueryInterface(Ci.sbILibraryConstraintGroup);
  1119.       for (let prop in ArrayConverter.JSEnum(group.properties)) {
  1120.         standardConstraintProperties[prop] = true;
  1121.       }
  1122.     }
  1123.  
  1124.     for (let group in ArrayConverter.JSEnum(aMediaListView.filterConstraint.groups)) {
  1125.       group.QueryInterface(Ci.sbILibraryConstraintGroup);
  1126.       for (let prop in ArrayConverter.JSEnum(group.properties)) {
  1127.         if (prop in standardConstraintProperties) {
  1128.           continue;
  1129.         }
  1130.         for (let value in ArrayConverter.JSEnum(group.getValues(prop))) {
  1131.           values.push(value);
  1132.         }
  1133.       }
  1134.     }
  1135.   }
  1136.  
  1137.   return values;
  1138. }
  1139.  
  1140.  
  1141. /**
  1142.  * Return true if the given device supports playlist
  1143.  */
  1144. sbLibraryServicePane.prototype._doesDeviceSupportPlaylist =
  1145. function sbLibraryServicePane__doesDeviceSupportPlaylist(aDevice) {
  1146.   // Check the device capabilities to see if it supports playlists.
  1147.   // Device implementations may respond to CONTENT_PLAYLIST for either
  1148.   // FUNCTION_DEVICE or FUNCTION_AUDIO_PLAYBACK.
  1149.   var capabilities = aDevice.capabilities;
  1150.   var sbIDC = Ci.sbIDeviceCapabilities;
  1151.   try {
  1152.     if (capabilities.supportsContent(sbIDC.FUNCTION_DEVICE,
  1153.                                      sbIDC.CONTENT_PLAYLIST) ||
  1154.         capabilities.supportsContent(sbIDC.FUNCTION_AUDIO_PLAYBACK,
  1155.                                      sbIDC.CONTENT_PLAYLIST)) {
  1156.       return true;
  1157.     }
  1158.   } catch (e) {}
  1159.  
  1160.   // couldn't find PLAYLIST support in either the DEVICE
  1161.   // or AUDIO_PLAYBACK category
  1162.   return false;
  1163. }
  1164.  
  1165.  
  1166. /**
  1167.  * Return true if the given library supports the given list type
  1168.  */
  1169. sbLibraryServicePane.prototype._doesLibrarySupportListType =
  1170. function sbLibraryServicePane__doesLibrarySupportListType(aLibrary, aListType) {
  1171.   //logcall(arguments);
  1172.  
  1173.   var device;
  1174.   // Get device from non-device library will cause NS_ERROR_NOT_IMPLEMENTED.
  1175.   // Device library could also return NS_ERROR_UNEXPECTED on failure.
  1176.   try {
  1177.     device = aLibrary.device;
  1178.   } catch (e) {
  1179.     device = null;
  1180.   }
  1181.  
  1182.   // Check whether the device support playlist.
  1183.   if (device && !this._doesDeviceSupportPlaylist(device)) {
  1184.     return false;
  1185.   }
  1186.  
  1187.   // If the transfer policy indicates read only media lists, the library does
  1188.   // not support adding media lists of any type
  1189.   // XXXerik less than SUPER HACK to keep new playlists from being added to
  1190.   // device libraries.  This uses a hacked up policy system that will be
  1191.   // replaced by a real one.
  1192.   var transferPolicy = aLibrary.getProperty(SBProperties.transferPolicy);
  1193.   if (transferPolicy && transferPolicy.match(/readOnlyMediaLists/)) {
  1194.     return false;
  1195.   }
  1196.  
  1197.   // XXXben SUPER HACK to keep new playlists from being added to the web
  1198.   //        library. We should really fix this with our policy system.
  1199.   if (aLibrary.equals(LibraryUtils.webLibrary)) {
  1200.     return false;
  1201.   }
  1202.  
  1203.   var types = aLibrary.mediaListTypes;
  1204.   while (types.hasMore()) {
  1205.     if(aListType == types.getNext())  {
  1206.       return true;
  1207.     }
  1208.   }
  1209.   return false;
  1210. }
  1211.  
  1212.  
  1213. /**
  1214.  * Add all registered libraries to the service pane
  1215.  */
  1216. sbLibraryServicePane.prototype._addAllLibraries =
  1217. function sbLibraryServicePane__addAllLibraries() {
  1218.   //logcall(arguments);
  1219.   var libraries = this._libraryManager.getLibraries();
  1220.   while (libraries.hasMoreElements()) {
  1221.     var library = libraries.getNext();
  1222.     this._libraryAdded(library);
  1223.   }
  1224. }
  1225.  
  1226. /**
  1227. * Add all media lists found in the given library
  1228.  */
  1229. sbLibraryServicePane.prototype._processListsInLibrary =
  1230. function sbLibraryServicePane__processListsInLibrary(aLibrary) {
  1231.   //logcall(arguments);
  1232.  
  1233.   // Listener to receive enumerated items and store then in an array
  1234.   var listener = {
  1235.     items: [],
  1236.     onEnumerationBegin: function() { },
  1237.     onEnumerationEnd: function() { },
  1238.     onEnumeratedItem: function(list, item) {
  1239.       this.items.push(item);
  1240.     }
  1241.   };
  1242.  
  1243.   // Enumerate all lists in this library
  1244.   aLibrary.enumerateItemsByProperty(SBProperties.isList, "1",
  1245.                                     listener );
  1246.   
  1247.   // copy array of lists
  1248.   var remaining = listener.items.slice(0);
  1249.   
  1250.   // create nodes in the saved order, ignore guids with no
  1251.   // corresponding medialist and nodes whose guid isnt in the
  1252.   // saved order
  1253.   var appPrefs = Cc["@mozilla.org/fuel/application;1"]
  1254.                    .getService(Ci.fuelIApplication).prefs;
  1255.   var saved = appPrefs.getValue("songbird.library_listorder." + aLibrary.guid, "");
  1256.   var savedArray = saved.split(",");
  1257.   for each (var listguid in savedArray) {
  1258.     for (var i in remaining) {
  1259.       if (remaining[i].guid == listguid) {
  1260.         this._ensureMediaListNodeExists(remaining[i], true);
  1261.         remaining.slice(i, 1);
  1262.         break;
  1263.       }
  1264.     }
  1265.   }
  1266.   
  1267.   // Make sure we have a node for each remaining list. each node will be
  1268.   // inserted after the last node of the same type
  1269.   for (var i = 0; i < remaining.length; i++) {
  1270.     this._ensureMediaListNodeExists(remaining[i], false);
  1271.   }
  1272. }
  1273.  
  1274.  
  1275. /**
  1276.  * The given library has been added.  Show it in the service pane.
  1277.  */
  1278. sbLibraryServicePane.prototype._libraryAdded =
  1279. function sbLibraryServicePane__libraryAdded(aLibrary) {
  1280.   //logcall(arguments);
  1281.   var node = this._ensureLibraryNodeExists(aLibrary);
  1282.  
  1283.   // Listen to changes in the library so that we can display new playlists
  1284.   var filter = SBProperties.createArray([[SBProperties.hidden, null],
  1285.                                          [SBProperties.mediaListName, null]]);
  1286.   aLibrary.addListener(this,
  1287.                        false,
  1288.                        Ci.sbIMediaList.LISTENER_FLAGS_ALL,
  1289.                        filter);
  1290.   this._libraries.push(aLibrary);
  1291.  
  1292.   this._processListsInLibrary(aLibrary);
  1293.  
  1294.   return node;
  1295. }
  1296.  
  1297.  
  1298. /**
  1299.  * The given library has been removed.  Just hide the contents
  1300.  * rather than deleting so that if it is ever reattached
  1301.  * we will remember any ordering (drag-drop) information
  1302.  */
  1303. sbLibraryServicePane.prototype._libraryRemoved =
  1304. function sbLibraryServicePane__libraryRemoved(aLibrary) {
  1305.   //logcall(arguments);
  1306.  
  1307.   // Get the list of nodes for items within the library
  1308.   var libraryItemNodeList = this._servicePane.getNodesByAttributeNS
  1309.                                                 (LSP,
  1310.                                                  "LibraryGUID",
  1311.                                                  aLibrary.guid);
  1312.  
  1313.   // Hide all nodes for items within the library
  1314.   var libraryItemNodeEnum = libraryItemNodeList.enumerate();
  1315.   while (libraryItemNodeEnum.hasMoreElements()) {
  1316.     // Hide the library item node
  1317.     libraryItemNode =
  1318.       libraryItemNodeEnum.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1319.     libraryItemNode.hidden = true;
  1320.   }
  1321.  
  1322.   aLibrary.removeListener(this);
  1323.   this._libraries.splice(this._libraries.indexOf(aLibrary), 1);
  1324. }
  1325.  
  1326. sbLibraryServicePane.prototype._refreshLibraryNodes =
  1327. function sbLibraryServicePane__refreshLibraryNodes(aLibrary) {
  1328.   var id = this._libraryURN(aLibrary);
  1329.   var node = this._servicePane.getNode(id);
  1330.   this._scanForRemovedItems(aLibrary);
  1331.   this._ensureLibraryNodeExists(aLibrary);
  1332.   this._processListsInLibrary(aLibrary);
  1333. }
  1334.  
  1335. /**
  1336.  * The given media list has been added. Show it in the service pane.
  1337.  */
  1338. sbLibraryServicePane.prototype._playlistAdded =
  1339. function sbLibraryServicePane__playlistAdded(aMediaList) {
  1340.   //logcall(arguments);
  1341.   this._ensureMediaListNodeExists(aMediaList);
  1342. }
  1343.  
  1344.  
  1345. /**
  1346.  * The given media list has been removed. Delete the node, as it
  1347.  * is unlikely that the same playlist will come back again.
  1348.  */
  1349. sbLibraryServicePane.prototype._playlistRemoved =
  1350. function sbLibraryServicePane__playlistRemoved(aMediaList) {
  1351.   //logcall(arguments);
  1352.  
  1353.   var id = this._itemURN(aMediaList);
  1354.   var node = this._servicePane.getNode(id);
  1355.   if (node) {
  1356.     node.parentNode.removeChild(node);
  1357.   }
  1358. }
  1359.  
  1360.  
  1361. /**
  1362.  * The given media list has been updated.
  1363.  * The name and other properties may have changed.
  1364.  */
  1365. sbLibraryServicePane.prototype._mediaListUpdated =
  1366. function sbLibraryServicePane__mediaListUpdated(aMediaList) {
  1367.   //logcall(arguments);
  1368.   if (aMediaList instanceof Ci.sbILibrary) {
  1369.     this._ensureLibraryNodeExists(aMediaList);
  1370.   } else if (aMediaList instanceof Ci.sbIMediaList) {
  1371.     this._ensureMediaListNodeExists(aMediaList);
  1372.   }
  1373. }
  1374.  
  1375.  
  1376. /**
  1377.  * Get a service pane identifier for the given media item
  1378.  */
  1379. sbLibraryServicePane.prototype._itemURN =
  1380. function sbLibraryServicePane__itemURN(aMediaItem) {
  1381.   return URN_PREFIX_ITEM + aMediaItem.guid;
  1382. }
  1383.  
  1384.  
  1385. /**
  1386.  * Get a service pane identifier for the given library
  1387.  */
  1388. sbLibraryServicePane.prototype._libraryURN =
  1389. function sbLibraryServicePane__libraryURN(aLibrary) {
  1390.   return URN_PREFIX_LIBRARY + aLibrary.guid;
  1391. }
  1392.  
  1393.  
  1394. /**
  1395.  * Given a resource id, attempt to extract the GUID of a media item.
  1396.  */
  1397. sbLibraryServicePane.prototype._getItemGUIDForURN =
  1398. function sbLibraryServicePane__getItemGUIDForURN(aID) {
  1399.   //logcall(arguments);
  1400.   var index = aID.indexOf(URN_PREFIX_ITEM);
  1401.   if (index >= 0) {
  1402.     return aID.slice(URN_PREFIX_ITEM.length);
  1403.   }
  1404.   return null;
  1405. }
  1406.  
  1407.  
  1408. /**
  1409.  * Given a resource id, attempt to extract the GUID of a library.
  1410.  */
  1411. sbLibraryServicePane.prototype._getLibraryGUIDForURN =
  1412. function sbLibraryServicePane__getLibraryGUIDForURN(aID) {
  1413.   //logcall(arguments);
  1414.   if (aID.substring(0, URN_PREFIX_LIBRARY.length) != URN_PREFIX_LIBRARY) {
  1415.     return null;
  1416.   }
  1417.   var id = aID.slice(URN_PREFIX_LIBRARY.length);
  1418.   id = id.replace(/:constraint\(.*?\)/g, '');
  1419.   return id;
  1420. }
  1421.  
  1422.  
  1423. /**
  1424.  * Given a resource id, attempt to get an sbIMediaItem.
  1425.  * This is the inverse of _itemURN
  1426.  */
  1427. sbLibraryServicePane.prototype._getItemForURN =
  1428. function sbLibraryServicePane__getItemForURN(aID) {
  1429.   //logcall(arguments);
  1430.   var guid = this._getItemGUIDForURN(aID);
  1431.   if (guid) {
  1432.     var node = this._servicePane.getNode(aID);
  1433.     var libraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  1434.     if (libraryGUID) {
  1435.       try {
  1436.         var library = this._libraryManager.getLibrary(libraryGUID);
  1437.         return library.getMediaItem(guid);
  1438.       } catch (e) {
  1439.         LOG("sbLibraryServicePane__getItemForURN: error trying to get medialist " +
  1440.              guid + " from library " + libraryGUID);
  1441.       }
  1442.     }
  1443.  
  1444.     // URNs of visible nodes in the servicetree should always refer
  1445.     // to an existing media item...
  1446.     LOG("sbLibraryServicePane__getItemForURN: could not find a mediaItem " +
  1447.          "for URN " + aID + ". The service pane must be out of sync with " +
  1448.          "the libraries!");
  1449.   }
  1450.   return null;
  1451. }
  1452.  
  1453.  
  1454. /**
  1455.  * Given a resource id, attempt to get an sbILibrary.
  1456.  * This is the inverse of _libraryURN
  1457.  */
  1458. sbLibraryServicePane.prototype._getLibraryForURN =
  1459. function sbLibraryServicePane__getLibraryForURN(aID) {
  1460.   //logcall(arguments);
  1461.   var guid = this._getLibraryGUIDForURN(aID);
  1462.   if (guid) {
  1463.     try {
  1464.       return this._libraryManager.getLibrary(guid);
  1465.     }
  1466.     catch (e) {
  1467.       LOG("sbLibraryServicePane__getLibraryForURN: error trying to get " +
  1468.           "library " + guid);
  1469.     }
  1470.   }
  1471.   return null;
  1472. }
  1473.  
  1474.  
  1475.  
  1476. /**
  1477.  * Ensure the library nodes for the given library exists, then return the
  1478.  * container node
  1479.  */
  1480. sbLibraryServicePane.prototype._ensureLibraryNodeExists =
  1481. function sbLibraryServicePane__ensureLibraryNodeExists(aLibrary) {
  1482.   //logcall(arguments);
  1483.   var self = this;
  1484.  
  1485.   /**
  1486.    * Find or create a library node for the given library, for the given
  1487.    * constraint type
  1488.    * @param aLibrary the library to find / create a node for
  1489.    * @param aConstraintType the type of library node, e.g. "audio", "video",
  1490.    *                        "podcast" - if not given, no constraint applies
  1491.    * @returns the node (old or new) for the library of the given type
  1492.    */
  1493.   function makeNodeFromLibrary(aLibrary, aConstraintType, aParentNode) {
  1494.     var id = self._libraryURN(aLibrary);
  1495.     if (aConstraintType) {
  1496.       // add a constraint only if the constraint type was specified
  1497.       id += ":constraint(" + aConstraintType + ")";
  1498.     }
  1499.     var node = self._servicePane.getNode(id);
  1500.     if (!node) {
  1501.       // Create the node
  1502.       node = self._servicePane.createNode();
  1503.       node.id = id;
  1504.       node.contractid = CONTRACTID;
  1505.       node.editable = false;
  1506.  
  1507.       // Set properties for styling purposes
  1508.       self._addClassNames(node,
  1509.                           ["library",
  1510.                            "libraryguid-" + aLibrary.guid,
  1511.                            aLibrary.type,
  1512.                            customType]);
  1513.       // Save the type of media list so that we can group by type
  1514.       node.setAttributeNS(LSP, "ListType", aLibrary.type)
  1515.       // Save the guid of the library
  1516.       node.setAttributeNS(LSP, "LibraryGUID", aLibrary.guid);
  1517.       // and save it as the list guid
  1518.       node.setAttributeNS(LSP, "ListGUID", aLibrary.guid);
  1519.       // Save the customType for use by metrics.
  1520.       node.setAttributeNS(LSP, "ListCustomType", customType);
  1521.       // Save the customType for use by metrics.
  1522.       node.setAttributeNS(LSP, "LibraryCustomType", customType);
  1523.   
  1524.       if (aConstraintType) {
  1525.         self._addClassNames(node, [aConstraintType]);
  1526.         var builder = Cc["@songbirdnest.com/Songbird/Library/ConstraintBuilder;1"]
  1527.                         .createInstance(Ci.sbILibraryConstraintBuilder);
  1528.         builder.includeConstraint(LibraryUtils.standardFilterConstraint);
  1529.         builder.intersect();
  1530.         builder.include(SBProperties.contentType, aConstraintType);
  1531.         node.setAttributeNS(SP,
  1532.                             "mediaListViewConstraints",
  1533.                             builder.get());
  1534.       }
  1535.     }
  1536.     var customType = aLibrary.getProperty(SBProperties.customType);
  1537.  
  1538.     // Refresh the information just in case it is supposed to change
  1539.     // Don't set name if it hasn't changed to avoid a UI redraw
  1540.     let name = (aConstraintType ? '&servicesource.library.' + aConstraintType :
  1541.                                   aLibrary.name);
  1542.     if (node.name != name) {
  1543.       node.name = name;
  1544.     }
  1545.     var hidden = (aLibrary.getProperty(SBProperties.hidden) == "1");
  1546.     node.hidden = hidden;
  1547.  
  1548.     if (aParentNode && !node.parentNode) {
  1549.       // Insert the node as first child
  1550.       aParentNode.insertBefore(node, aParentNode.firstChild);
  1551.     }
  1552.     return node;
  1553.   }
  1554.  
  1555.   var customType = aLibrary.getProperty(SBProperties.customType);
  1556.  
  1557.   if (customType == 'web') {
  1558.     // the web library has no video/audio split, nor a parent, so we need to
  1559.     // special case it here and return early
  1560.     let node = makeNodeFromLibrary(aLibrary, null, null);
  1561.  
  1562.     // Set the weight of the web library
  1563.     node.setAttributeNS(SP, 'Weight', 5);
  1564.     node.hidden = true;
  1565.  
  1566.     if (!node.parentNode)
  1567.       this._servicePane.root.appendChild(node);
  1568.  
  1569.     return node;
  1570.   }
  1571.  
  1572.   // make the parent node
  1573.   var id = self._libraryURN(aLibrary);
  1574.   var parentNode = self._servicePane.getNode(id);
  1575.   if (!parentNode) {
  1576.     // Create the node
  1577.     parentNode = this._servicePane.createNode();
  1578.     parentNode.id = id;
  1579.     parentNode.editable = false;
  1580.     parentNode.setAttributeNS(SP, 'Weight', -4);
  1581.  
  1582.     // uncomment this to cause clicks on the container node to load an unfiltered
  1583.     // library.  we need to think more about the ramifications for UE before
  1584.     // turning this on, so it's off for now.
  1585.     // (see also below on the migration part)
  1586.     //parentNode.contractid = CONTRACTID;
  1587.  
  1588.     // class names that should exist on the parent container node
  1589.     const K_PARENT_PROPS = ["folder", "library-container"];
  1590.   
  1591.     self._addClassNames(parentNode, K_PARENT_PROPS);
  1592.  
  1593.     if (aLibrary != this._libraryManager.mainLibrary) {
  1594.       // always create them as hidden
  1595.       parentNode.hidden = true;
  1596.     }
  1597.   }
  1598.  
  1599.   // Refresh the information just in case it is supposed to change
  1600.   // Don't set name if it hasn't changed to avoid a UI redraw
  1601.   if (parentNode.name != aLibrary.name) {
  1602.     parentNode.name = aLibrary.name;
  1603.   }
  1604.  
  1605.   if (aLibrary == this._libraryManager.mainLibrary) {
  1606.     for each (let type in ["video", "audio"]) {
  1607.       let node = makeNodeFromLibrary(aLibrary, type, parentNode);
  1608.     }
  1609.  
  1610.     // the main library uses a separate Playlists and Podcasts folder
  1611.     this._ensurePlaylistFolderExists();
  1612.     this._ensurePodcastFolderExists();
  1613.  
  1614.     // if the iTunes folder exists, then make it visible
  1615.     var fnode = this._servicePane.getNode('SB:iTunes');
  1616.     if (fnode)
  1617.       fnode.hidden = false;
  1618.   }
  1619.   else {
  1620.     for each (let type in ["video", "audio"]) {
  1621.       let node = makeNodeFromLibrary(aLibrary, type, parentNode);
  1622.  
  1623.       // other libraries store the playlists under them, but only
  1624.       // assign the default value if they do not specifically tell
  1625.       // us not to do so
  1626.  
  1627.       if (node.getAttributeNS(SP,'dndCustomAccept') != 'true')
  1628.         node.dndAcceptIn = 'text/x-sb-playlist-'+aLibrary.guid;
  1629.     }
  1630.   }
  1631.  
  1632.   if (!parentNode.parentNode) {
  1633.     // Append library to the root, service pane will sort by weight
  1634.     this._servicePane.root.appendChild(parentNode);
  1635.   }
  1636.  
  1637.   return parentNode;
  1638. }
  1639.  
  1640.  
  1641. /**
  1642.  * Get the service pane node for the given media list,
  1643.  * creating one if none exists.
  1644.  */
  1645. sbLibraryServicePane.prototype._ensureMediaListNodeExists =
  1646. function sbLibraryServicePane__ensureMediaListNodeExists(aMediaList, aAppend) {
  1647.   //logcall(arguments);
  1648.  
  1649.   var id = this._itemURN(aMediaList);
  1650.   var node = this._servicePane.getNode(id);
  1651.  
  1652.   var customType = aMediaList.getProperty(SBProperties.customType);
  1653.   var libCustomType = aMediaList.library.getProperty(SBProperties.customType);
  1654.  
  1655.   if (!node) {
  1656.     // Create the node
  1657.     // NOTE: it's a container for drag and drop purposes only.
  1658.     node = this._servicePane.createNode();
  1659.     node.id = id;
  1660.     node.contractid = CONTRACTID;
  1661.  
  1662.     if (customType == 'download') {
  1663.       // the download media list isn't editable
  1664.       node.editable = false;
  1665.       // set the weight of the downloads list
  1666.       node.setAttributeNS(SP, 'Weight', 999);
  1667.     } else {
  1668.       // the rest are, but only if the items themselves are not readonly
  1669.       node.editable = aMediaList.userEditable;
  1670.     }
  1671.  
  1672.     // Set properties for styling purposes
  1673.     if (aMediaList.getProperty(SBProperties.isSubscription) == "1") {
  1674.       this._addClassNames(node, ["medialist", "medialisttype-dynamic"]);
  1675.       node.setAttributeNS(LSP, "ListSubscription", "1");
  1676.     } else {
  1677.       this._addClassNames(node, ["medialist medialisttype-" + aMediaList.type]);
  1678.       node.setAttributeNS(LSP, "ListSubscription", "0");
  1679.     }
  1680.     // Add the customType to the properties to encourage people to set it for CSS
  1681.     this._addClassNames(node, [customType]);
  1682.     // Save the type of media list so that we can group by type
  1683.     node.setAttributeNS(LSP, "ListType", aMediaList.type);
  1684.     // Save the guid of the library that owns this media list
  1685.     node.setAttributeNS(LSP, "LibraryGUID", aMediaList.library.guid);
  1686.     // and the guid of this list
  1687.     node.setAttributeNS(LSP, "ListGUID", aMediaList.guid);
  1688.     // Save the parent library custom type for this list.
  1689.     node.setAttributeNS(LSP, "LibraryCustomType", libCustomType);
  1690.     // Save the list customType for use by metrics.
  1691.     node.setAttributeNS(LSP, "ListCustomType", customType);
  1692.  
  1693.     // if auto dndAcceptIn/Near hasn't been disabled, assign it now
  1694.     if (node.getAttributeNS(SP,'dndCustomAccept') != 'true') {
  1695.       if (aMediaList.library == this._libraryManager.mainLibrary) {
  1696.         // a playlist in the main library is considered a toplevel node
  1697.         // (unless it's the download playlist)
  1698.         if (customType != 'download') {
  1699.           node.dndDragTypes = 'text/x-sb-playlist';
  1700.           node.dndAcceptNear = 'text/x-sb-playlist';
  1701.         }
  1702.       } else {
  1703.         // playlists in other libraries can only go into their libraries' nodes
  1704.         node.dndDragTypes = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1705.         node.dndAcceptNear = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1706.       }
  1707.     }
  1708.   }
  1709.  
  1710.   // Refresh the information just in case it is supposed to change
  1711.   // Don't set name if it hasn't changed to avoid a UI redraw
  1712.   if (node.name != aMediaList.name) {
  1713.     node.name = aMediaList.name;
  1714.   }
  1715.  
  1716.   // Get hidden state from list
  1717.   var hidden = (aMediaList.getProperty(SBProperties.hidden) == "1");
  1718.   node.hidden = hidden;
  1719.  
  1720.   if (!node.parentNode) {
  1721.     // Place the node in the tree
  1722.     this._insertMediaListNode(node, aMediaList, aAppend);
  1723.   }
  1724.  
  1725.   return node;
  1726. }
  1727.  
  1728. /**
  1729.  * Get the service pane node for the Playlists folder (which contains all
  1730.  * the playlists in the main library).
  1731.  */
  1732. sbLibraryServicePane.prototype._ensurePlaylistFolderExists =
  1733. function sbLibraryServicePane__ensurePlaylistFolderExists() {
  1734.   let fnode = this._servicePane.getNode("SB:Playlists");
  1735.   if (!fnode) {
  1736.     // make sure it exists
  1737.     fnode = this._servicePane.createNode();
  1738.     fnode.id = "SB:Playlists";
  1739.     fnode.name = '&servicesource.playlists';
  1740.     this._addClassNames(fnode, ["folder", this._makeCSSProperty(fnode.name)]);
  1741.     fnode.contractid = CONTRACTID;
  1742.     fnode.dndAcceptIn = 'text/x-sb-playlist';
  1743.     fnode.editable = false;
  1744.     fnode.setAttributeNS(SP, 'Weight', 3);
  1745.     this._servicePane.root.appendChild(fnode);
  1746.   }
  1747.  
  1748.   return fnode;
  1749. }
  1750.  
  1751. /**
  1752.  * Get the service pane node for the Podcasts folder (which contains all
  1753.  * the podcasts in the main library).
  1754.  */
  1755. sbLibraryServicePane.prototype._ensurePodcastFolderExists =
  1756. function sbLibraryServicePane__ensurePodcastFolderExists() {
  1757.   // Return null per bug 17607.  Return value should not get used since podcasts
  1758.   // can no longer be created.
  1759.   return null;
  1760.  
  1761.   let fnode = this._servicePane.getNode("SB:Podcasts");
  1762.   if (!fnode) {
  1763.     // make sure it exists
  1764.     fnode = this._servicePane.createNode();
  1765.     fnode.id = "SB:Podcasts";
  1766.     fnode.name = "&servicesource.podcasts";
  1767.     this._addClassNames(fnode, ["folder", this._makeCSSProperty(fnode.name)]);
  1768.     fnode.contractid = CONTRACTID;
  1769.     fnode.editable = false;
  1770.     fnode.setAttributeNS(SP, "Weight", 2);
  1771.     this._servicePane.root.appendChild(fnode);
  1772.   }
  1773.   return fnode;
  1774. }
  1775.  
  1776. /**
  1777.  * Get the service pane node for the iTunes folder (which contains all
  1778.  * the imported iTunes playlists in the main library).
  1779.  */
  1780. sbLibraryServicePane.prototype._ensureiTunesFolderExists =
  1781. function sbLibraryServicePane__ensureiTunesFolderExists() {
  1782.   let fnode = this._servicePane.getNode("SB:iTunes");
  1783.   if (!fnode) {
  1784.     // make sure it exists
  1785.     fnode = this._servicePane.createNode();
  1786.     fnode.id = "SB:iTunes";
  1787.     fnode.name = '&servicesource.itunes';
  1788.     this._addClassNames(fnode, ["folder", this._makeCSSProperty(fnode.name)]);
  1789.     fnode.contractid = CONTRACTID;
  1790.     fnode.editable = false;
  1791.     fnode.setAttributeNS(SP, 'Weight', 3);
  1792.     this._servicePane.root.appendChild(fnode);
  1793.   }
  1794.   return fnode;
  1795. }
  1796.  
  1797. sbLibraryServicePane.prototype._scanForRemovedItems =
  1798. function sbLibraryServicePane__scanForRemovedItems(aLibrary) {
  1799.   // Get the list of nodes for items within the library
  1800.   var libraryItemNodeList = this._servicePane.getNodesByAttributeNS
  1801.                                                 (LSP,
  1802.                                                  "LibraryGUID",
  1803.                                                  aLibrary.guid);
  1804.  
  1805.   // Remove nodes whose items no longer exist
  1806.   var libraryItemNodeEnum = libraryItemNodeList.enumerate();
  1807.   while (libraryItemNodeEnum.hasMoreElements()) {
  1808.     // Get the library item node
  1809.     libraryItemNode =
  1810.       libraryItemNodeEnum.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1811.  
  1812.     // Skip library nodes
  1813.     if (this._nodeIsLibrary(libraryItemNode))
  1814.       continue;
  1815.  
  1816.     // Remove node if item no longer exists
  1817.     var mediaItem = this._getItemForURN(libraryItemNode.id);
  1818.     if (!mediaItem)
  1819.       libraryItemNode.parentNode.removeChild(libraryItemNode);
  1820.   }
  1821. }
  1822.  
  1823. /**
  1824.  * Logic to determine where a media list node should appear
  1825.  * in the service pane tree
  1826.  */
  1827. sbLibraryServicePane.prototype._insertMediaListNode =
  1828. function sbLibraryServicePane__insertMediaListNode(aNode, aMediaList, aAppend) {
  1829.   //logcall(arguments);
  1830.  
  1831.   // If it is a main library media list, it belongs in either the
  1832.   // "Playlists" or "iTunes" folder, depending on whether it was imported
  1833.   // from iTunes or not
  1834.   if (aMediaList.library == this._libraryManager.mainLibrary)
  1835.   {
  1836.     // the download playlist is a special case
  1837.     if (aNode.getAttributeNS(LSP, 'ListCustomType') == 'download') {
  1838.       // Fix it to the bottom of the library node
  1839.       var libraryNode = this.getNodeForLibraryResource(aMediaList.library);
  1840.  
  1841.       // getNodeForLibraryResource will usually return a child of the library
  1842.       // container
  1843.       if (libraryNode.parentNode != this._servicePane.root)
  1844.         libraryNode = libraryNode.parentNode;
  1845.  
  1846.       libraryNode.appendChild(aNode);
  1847.     } else {
  1848.       // make sure the playlist folder exists
  1849.       var folder;
  1850.       // if it has an iTunesGUID property, it's imported from iTunes
  1851.       if (aMediaList.getProperty(SBProperties.iTunesGUID) != null) {
  1852.         folder = this._ensureiTunesFolderExists();
  1853.       } else if (aMediaList.getProperty(SBProperties.customType) == "podcast") {
  1854.         folder = this._ensurePodcastFolderExists();
  1855.       } else {
  1856.         folder = this._ensurePlaylistFolderExists();
  1857.       }
  1858.  
  1859.       if (aAppend)
  1860.         folder.appendChild(aNode);
  1861.       else  
  1862.         this._insertAfterLastOfSameType(aNode, folder);
  1863.     }
  1864.   }
  1865.   // If it is a secondary library playlist, it should be
  1866.   // added as a child of that library
  1867.   else
  1868.   {
  1869.     // Find the parent libary in the tree
  1870.     var parentLibraryNode = this.getNodeForLibraryResource(aMediaList.library);
  1871.  
  1872.     // getNodeForLibraryResource will usually return a child of the library
  1873.     // container
  1874.     if (parentLibraryNode && parentLibraryNode.parentNode != this._servicePane.root)
  1875.       parentLibraryNode = parentLibraryNode.parentNode;
  1876.  
  1877.     // If we found a parent library node make the playlist node its child
  1878.     if (parentLibraryNode) {
  1879.       if (aAppend)
  1880.         parentLibraryNode.appendChild(aNode);
  1881.       else
  1882.         this._insertAfterLastOfSameType(aNode, parentLibraryNode);
  1883.     } else {
  1884.       LOG("sbLibraryServicePane__insertMediaListNode: could not add media list to parent library");
  1885.       this._servicePane.root.appendChild(aNode);
  1886.     }
  1887.   }
  1888. }
  1889.  
  1890. /**
  1891.  * Inserts the given node under the given parent and attempts to keep
  1892.  * children grouped by type.  The node is inserted at the end of the list
  1893.  * or next to the last child of the same type as the given node.
  1894.  */
  1895. sbLibraryServicePane.prototype._insertAfterLastOfSameType =
  1896. function sbLibraryServicePane__insertAfterLastOfSameType(aNode, aParent) {
  1897.   //logcall(arguments);
  1898.  
  1899.   function getNodeType(aNode) {
  1900.     let type = aNode.getAttributeNS(LSP, "ListType");
  1901.     if (type == "simple" && aNode.getAttributeNS(LSP, "ListSubscription") == "1")
  1902.       return "subscription";
  1903.     else
  1904.       return type;
  1905.   }
  1906.  
  1907.   // Try to insert after last of same type
  1908.   let nodeType = getNodeType(aNode);
  1909.   let insertionPoint = null;
  1910.   let found = false;
  1911.   for (let child = aParent.lastChild; child; child = child.previousSibling) {
  1912.     if (child.hidden)
  1913.       continue;
  1914.     // Stop at a node that belongs to the library service pane and has the
  1915.     // same list type as the node we are inserting
  1916.     if (child.contractid == CONTRACTID && getNodeType(child) == nodeType) {
  1917.       found = true;
  1918.       break;
  1919.     }
  1920.  
  1921.     insertionPoint = child;
  1922.   }
  1923.  
  1924.   // Insert the new list after the last of the same type
  1925.   // or at the end of the list
  1926.   if (found) {
  1927.     aParent.insertBefore(aNode, insertionPoint);
  1928.   } else {
  1929.     aParent.appendChild(aNode);
  1930.   }
  1931.  
  1932.   // Ensure that all parent containers are open and the node is visible
  1933.   for (let parent = aParent; parent; parent = parent.parentNode)
  1934.     if (!parent.isOpen)
  1935.       parent.isOpen = true;
  1936. }
  1937.  
  1938. sbLibraryServicePane.prototype._appendMenuItem =
  1939. function sbLibraryServicePane__appendMenuItem(aContextMenu, aLabel, aCallback) {
  1940.   var item = aContextMenu.ownerDocument.createElement("menuitem");
  1941.   item.setAttribute("label", aLabel);
  1942.   item.addEventListener("command", aCallback, false);
  1943.   aContextMenu.appendChild(item);
  1944. }
  1945.  
  1946. sbLibraryServicePane.prototype._appendCommands =
  1947. function sbLibraryServicePane__appendCommands(aContextMenu, aList, aParentWindow) {
  1948.   if (this._lastMenuitems && this._lastMenuitems.destroy) {
  1949.     var pnode = this._lastMenuitems.parentNode;
  1950.     this._lastMenuitems.destroy();
  1951.     this._lastMenuitems = null;
  1952.   }
  1953.   var itemBuilder = aContextMenu.ownerDocument.createElement("sb-commands-menuitems");
  1954.   itemBuilder.setAttribute("id", "playlist-commands");
  1955.   itemBuilder.setAttribute("commandtype", "medialist");
  1956.   itemBuilder.setAttribute("bind", aList.library.guid + ';' + aList.guid);
  1957.   aContextMenu.appendChild(itemBuilder);
  1958.   this._lastMenuitems = itemBuilder;
  1959. }
  1960.  
  1961. /**
  1962.  * This function is a recursive helper for onListCleared (below) that will
  1963.  * remove all the playlist nodes for a given library.
  1964.  */
  1965. sbLibraryServicePane.prototype._removeListNodesForLibrary =
  1966. function sbLibraryServicePane__removeListNodesForLibrary(aStartNode, aLibraryGUID) {
  1967.  
  1968.   var node = aStartNode.firstChild;
  1969.  
  1970.   while (node) {
  1971.  
  1972.     this._removeListNodesForLibrary(node, aLibraryGUID);
  1973.  
  1974.     var nextSibling = node.nextSibling;
  1975.  
  1976.     if (this._getItemGUIDForURN(node.id)) {
  1977.       var nodeLibraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  1978.       if (nodeLibraryGUID == aLibraryGUID) {
  1979.         node.parentNode.removeChild(node);
  1980.       }
  1981.     }
  1982.  
  1983.     node = nextSibling;
  1984.   }
  1985. }
  1986.  
  1987. sbLibraryServicePane.prototype._addClassNames =
  1988. function sbLibraryServicePane__addClassNames(aNode, aList) {
  1989.   let className = aNode.className || "";
  1990.   let existing = {};
  1991.   for each (let name in className.split(" "))
  1992.     existing[name] = true;
  1993.  
  1994.   for each (let name in aList)
  1995.     if (!existing.hasOwnProperty(name))
  1996.       className += (className ? " " : "") + name;
  1997.  
  1998.   aNode.className = className;
  1999. }
  2000.  
  2001. /**
  2002.  * Turn a partial entity (&foo.bar) into a css property string (foo-bar),
  2003.  * but leaves other strings as they are.
  2004.  */
  2005. sbLibraryServicePane.prototype._makeCSSProperty = 
  2006. function sbLibraryServicePane__makeCSSProperty(aString) {
  2007.   if ( aString[0] == "&" ) {
  2008.     aString = aString.substr(1, aString.length);
  2009.     aString = aString.replace(/\./g, "-");
  2010.   }
  2011.   return aString;
  2012. }
  2013.  
  2014. ///////////////////////////////
  2015. // sbILibraryManagerListener //
  2016. ///////////////////////////////
  2017.  
  2018. sbLibraryServicePane.prototype.onLibraryRegistered =
  2019. function sbLibraryServicePane_onLibraryRegistered(aLibrary) {
  2020.   //logcall(arguments);
  2021.   this._libraryAdded(aLibrary);
  2022. }
  2023. sbLibraryServicePane.prototype.onLibraryUnregistered =
  2024. function sbLibraryServicePane_onLibraryUnregistered(aLibrary) {
  2025.   //logcall(arguments);
  2026.   this._libraryRemoved(aLibrary);
  2027. }
  2028.  
  2029. //////////////////////////
  2030. // sbIMediaListListener //
  2031. //////////////////////////
  2032.  
  2033. sbLibraryServicePane.prototype.onItemAdded =
  2034. function sbLibraryServicePane_onItemAdded(aMediaList, aMediaItem, aIndex) {
  2035.   //logcall(arguments);
  2036.   if (this._batch[aMediaList.guid] && this._batch[aMediaList.guid].isActive()) {
  2037.     // We are going to refresh all the nodes once we exit the batch so
  2038.     // we don't need any more of these notifications
  2039.     this._refreshPending = true;
  2040.     return true;
  2041.   }
  2042.   else {
  2043.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  2044.     if (isList) {
  2045.       this._playlistAdded(aMediaItem);
  2046.     }
  2047.     return false;
  2048.   }
  2049. }
  2050. sbLibraryServicePane.prototype.onBeforeItemRemoved =
  2051. function sbLibraryServicePane_onBeforeItemRemoved(aMediaList, aMediaItem, aIndex) {
  2052.   return true;
  2053. }
  2054. sbLibraryServicePane.prototype.onAfterItemRemoved =
  2055. function sbLibraryServicePane_onAfterItemRemoved(aMediaList, aMediaItem, aIndex) {
  2056.   //logcall(arguments);
  2057.   if (this._batch[aMediaList.guid] && this._batch[aMediaList.guid].isActive()) {
  2058.     // We are going to refresh all the nodes once we exit the batch so
  2059.     // we don't need any more of these notifications
  2060.     this._refreshPending = true;
  2061.     return true;
  2062.   }
  2063.   else {
  2064.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  2065.     if (isList) {
  2066.       this._playlistRemoved(aMediaItem);
  2067.     }
  2068.     return false;
  2069.   }
  2070. }
  2071. sbLibraryServicePane.prototype.onItemUpdated =
  2072. function sbLibraryServicePane_onItemUpdated(aMediaList,
  2073.                                             aMediaItem,
  2074.                                             aProperties) {
  2075.   if (this._batch[aMediaList.guid] && this._batch[aMediaList.guid].isActive()) {
  2076.     // We are going to refresh all the nodes once we exit the batch so
  2077.     // we don't need any more of these notifications
  2078.     this._refreshPending = true;
  2079.     return true;
  2080.   }
  2081.   else {
  2082.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  2083.     if (isList) {
  2084.       this._mediaListUpdated(aMediaItem);
  2085.     }
  2086.     return false;
  2087.   }
  2088. }
  2089. sbLibraryServicePane.prototype.onItemMoved =
  2090. function sbLibraryServicePane_onItemMoved(aMediaList,
  2091.                                           aFromIndex,
  2092.                                           aToIndex) {
  2093.   return true;
  2094. }
  2095. sbLibraryServicePane.prototype.onBeforeListCleared =
  2096. function sbLibraryServicePane_onBeforeListCleared(aMediaList,
  2097.                                                   aExcludeLists) {
  2098.   return true;
  2099. }
  2100. sbLibraryServicePane.prototype.onListCleared =
  2101. function sbLibraryServicePane_onListCleared(aMediaList,
  2102.                                             aExcludeLists) {
  2103.   if (this._batch[aMediaList.guid] && this._batch[aMediaList.guid].isActive()) {
  2104.     // We are going to refresh all the nodes once we exit the batch so
  2105.     // we don't need any more of these notifications
  2106.     this._refreshPending = true;
  2107.     return true;
  2108.   }
  2109.   else {
  2110.     if (aMediaList instanceof Ci.sbILibrary) {
  2111.       var libraryGUID = aMediaList.guid;
  2112.  
  2113.       var node = this._servicePane.root;
  2114.       this._removeListNodesForLibrary(node, libraryGUID);
  2115.     }
  2116.     return false;
  2117.   }
  2118. }
  2119. sbLibraryServicePane.prototype.onBatchBegin =
  2120. function sbLibraryServicePane_onBatchBegin(aMediaList) {
  2121.   //logcall(arguments);
  2122.   if (!this._batch[aMediaList.guid]) {
  2123.     this._batch[aMediaList.guid] = new LibraryUtils.BatchHelper();
  2124.   }
  2125.   this._batch[aMediaList.guid].begin();
  2126. }
  2127. sbLibraryServicePane.prototype.onBatchEnd =
  2128. function sbLibraryServicePane_onBatchEnd(aMediaList) {
  2129.   //logcall(arguments);
  2130.   if (!this._batch[aMediaList.guid]) {
  2131.     return;
  2132.   }
  2133.  
  2134.   this._batch[aMediaList.guid].end();
  2135.   if (!this._batch[aMediaList.guid].isActive() && this._refreshPending) {
  2136.     this._refreshLibraryNodes(aMediaList);
  2137.     this._refreshPending = false;
  2138.   }
  2139.  
  2140. }
  2141.  
  2142. sbLibraryServicePane.prototype._initLibraryManager =
  2143. function sbLibraryServicePane__initLibraryManager() {
  2144.   // get the library manager
  2145.   this._libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  2146.                            .getService(Ci.sbILibraryManager);
  2147.  
  2148.   // register for notifications so that we can keep the service pane
  2149.   // in sync with the the libraries
  2150.   this._libraryManager.addListener(this);
  2151.  
  2152.   // Make sure to remove the library manager listener on shutdown
  2153.   var obs = Cc["@mozilla.org/observer-service;1"].
  2154.             getService(Ci.nsIObserverService);
  2155.   obs.addObserver(this, "songbird-library-manager-before-shutdown", false);
  2156.  
  2157.   this._addAllLibraries();
  2158. }
  2159.  
  2160. /////////////////
  2161. // nsIObserver //
  2162. /////////////////
  2163.  
  2164. sbLibraryServicePane.prototype.observe =
  2165. function sbLibraryServicePane_observe(subject, topic, data) {
  2166.  
  2167.   var obs = Cc["@mozilla.org/observer-service;1"]
  2168.               .getService(Ci.nsIObserverService);
  2169.  
  2170.   if (topic == "songbird-library-manager-before-shutdown") {
  2171.     obs.removeObserver(this, "songbird-library-manager-before-shutdown");
  2172.  
  2173.     var libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  2174.                            .getService(Ci.sbILibraryManager);
  2175.     libraryManager.removeListener(this);
  2176.     for each (let lib in this._libraries) {
  2177.       lib.removeListener(this);
  2178.     }
  2179.   }
  2180. }
  2181.  
  2182. ///////////
  2183. // XPCOM //
  2184. ///////////
  2185.  
  2186. var NSGetModule = XPCOMUtils.generateNSGetModule([sbLibraryServicePane]);
  2187.