home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / components / sbLibraryServicePaneService.js < prev    next >
Text File  |  2012-06-08  |  74KB  |  2,188 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, aDataTransfer) {
  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.   aDataTransfer.setData(TYPE_X_SB_TRANSFER_MEDIA_LIST, handle);
  725.  
  726.   return true;
  727. }
  728.  
  729.  
  730. /**
  731.  * Called when the user is about to attempt to rename a library/medialist node
  732.  */
  733. sbLibraryServicePane.prototype.onBeforeRename =
  734. function sbLibraryServicePane_onBeforeRename(aNode) {
  735. }
  736.  
  737. /**
  738.  * Called when the user has attempted to rename a library/medialist node
  739.  */
  740. sbLibraryServicePane.prototype.onRename =
  741. function sbLibraryServicePane_onRename(aNode, aNewName) {
  742.   //logcall(arguments);
  743.   if (aNode && aNewName) {
  744.     var libraryResource = this.getLibraryResourceForNode(aNode);
  745.     libraryResource.name = aNewName;
  746.     aNode.name = aNewName;
  747.   }
  748. }
  749.  
  750.  
  751. //////////////////////////////////
  752. // sbILibraryServicePaneService //
  753. //////////////////////////////////
  754.  
  755.  
  756. /* \brief Suggest a library for creating a new media list
  757.  *
  758.  * \param aMediaListType string identifying a media list type, eg "simple"
  759.  * \param aNode A service pane node to provide context for new list creation
  760.  * \return a library, or null if this service can't suggest anything based on
  761.  *         the given context and type.
  762.  */
  763. sbLibraryServicePane.prototype.suggestLibraryForNewList =
  764. function sbLibraryServicePane_suggestLibraryForNewList(aMediaListType, aNode) {
  765.   //logcall(arguments);
  766.  
  767.   // Must provide a media list type
  768.   if (!aMediaListType) {
  769.     throw Components.results.NS_ERROR_INVALID_ARG;
  770.   }
  771.  
  772.   // Make sure we are fully initialized
  773.   if (!this._libraryManager || !this._servicePane) {
  774.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  775.   }
  776.  
  777.   // if no node was provided, then suggest the main library
  778.   if (!aNode)
  779.     return this._libraryManager.mainLibrary;
  780.  
  781.   function checkNode(aNode, aLibServicePane) {
  782.     // If this node is visible and belongs to the library
  783.     // service pane service...
  784.     if (aNode.contractid == CONTRACTID && !aNode.hidden) {
  785.       // If this is a playlist and the playlist belongs
  786.       // to a library that supports the given type,
  787.       // then suggest that library
  788.       var mediaItem = aLibServicePane._getItemForURN(aNode.id);
  789.       if (mediaItem && mediaItem instanceof Ci.sbIMediaList &&
  790.           aLibServicePane._doesLibrarySupportListType(mediaItem.library, aMediaListType))
  791.       {
  792.         return mediaItem.library;
  793.       }
  794.  
  795.       // If this is a library that supports the given type,
  796.       // then suggest the library
  797.       var library = aLibServicePane._getLibraryForURN(aNode.id);
  798.       if (library && library instanceof Ci.sbILibrary &&
  799.           aLibServicePane._doesLibrarySupportListType(library, aMediaListType))
  800.       {
  801.         return library;
  802.       }
  803.     }
  804.  
  805.     return null;
  806.   }
  807.  
  808.   // first, check if the given node is useable as a library...
  809.   var lib = checkNode(aNode, this);
  810.   if (lib)
  811.     return lib;
  812.  
  813.   // check the children of the node (but not recursively) for a usable library
  814.   for (var child = aNode.firstChild; child; child = child.nextSibling) {
  815.     lib = checkNode(child, this);
  816.     if (lib)
  817.       return lib;
  818.   }
  819.  
  820.   // Move up the tree looking for libraries that support the
  821.   // given media list type.
  822.   aNode = aNode.parentNode;
  823.   while (aNode && aNode != this._servicePane.root) {
  824.  
  825.     lib = checkNode(aNode, this);
  826.     if (lib)
  827.       return lib;
  828.  
  829.     // Move up the tree
  830.     aNode = aNode.parentNode;
  831.   } // end of while
  832.  
  833.   // If the main library supports the given type, then return that
  834.   if (this._doesLibrarySupportListType(this._libraryManager.mainLibrary,
  835.                                        aMediaListType))
  836.   {
  837.     return this._libraryManager.mainLibrary;
  838.   }
  839.  
  840.   // Oh well, out of luck
  841.   return null;
  842. }
  843.  
  844. /* \brief Suggest a unique name for creating a new playlist
  845.  *
  846.  * \param aLibrary an sbILibrary.
  847.  * \return a unique playlist name.
  848.  */
  849. sbLibraryServicePane.prototype.suggestNameForNewPlaylist =
  850. function sbLibraryServicePane_suggestNameForNewPlaylist(aLibrary) {
  851.   // Give the playlist a default name
  852.   // TODO: Localization should be done internally
  853.   var name = SBString("playlist", "Playlist");
  854.   var length = name.length;
  855.  
  856.   if(aLibrary instanceof Ci.sbILibrary) {
  857.     // Build the existing IDs array
  858.     let listIDs = [];
  859.     let mediaLists = aLibrary.getItemsByProperty(SBProperties.isList, "1");
  860.     for (let i = 0; i < mediaLists.length; ++i) {
  861.       let mediaListName = mediaLists.queryElementAt(i, Ci.sbIMediaList).name;
  862.       if (mediaListName && mediaListName.substr(0, length) == name) {
  863.         if (mediaListName.length == length) {
  864.           listIDs.push(1);
  865.         }
  866.         else if (mediaListName.length > length + 1) {
  867.           listIDs.push(parseInt(mediaListName.substr(length + 1)));
  868.         }
  869.       }
  870.     }
  871.  
  872.     let id = 1;
  873.     while (1) {
  874.       // The id is available.
  875.       if (listIDs.indexOf(id) == -1)
  876.         break;
  877.  
  878.       ++id;
  879.     }
  880.  
  881.     if (id > 1)
  882.       name = SBFormattedString("playlist.sequence", [id]);
  883.   }
  884.  
  885.   return name;
  886. }
  887.  
  888. sbLibraryServicePane.prototype.createNodeForLibrary =
  889. function sbLibraryServicePane_createNodeForLibrary(aLibrary) {
  890.   if(aLibrary instanceof Ci.sbILibrary) {
  891.     return this._libraryAdded(aLibrary);
  892.   }
  893.  
  894.   return null;
  895. }
  896.  
  897. /**
  898.  * \brief Get the URN for a resource, to be used to look up the service pane node
  899.  * \param aResource to resource to get the service pane URN for
  900.  * \returns The expected URN for the resource
  901.  */
  902. sbLibraryServicePane.prototype._getURNForLibraryResource =
  903. function sbLibraryServicePane_getURNForLibraryResource(aResource) {
  904.   //logcall(arguments);
  905.  
  906.   // Must be initialized
  907.   if (!this._libraryManager || !this._servicePane) {
  908.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  909.   }
  910.  
  911.   // If this is a library, get the library URN
  912.   if (aResource instanceof Ci.sbILibrary) {
  913.     return this._libraryURN(aResource);
  914.  
  915.   // If this is a mediaitem, get an item urn
  916.   } else if (aResource instanceof Ci.sbIMediaItem) {
  917.     // Check if this is a storage list for an outer list
  918.     var outerListGuid = aResource.getProperty(SBProperties.outerGUID);
  919.     if (outerListGuid) {
  920.       var library = aResource.library;
  921.       var outerList = library.getMediaItem(outerListGuid);
  922.       if (outerList) {
  923.         aResource = outerList;
  924.       }
  925.     }
  926.     // cacluate the URN for the item
  927.     return this._itemURN(aResource);
  928.  
  929.   // Else we don't know what to do, so
  930.   // the arg must be invalid
  931.   } else {
  932.     throw Components.results.NS_ERROR_INVALID_ARG;
  933.   }
  934.  
  935.   return node;
  936. }
  937.  
  938. /* \brief Attempt to get a service pane node for the given library resource
  939.  *
  940.  * \param aResource an sbIMediaItem, sbIMediaItem, or sbILibrary
  941.  * \return a service pane node that represents the given resource, if one
  942.  *         exists. Note that in the case that more than one node related to a
  943.  *         given library exists, it is not specified which node will be
  944.  *         returned. However, nodes that are not hidden will be preferred.
  945.  */
  946. sbLibraryServicePane.prototype.getNodeForLibraryResource =
  947. function sbLibraryServicePane_getNodeForLibraryResource(aResource, aType) {
  948.   //logcall(arguments);
  949.  
  950.   var urn = this._getURNForLibraryResource(aResource);
  951.  
  952.   if (aResource instanceof Ci.sbILibrary) {
  953.     // For backwards compatibility, prefer the audio/video node for libraries
  954.     // over the container (if any of them is visible)
  955.     var types = aType ? [aType] : ["audio", "video", "podcast"];
  956.     for each (let type in types) {
  957.       let constrainedURN = urn + ":constraint(" + type + ")";
  958.       let node = this._servicePane.getNode(constrainedURN);
  959.       if (node && !node.hidden) {
  960.         return node;
  961.       }
  962.     }
  963.   }
  964.  
  965.   // For playlists and libraries that don't have any visible children, return
  966.   // the main node - even if it is hidden
  967.   return this._servicePane.getNode(urn);
  968. }
  969.  
  970. /**
  971.  * \brief Return all service pane nodes related to the library resource
  972.  *        specified by aResource.
  973.  *
  974.  * \param aResource an sbIMediaItem, sbIMediaItem, or sbILibrary
  975.  * \return An nsIArray of service pane nodes representing the given resource.
  976.  *         Note that in the case that more than one node is related to a given
  977.  *         library, multiple nodes will be returned.
  978.  */
  979. sbLibraryServicePane.prototype.getNodesForLibraryResource =
  980. function sbLibraryServicePane_getNodesForLibraryResource(aResource) {
  981.   let nodeList = [];
  982.   let urn = this._getURNForLibraryResource(aResource);
  983.   let node = this._servicePane.getNode(urn);
  984.   if (node)
  985.     nodeList.push(node);
  986.   if (aResource instanceof Ci.sbILibrary) {
  987.     for each (let type in ["audio", "video", "podcast"]) {
  988.       let constrainedURN = urn + ":constraint(" + type + ")";
  989.       node = this._servicePane.getNode(constrainedURN);
  990.       if (node)
  991.         nodeList.push(node);
  992.     }
  993.   }
  994.  
  995.   return ArrayConverter.nsIArray(nodeList);
  996. }
  997.  
  998. /**
  999.  * \brief Attempt to get a service pane node for the given media list view
  1000.  *
  1001.  * \param aMediaListView the view for which to get the service pane node
  1002.  * \returns a service pane node that represents the given media list view
  1003.  */
  1004. sbLibraryServicePane.prototype.getNodeFromMediaListView =
  1005. function sbLibraryServicePane_getNodeFromMediaListView(aMediaListView) {
  1006.   //logcall(arguments);
  1007.  
  1008.   // get the base URN...
  1009.   var urn = this._getURNForLibraryResource(aMediaListView.mediaList);
  1010.  
  1011.   var values = this._getConstraintsValueArrayFromMediaListView(aMediaListView);
  1012.   for (let i = 0; i < values.length; ++i) {
  1013.     urn += ":constraint(" + values[i] + ")";
  1014.   }
  1015.  
  1016.   return this._servicePane.getNode(urn);
  1017. }
  1018.  
  1019. /**
  1020.  * \brief Attempt to get the content type of service pane node for the given
  1021.  *        media list view.
  1022.  *
  1023.  * \param aMediaListView the view for which to get the content type.
  1024.  * \returns the content type of service pane node that represents the given
  1025.  *          media list view or null if the type information is not available
  1026.  *          for the node.
  1027.  */
  1028. sbLibraryServicePane.prototype.getNodeContentTypeFromMediaListView =
  1029. function sbLibraryServicePane_getNodeContentTypeFromMediaListView(
  1030.                                   aMediaListView) {
  1031.   var values = this._getConstraintsValueArrayFromMediaListView(aMediaListView);
  1032.  
  1033.   const K_TYPES = ["audio", "video", "podcast"];
  1034.  
  1035.   for (let i = 0; i < values.length; ++i) {
  1036.     if (K_TYPES.indexOf(values[i]) > -1)
  1037.       return values[i];
  1038.   }
  1039.  
  1040.   return null;
  1041. }
  1042.  
  1043. /* \brief Attempt to get a library resource for the given service pane node.
  1044.  *
  1045.  * Note that there is no guarantee that hidden service pane nodes
  1046.  * will have corresponding library resources
  1047.  *
  1048.  * \param aNode
  1049.  * \return a sbIMediaItem, sbIMediaItem, sbILibrary, or null
  1050.  */
  1051. sbLibraryServicePane.prototype.getLibraryResourceForNode =
  1052. function sbLibraryServicePane_getLibraryResourceForNode(aNode) {
  1053.   //logcall(arguments);
  1054.  
  1055.   // Must provide a node
  1056.   if (!(aNode instanceof Ci.sbIServicePaneNode)) {
  1057.     throw Components.results.NS_ERROR_INVALID_ARG;
  1058.   }
  1059.   // Must be initialized
  1060.   if (!this._libraryManager || !this._servicePane) {
  1061.     throw Components.results.NS_ERROR_NOT_INITIALIZED;
  1062.   }
  1063.  
  1064.   // If the node does not belong to us, then we aren't
  1065.   // going to find a resource
  1066.   if (aNode.contractid != CONTRACTID) {
  1067.     return null;
  1068.   }
  1069.  
  1070.   // Attempt to get a resource from the id of the given node
  1071.   var resource = this._getItemForURN(aNode.id);
  1072.   if (!resource) {
  1073.     resource = this._getLibraryForURN(aNode.id);
  1074.   }
  1075.  
  1076.   return resource;
  1077. }
  1078.  
  1079.  
  1080. /* \brief Set node read-only property.
  1081.  *
  1082.  * \param aNode Node to set.
  1083.  * \param aReadOnly If true, node is read-only.
  1084.  */
  1085. sbLibraryServicePane.prototype.setNodeReadOnly =
  1086. function sbLibraryServicePane_setNodeReadOnly(aNode, aReadOnly) {
  1087.   if (aReadOnly) {
  1088.     aNode.editable = false;
  1089.     aNode.setAttributeNS(LSP, "ReadOnly", "true");
  1090.   } else {
  1091.     aNode.editable = true;
  1092.     aNode.setAttributeNS(LSP, "ReadOnly", "false");
  1093.   }
  1094. }
  1095.  
  1096.  
  1097. /////////////////////
  1098. // Private Methods //
  1099. /////////////////////
  1100.  
  1101. sbLibraryServicePane.prototype._getConstraintsValueArrayFromMediaListView =
  1102. function sbLibraryServicePane__getConstraintsValueArrayFromMediaListView(
  1103.                                    aMediaListView) {
  1104.   var values = [];
  1105.  
  1106.   if ((aMediaListView instanceof Ci.sbIFilterableMediaListView) &&
  1107.       aMediaListView.filterConstraint)
  1108.   {
  1109.     // stash off the properties involved in the standard constraints,
  1110.     // so that we don't look at them
  1111.     var standardConstraintProperties = {};
  1112.     const standardConstraint = LibraryUtils.standardFilterConstraint;
  1113.     for (let group in ArrayConverter.JSEnum(standardConstraint.groups)) {
  1114.       group.QueryInterface(Ci.sbILibraryConstraintGroup);
  1115.       for (let prop in ArrayConverter.JSEnum(group.properties)) {
  1116.         standardConstraintProperties[prop] = true;
  1117.       }
  1118.     }
  1119.  
  1120.     for (let group in ArrayConverter.JSEnum(aMediaListView.filterConstraint.groups)) {
  1121.       group.QueryInterface(Ci.sbILibraryConstraintGroup);
  1122.       for (let prop in ArrayConverter.JSEnum(group.properties)) {
  1123.         if (prop in standardConstraintProperties) {
  1124.           continue;
  1125.         }
  1126.         for (let value in ArrayConverter.JSEnum(group.getValues(prop))) {
  1127.           values.push(value);
  1128.         }
  1129.       }
  1130.     }
  1131.   }
  1132.  
  1133.   return values;
  1134. }
  1135.  
  1136.  
  1137. /**
  1138.  * Return true if the given device supports playlist
  1139.  */
  1140. sbLibraryServicePane.prototype._doesDeviceSupportPlaylist =
  1141. function sbLibraryServicePane__doesDeviceSupportPlaylist(aDevice) {
  1142.   // Check the device capabilities to see if it supports playlists.
  1143.   // Device implementations may respond to CONTENT_PLAYLIST for either
  1144.   // FUNCTION_DEVICE or FUNCTION_AUDIO_PLAYBACK.
  1145.   var capabilities = aDevice.capabilities;
  1146.   var sbIDC = Ci.sbIDeviceCapabilities;
  1147.   try {
  1148.     if (capabilities.supportsContent(sbIDC.FUNCTION_DEVICE,
  1149.                                      sbIDC.CONTENT_PLAYLIST) ||
  1150.         capabilities.supportsContent(sbIDC.FUNCTION_AUDIO_PLAYBACK,
  1151.                                      sbIDC.CONTENT_PLAYLIST)) {
  1152.       return true;
  1153.     }
  1154.   } catch (e) {}
  1155.  
  1156.   // couldn't find PLAYLIST support in either the DEVICE
  1157.   // or AUDIO_PLAYBACK category
  1158.   return false;
  1159. }
  1160.  
  1161.  
  1162. /**
  1163.  * Return true if the given library supports the given list type
  1164.  */
  1165. sbLibraryServicePane.prototype._doesLibrarySupportListType =
  1166. function sbLibraryServicePane__doesLibrarySupportListType(aLibrary, aListType) {
  1167.   //logcall(arguments);
  1168.  
  1169.   var device;
  1170.   // Get device from non-device library will cause NS_ERROR_NOT_IMPLEMENTED.
  1171.   // Device library could also return NS_ERROR_UNEXPECTED on failure.
  1172.   try {
  1173.     device = aLibrary.device;
  1174.   } catch (e) {
  1175.     device = null;
  1176.   }
  1177.  
  1178.   // Check whether the device support playlist.
  1179.   if (device && !this._doesDeviceSupportPlaylist(device)) {
  1180.     return false;
  1181.   }
  1182.  
  1183.   // If the transfer policy indicates read only media lists, the library does
  1184.   // not support adding media lists of any type
  1185.   // XXXerik less than SUPER HACK to keep new playlists from being added to
  1186.   // device libraries.  This uses a hacked up policy system that will be
  1187.   // replaced by a real one.
  1188.   var transferPolicy = aLibrary.getProperty(SBProperties.transferPolicy);
  1189.   if (transferPolicy && transferPolicy.match(/readOnlyMediaLists/)) {
  1190.     return false;
  1191.   }
  1192.  
  1193.   // XXXben SUPER HACK to keep new playlists from being added to the web
  1194.   //        library. We should really fix this with our policy system.
  1195.   if (aLibrary.equals(LibraryUtils.webLibrary)) {
  1196.     return false;
  1197.   }
  1198.  
  1199.   var types = aLibrary.mediaListTypes;
  1200.   while (types.hasMore()) {
  1201.     if(aListType == types.getNext())  {
  1202.       return true;
  1203.     }
  1204.   }
  1205.   return false;
  1206. }
  1207.  
  1208.  
  1209. /**
  1210.  * Add all registered libraries to the service pane
  1211.  */
  1212. sbLibraryServicePane.prototype._addAllLibraries =
  1213. function sbLibraryServicePane__addAllLibraries() {
  1214.   //logcall(arguments);
  1215.   var libraries = this._libraryManager.getLibraries();
  1216.   while (libraries.hasMoreElements()) {
  1217.     var library = libraries.getNext();
  1218.     this._libraryAdded(library);
  1219.   }
  1220. }
  1221.  
  1222. /**
  1223. * Add all media lists found in the given library
  1224.  */
  1225. sbLibraryServicePane.prototype._processListsInLibrary =
  1226. function sbLibraryServicePane__processListsInLibrary(aLibrary) {
  1227.   //logcall(arguments);
  1228.  
  1229.   // Listener to receive enumerated items and store then in an array
  1230.   var listener = {
  1231.     items: [],
  1232.     onEnumerationBegin: function() { },
  1233.     onEnumerationEnd: function() { },
  1234.     onEnumeratedItem: function(list, item) {
  1235.       this.items.push(item);
  1236.     }
  1237.   };
  1238.  
  1239.   // Enumerate all lists in this library
  1240.   aLibrary.enumerateItemsByProperty(SBProperties.isList, "1",
  1241.                                     listener );
  1242.  
  1243.   // copy array of lists
  1244.   var remaining = listener.items.slice(0);
  1245.  
  1246.   // create nodes in the saved order, ignore guids with no
  1247.   // corresponding medialist and nodes whose guid isnt in the
  1248.   // saved order
  1249.   var appPrefs = Cc["@mozilla.org/fuel/application;1"]
  1250.                    .getService(Ci.fuelIApplication).prefs;
  1251.   var saved = appPrefs.getValue("songbird.library_listorder." + aLibrary.guid, "");
  1252.   var savedArray = saved.split(",");
  1253.   for each (var listguid in savedArray) {
  1254.     for (var i in remaining) {
  1255.       if (remaining[i].guid == listguid) {
  1256.         this._ensureMediaListNodeExists(remaining[i], true);
  1257.         remaining.slice(i, 1);
  1258.         break;
  1259.       }
  1260.     }
  1261.   }
  1262.  
  1263.   // Make sure we have a node for each remaining list. each node will be
  1264.   // inserted after the last node of the same type
  1265.   for (var i = 0; i < remaining.length; i++) {
  1266.     this._ensureMediaListNodeExists(remaining[i], false);
  1267.   }
  1268. }
  1269.  
  1270.  
  1271. /**
  1272.  * The given library has been added.  Show it in the service pane.
  1273.  */
  1274. sbLibraryServicePane.prototype._libraryAdded =
  1275. function sbLibraryServicePane__libraryAdded(aLibrary) {
  1276.   //logcall(arguments);
  1277.   var node = this._ensureLibraryNodeExists(aLibrary);
  1278.  
  1279.   // Listen to changes in the library so that we can display new playlists
  1280.   var filter = SBProperties.createArray([[SBProperties.hidden, null],
  1281.                                          [SBProperties.mediaListName, null]]);
  1282.   aLibrary.addListener(this,
  1283.                        false,
  1284.                        Ci.sbIMediaList.LISTENER_FLAGS_ALL,
  1285.                        filter);
  1286.   this._libraries.push(aLibrary);
  1287.  
  1288.   this._processListsInLibrary(aLibrary);
  1289.  
  1290.   return node;
  1291. }
  1292.  
  1293.  
  1294. /**
  1295.  * The given library has been removed.  Just hide the contents
  1296.  * rather than deleting so that if it is ever reattached
  1297.  * we will remember any ordering (drag-drop) information
  1298.  */
  1299. sbLibraryServicePane.prototype._libraryRemoved =
  1300. function sbLibraryServicePane__libraryRemoved(aLibrary) {
  1301.   //logcall(arguments);
  1302.  
  1303.   // Get the list of nodes for items within the library
  1304.   var libraryItemNodeList = this._servicePane.getNodesByAttributeNS
  1305.                                                 (LSP,
  1306.                                                  "LibraryGUID",
  1307.                                                  aLibrary.guid);
  1308.  
  1309.   // Hide all nodes for items within the library
  1310.   var libraryItemNodeEnum = libraryItemNodeList.enumerate();
  1311.   while (libraryItemNodeEnum.hasMoreElements()) {
  1312.     // Hide the library item node
  1313.     var libraryItemNode =
  1314.       libraryItemNodeEnum.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1315.     libraryItemNode.hidden = true;
  1316.   }
  1317.  
  1318.   aLibrary.removeListener(this);
  1319.   this._libraries.splice(this._libraries.indexOf(aLibrary), 1);
  1320. }
  1321.  
  1322. sbLibraryServicePane.prototype._refreshLibraryNodes =
  1323. function sbLibraryServicePane__refreshLibraryNodes(aLibrary) {
  1324.   var id = this._libraryURN(aLibrary);
  1325.   var node = this._servicePane.getNode(id);
  1326.   this._scanForRemovedItems(aLibrary);
  1327.   this._ensureLibraryNodeExists(aLibrary);
  1328.   this._processListsInLibrary(aLibrary);
  1329. }
  1330.  
  1331. /**
  1332.  * The given media list has been added. Show it in the service pane.
  1333.  */
  1334. sbLibraryServicePane.prototype._playlistAdded =
  1335. function sbLibraryServicePane__playlistAdded(aMediaList) {
  1336.   //logcall(arguments);
  1337.   this._ensureMediaListNodeExists(aMediaList);
  1338. }
  1339.  
  1340.  
  1341. /**
  1342.  * The given media list has been removed. Delete the node, as it
  1343.  * is unlikely that the same playlist will come back again.
  1344.  */
  1345. sbLibraryServicePane.prototype._playlistRemoved =
  1346. function sbLibraryServicePane__playlistRemoved(aMediaList) {
  1347.   //logcall(arguments);
  1348.  
  1349.   var id = this._itemURN(aMediaList);
  1350.   var node = this._servicePane.getNode(id);
  1351.   if (node) {
  1352.     node.parentNode.removeChild(node);
  1353.   }
  1354. }
  1355.  
  1356.  
  1357. /**
  1358.  * The given media list has been updated.
  1359.  * The name and other properties may have changed.
  1360.  */
  1361. sbLibraryServicePane.prototype._mediaListUpdated =
  1362. function sbLibraryServicePane__mediaListUpdated(aMediaList) {
  1363.   //logcall(arguments);
  1364.   if (aMediaList instanceof Ci.sbILibrary) {
  1365.     this._ensureLibraryNodeExists(aMediaList);
  1366.   } else if (aMediaList instanceof Ci.sbIMediaList) {
  1367.     this._ensureMediaListNodeExists(aMediaList);
  1368.   }
  1369. }
  1370.  
  1371.  
  1372. /**
  1373.  * Get a service pane identifier for the given media item
  1374.  */
  1375. sbLibraryServicePane.prototype._itemURN =
  1376. function sbLibraryServicePane__itemURN(aMediaItem) {
  1377.   return URN_PREFIX_ITEM + aMediaItem.guid;
  1378. }
  1379.  
  1380.  
  1381. /**
  1382.  * Get a service pane identifier for the given library
  1383.  */
  1384. sbLibraryServicePane.prototype._libraryURN =
  1385. function sbLibraryServicePane__libraryURN(aLibrary) {
  1386.   return URN_PREFIX_LIBRARY + aLibrary.guid;
  1387. }
  1388.  
  1389.  
  1390. /**
  1391.  * Given a resource id, attempt to extract the GUID of a media item.
  1392.  */
  1393. sbLibraryServicePane.prototype._getItemGUIDForURN =
  1394. function sbLibraryServicePane__getItemGUIDForURN(aID) {
  1395.   //logcall(arguments);
  1396.   var index = aID.indexOf(URN_PREFIX_ITEM);
  1397.   if (index >= 0) {
  1398.     return aID.slice(URN_PREFIX_ITEM.length);
  1399.   }
  1400.   return null;
  1401. }
  1402.  
  1403.  
  1404. /**
  1405.  * Given a resource id, attempt to extract the GUID of a library.
  1406.  */
  1407. sbLibraryServicePane.prototype._getLibraryGUIDForURN =
  1408. function sbLibraryServicePane__getLibraryGUIDForURN(aID) {
  1409.   //logcall(arguments);
  1410.   if (aID.substring(0, URN_PREFIX_LIBRARY.length) != URN_PREFIX_LIBRARY) {
  1411.     return null;
  1412.   }
  1413.   var id = aID.slice(URN_PREFIX_LIBRARY.length);
  1414.   id = id.replace(/:constraint\(.*?\)/g, '');
  1415.   return id;
  1416. }
  1417.  
  1418.  
  1419. /**
  1420.  * Given a resource id, attempt to get an sbIMediaItem.
  1421.  * This is the inverse of _itemURN
  1422.  */
  1423. sbLibraryServicePane.prototype._getItemForURN =
  1424. function sbLibraryServicePane__getItemForURN(aID) {
  1425.   //logcall(arguments);
  1426.   var guid = this._getItemGUIDForURN(aID);
  1427.   if (guid) {
  1428.     var node = this._servicePane.getNode(aID);
  1429.     var libraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  1430.     if (libraryGUID) {
  1431.       try {
  1432.         var library = this._libraryManager.getLibrary(libraryGUID);
  1433.         return library.getMediaItem(guid);
  1434.       } catch (e) {
  1435.         LOG("sbLibraryServicePane__getItemForURN: error trying to get medialist " +
  1436.              guid + " from library " + libraryGUID);
  1437.       }
  1438.     }
  1439.  
  1440.     // URNs of visible nodes in the servicetree should always refer
  1441.     // to an existing media item...
  1442.     LOG("sbLibraryServicePane__getItemForURN: could not find a mediaItem " +
  1443.          "for URN " + aID + ". The service pane must be out of sync with " +
  1444.          "the libraries!");
  1445.   }
  1446.   return null;
  1447. }
  1448.  
  1449.  
  1450. /**
  1451.  * Given a resource id, attempt to get an sbILibrary.
  1452.  * This is the inverse of _libraryURN
  1453.  */
  1454. sbLibraryServicePane.prototype._getLibraryForURN =
  1455. function sbLibraryServicePane__getLibraryForURN(aID) {
  1456.   //logcall(arguments);
  1457.   var guid = this._getLibraryGUIDForURN(aID);
  1458.   if (guid) {
  1459.     try {
  1460.       return this._libraryManager.getLibrary(guid);
  1461.     }
  1462.     catch (e) {
  1463.       LOG("sbLibraryServicePane__getLibraryForURN: error trying to get " +
  1464.           "library " + guid);
  1465.     }
  1466.   }
  1467.   return null;
  1468. }
  1469.  
  1470.  
  1471.  
  1472. /**
  1473.  * Ensure the library nodes for the given library exists, then return the
  1474.  * container node
  1475.  */
  1476. sbLibraryServicePane.prototype._ensureLibraryNodeExists =
  1477. function sbLibraryServicePane__ensureLibraryNodeExists(aLibrary) {
  1478.   //logcall(arguments);
  1479.   var self = this;
  1480.  
  1481.   /**
  1482.    * Find or create a library node for the given library, for the given
  1483.    * constraint type
  1484.    * @param aLibrary the library to find / create a node for
  1485.    * @param aConstraintType the type of library node, e.g. "audio", "video",
  1486.    *                        "podcast" - if not given, no constraint applies
  1487.    * @returns the node (old or new) for the library of the given type
  1488.    */
  1489.   function makeNodeFromLibrary(aLibrary, aConstraintType, aParentNode) {
  1490.     var id = self._libraryURN(aLibrary);
  1491.     if (aConstraintType) {
  1492.       // add a constraint only if the constraint type was specified
  1493.       id += ":constraint(" + aConstraintType + ")";
  1494.     }
  1495.     var node = self._servicePane.getNode(id);
  1496.     if (!node) {
  1497.       // Create the node
  1498.       node = self._servicePane.createNode();
  1499.       node.id = id;
  1500.       node.contractid = CONTRACTID;
  1501.       node.editable = false;
  1502.  
  1503.       // Set properties for styling purposes
  1504.       self._addClassNames(node,
  1505.                           ["library",
  1506.                            "libraryguid-" + aLibrary.guid,
  1507.                            aLibrary.type,
  1508.                            customType]);
  1509.       // Save the type of media list so that we can group by type
  1510.       node.setAttributeNS(LSP, "ListType", aLibrary.type)
  1511.       // Save the guid of the library
  1512.       node.setAttributeNS(LSP, "LibraryGUID", aLibrary.guid);
  1513.       // and save it as the list guid
  1514.       node.setAttributeNS(LSP, "ListGUID", aLibrary.guid);
  1515.       // Save the customType for use by metrics.
  1516.       node.setAttributeNS(LSP, "ListCustomType", customType);
  1517.       // Save the customType for use by metrics.
  1518.       node.setAttributeNS(LSP, "LibraryCustomType", customType);
  1519.  
  1520.       if (aConstraintType) {
  1521.         self._addClassNames(node, [aConstraintType]);
  1522.         var builder = Cc["@songbirdnest.com/Songbird/Library/ConstraintBuilder;1"]
  1523.                         .createInstance(Ci.sbILibraryConstraintBuilder);
  1524.         builder.includeConstraint(LibraryUtils.standardFilterConstraint);
  1525.         builder.intersect();
  1526.         builder.include(SBProperties.contentType, aConstraintType);
  1527.         node.setAttributeNS(SP,
  1528.                             "mediaListViewConstraints",
  1529.                             builder.get());
  1530.       }
  1531.     }
  1532.     var customType = aLibrary.getProperty(SBProperties.customType);
  1533.  
  1534.     // Refresh the information just in case it is supposed to change
  1535.     // Don't set name if it hasn't changed to avoid a UI redraw
  1536.     let name = (aConstraintType ? '&servicesource.library.' + aConstraintType :
  1537.                                   aLibrary.name);
  1538.     if (node.name != name) {
  1539.       node.name = name;
  1540.     }
  1541.     var hidden = (aLibrary.getProperty(SBProperties.hidden) == "1");
  1542.     node.hidden = hidden;
  1543.  
  1544.     if (aParentNode && !node.parentNode) {
  1545.       // Insert the node as first child
  1546.       aParentNode.insertBefore(node, aParentNode.firstChild);
  1547.     }
  1548.     return node;
  1549.   }
  1550.  
  1551.   var customType = aLibrary.getProperty(SBProperties.customType);
  1552.  
  1553.   if (customType == 'web') {
  1554.     // the web library has no video/audio split, nor a parent, so we need to
  1555.     // special case it here and return early
  1556.     let node = makeNodeFromLibrary(aLibrary, null, null);
  1557.  
  1558.     // Set the weight of the web library
  1559.     node.setAttributeNS(SP, 'Weight', 5);
  1560.     node.hidden = true;
  1561.  
  1562.     if (!node.parentNode)
  1563.       this._servicePane.root.appendChild(node);
  1564.  
  1565.     return node;
  1566.   }
  1567.  
  1568.   // make the parent node
  1569.   var id = self._libraryURN(aLibrary);
  1570.   var parentNode = self._servicePane.getNode(id);
  1571.   if (!parentNode) {
  1572.     // Create the node
  1573.     parentNode = this._servicePane.createNode();
  1574.     parentNode.id = id;
  1575.     parentNode.editable = false;
  1576.     parentNode.setAttributeNS(SP, 'Weight', -4);
  1577.  
  1578.     // uncomment this to cause clicks on the container node to load an unfiltered
  1579.     // library.  we need to think more about the ramifications for UE before
  1580.     // turning this on, so it's off for now.
  1581.     // (see also below on the migration part)
  1582.     //parentNode.contractid = CONTRACTID;
  1583.  
  1584.     // class names that should exist on the parent container node
  1585.     const K_PARENT_PROPS = ["folder", "library-container"];
  1586.  
  1587.     self._addClassNames(parentNode, K_PARENT_PROPS);
  1588.  
  1589.     if (aLibrary != this._libraryManager.mainLibrary) {
  1590.       // always create them as hidden
  1591.       parentNode.hidden = true;
  1592.     }
  1593.   }
  1594.  
  1595.   // Refresh the information just in case it is supposed to change
  1596.   // Don't set name if it hasn't changed to avoid a UI redraw
  1597.   if (parentNode.name != aLibrary.name) {
  1598.     parentNode.name = aLibrary.name;
  1599.   }
  1600.  
  1601.   if (aLibrary == this._libraryManager.mainLibrary) {
  1602.     for each (let type in ["video", "audio"]) {
  1603.       let node = makeNodeFromLibrary(aLibrary, type, parentNode);
  1604.     }
  1605.  
  1606.     // the main library uses a separate Playlists and Podcasts folder
  1607.     this._ensurePlaylistFolderExists();
  1608.     this._ensurePodcastFolderExists();
  1609.  
  1610.     // if the iTunes folder exists, then make it visible
  1611.     var fnode = this._servicePane.getNode('SB:iTunes');
  1612.     if (fnode)
  1613.       fnode.hidden = false;
  1614.   }
  1615.   else {
  1616.     for each (let type in ["video", "audio"]) {
  1617.       let node = makeNodeFromLibrary(aLibrary, type, parentNode);
  1618.  
  1619.       // other libraries store the playlists under them, but only
  1620.       // assign the default value if they do not specifically tell
  1621.       // us not to do so
  1622.  
  1623.       if (node.getAttributeNS(SP,'dndCustomAccept') != 'true')
  1624.         node.dndAcceptIn = 'text/x-sb-playlist-'+aLibrary.guid;
  1625.     }
  1626.   }
  1627.  
  1628.   if (!parentNode.parentNode) {
  1629.     // Append library to the root, service pane will sort by weight
  1630.     this._servicePane.root.appendChild(parentNode);
  1631.   }
  1632.  
  1633.   return parentNode;
  1634. }
  1635.  
  1636.  
  1637. /**
  1638.  * Get the service pane node for the given media list,
  1639.  * creating one if none exists.
  1640.  */
  1641. sbLibraryServicePane.prototype._ensureMediaListNodeExists =
  1642. function sbLibraryServicePane__ensureMediaListNodeExists(aMediaList, aAppend) {
  1643.   //logcall(arguments);
  1644.  
  1645.   var id = this._itemURN(aMediaList);
  1646.   var node = this._servicePane.getNode(id);
  1647.  
  1648.   var customType = aMediaList.getProperty(SBProperties.customType);
  1649.   var libCustomType = aMediaList.library.getProperty(SBProperties.customType);
  1650.  
  1651.   if (!node) {
  1652.     // Create the node
  1653.     // NOTE: it's a container for drag and drop purposes only.
  1654.     node = this._servicePane.createNode();
  1655.     node.id = id;
  1656.     node.contractid = CONTRACTID;
  1657.  
  1658.     if (customType == 'download') {
  1659.       // the download media list isn't editable
  1660.       node.editable = false;
  1661.       // set the weight of the downloads list
  1662.       node.setAttributeNS(SP, 'Weight', 999);
  1663.     } else {
  1664.       // the rest are, but only if the items themselves are not readonly
  1665.       node.editable = aMediaList.userEditable;
  1666.     }
  1667.  
  1668.     // Set properties for styling purposes
  1669.     if (aMediaList.getProperty(SBProperties.isSubscription) == "1") {
  1670.       this._addClassNames(node, ["medialist", "medialisttype-dynamic"]);
  1671.       node.setAttributeNS(LSP, "ListSubscription", "1");
  1672.     } else {
  1673.       this._addClassNames(node, ["medialist medialisttype-" + aMediaList.type]);
  1674.       node.setAttributeNS(LSP, "ListSubscription", "0");
  1675.     }
  1676.     // Add the customType to the properties to encourage people to set it for CSS
  1677.     this._addClassNames(node, [customType]);
  1678.     // Save the type of media list so that we can group by type
  1679.     node.setAttributeNS(LSP, "ListType", aMediaList.type);
  1680.     // Save the guid of the library that owns this media list
  1681.     node.setAttributeNS(LSP, "LibraryGUID", aMediaList.library.guid);
  1682.     // and the guid of this list
  1683.     node.setAttributeNS(LSP, "ListGUID", aMediaList.guid);
  1684.     // Save the parent library custom type for this list.
  1685.     node.setAttributeNS(LSP, "LibraryCustomType", libCustomType);
  1686.     // Save the list customType for use by metrics.
  1687.     node.setAttributeNS(LSP, "ListCustomType", customType);
  1688.  
  1689.     // if auto dndAcceptIn/Near hasn't been disabled, assign it now
  1690.     if (node.getAttributeNS(SP,'dndCustomAccept') != 'true') {
  1691.       if (aMediaList.library == this._libraryManager.mainLibrary) {
  1692.         // a playlist in the main library is considered a toplevel node
  1693.         // (unless it's the download playlist)
  1694.         if (customType != 'download') {
  1695.           node.dndDragTypes = 'text/x-sb-playlist';
  1696.           node.dndAcceptNear = 'text/x-sb-playlist';
  1697.         }
  1698.       } else {
  1699.         // playlists in other libraries can only go into their libraries' nodes
  1700.         node.dndDragTypes = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1701.         node.dndAcceptNear = 'text/x-sb-playlist-'+aMediaList.library.guid;
  1702.       }
  1703.     }
  1704.   }
  1705.  
  1706.   // Refresh the information just in case it is supposed to change
  1707.   // Don't set name if it hasn't changed to avoid a UI redraw
  1708.   if (node.name != aMediaList.name) {
  1709.     node.name = aMediaList.name;
  1710.   }
  1711.  
  1712.   // Get hidden state from list
  1713.   var hidden = (aMediaList.getProperty(SBProperties.hidden) == "1");
  1714.   node.hidden = hidden;
  1715.  
  1716.   if (!node.parentNode) {
  1717.     // Place the node in the tree
  1718.     this._insertMediaListNode(node, aMediaList, aAppend);
  1719.   }
  1720.  
  1721.   return node;
  1722. }
  1723.  
  1724. /**
  1725.  * Get the service pane node for the Playlists folder (which contains all
  1726.  * the playlists in the main library).
  1727.  */
  1728. sbLibraryServicePane.prototype._ensurePlaylistFolderExists =
  1729. function sbLibraryServicePane__ensurePlaylistFolderExists() {
  1730.   let fnode = this._servicePane.getNode("SB:Playlists");
  1731.   if (!fnode) {
  1732.     // make sure it exists
  1733.     fnode = this._servicePane.createNode();
  1734.     fnode.id = "SB:Playlists";
  1735.     fnode.name = '&servicesource.playlists';
  1736.     this._addClassNames(fnode, ["folder", this._makeCSSProperty(fnode.name)]);
  1737.     fnode.contractid = CONTRACTID;
  1738.     fnode.dndAcceptIn = 'text/x-sb-playlist';
  1739.     fnode.editable = false;
  1740.     fnode.setAttributeNS(SP, 'Weight', 3);
  1741.     this._servicePane.root.appendChild(fnode);
  1742.   }
  1743.  
  1744.   return fnode;
  1745. }
  1746.  
  1747. /**
  1748.  * Get the service pane node for the Podcasts folder (which contains all
  1749.  * the podcasts in the main library).
  1750.  */
  1751. sbLibraryServicePane.prototype._ensurePodcastFolderExists =
  1752. function sbLibraryServicePane__ensurePodcastFolderExists() {
  1753.   // Return null per bug 17607.  Return value should not get used since podcasts
  1754.   // can no longer be created.
  1755.   return null;
  1756.  
  1757.   let fnode = this._servicePane.getNode("SB:Podcasts");
  1758.   if (!fnode) {
  1759.     // make sure it exists
  1760.     fnode = this._servicePane.createNode();
  1761.     fnode.id = "SB:Podcasts";
  1762.     fnode.name = "&servicesource.podcasts";
  1763.     this._addClassNames(fnode, ["folder", this._makeCSSProperty(fnode.name)]);
  1764.     fnode.contractid = CONTRACTID;
  1765.     fnode.editable = false;
  1766.     fnode.setAttributeNS(SP, "Weight", 2);
  1767.     this._servicePane.root.appendChild(fnode);
  1768.   }
  1769.   return fnode;
  1770. }
  1771.  
  1772. /**
  1773.  * Get the service pane node for the iTunes folder (which contains all
  1774.  * the imported iTunes playlists in the main library).
  1775.  */
  1776. sbLibraryServicePane.prototype._ensureiTunesFolderExists =
  1777. function sbLibraryServicePane__ensureiTunesFolderExists() {
  1778.   let fnode = this._servicePane.getNode("SB:iTunes");
  1779.   if (!fnode) {
  1780.     // make sure it exists
  1781.     fnode = this._servicePane.createNode();
  1782.     fnode.id = "SB:iTunes";
  1783.     fnode.name = '&servicesource.itunes';
  1784.     this._addClassNames(fnode, ["folder", this._makeCSSProperty(fnode.name)]);
  1785.     fnode.contractid = CONTRACTID;
  1786.     fnode.editable = false;
  1787.     fnode.setAttributeNS(SP, 'Weight', 3);
  1788.     this._servicePane.root.appendChild(fnode);
  1789.   }
  1790.   return fnode;
  1791. }
  1792.  
  1793. sbLibraryServicePane.prototype._scanForRemovedItems =
  1794. function sbLibraryServicePane__scanForRemovedItems(aLibrary) {
  1795.   // Get the list of nodes for items within the library
  1796.   var libraryItemNodeList = this._servicePane.getNodesByAttributeNS
  1797.                                                 (LSP,
  1798.                                                  "LibraryGUID",
  1799.                                                  aLibrary.guid);
  1800.  
  1801.   // Remove nodes whose items no longer exist
  1802.   var libraryItemNodeEnum = libraryItemNodeList.enumerate();
  1803.   while (libraryItemNodeEnum.hasMoreElements()) {
  1804.     // Get the library item node
  1805.     var libraryItemNode =
  1806.       libraryItemNodeEnum.getNext().QueryInterface(Ci.sbIServicePaneNode);
  1807.  
  1808.     // Skip library nodes
  1809.     if (this._nodeIsLibrary(libraryItemNode))
  1810.       continue;
  1811.  
  1812.     // Remove node if item no longer exists
  1813.     var mediaItem = this._getItemForURN(libraryItemNode.id);
  1814.     if (!mediaItem)
  1815.       libraryItemNode.parentNode.removeChild(libraryItemNode);
  1816.   }
  1817. }
  1818.  
  1819. /**
  1820.  * Logic to determine where a media list node should appear
  1821.  * in the service pane tree
  1822.  */
  1823. sbLibraryServicePane.prototype._insertMediaListNode =
  1824. function sbLibraryServicePane__insertMediaListNode(aNode, aMediaList, aAppend) {
  1825.   //logcall(arguments);
  1826.  
  1827.   // If it is a main library media list, it belongs in either the
  1828.   // "Playlists" or "iTunes" folder, depending on whether it was imported
  1829.   // from iTunes or not
  1830.   if (aMediaList.library == this._libraryManager.mainLibrary)
  1831.   {
  1832.     // the download playlist is a special case
  1833.     if (aNode.getAttributeNS(LSP, 'ListCustomType') == 'download') {
  1834.       // Fix it to the bottom of the library node
  1835.       var libraryNode = this.getNodeForLibraryResource(aMediaList.library);
  1836.  
  1837.       // getNodeForLibraryResource will usually return a child of the library
  1838.       // container
  1839.       if (libraryNode.parentNode != this._servicePane.root)
  1840.         libraryNode = libraryNode.parentNode;
  1841.  
  1842.       libraryNode.appendChild(aNode);
  1843.     } else {
  1844.       // make sure the playlist folder exists
  1845.       var folder;
  1846.       // if it has an iTunesGUID property, it's imported from iTunes
  1847.       if (aMediaList.getProperty(SBProperties.iTunesGUID) != null) {
  1848.         folder = this._ensureiTunesFolderExists();
  1849.       } else if (aMediaList.getProperty(SBProperties.customType) == "podcast") {
  1850.         folder = this._ensurePodcastFolderExists();
  1851.       } else {
  1852.         folder = this._ensurePlaylistFolderExists();
  1853.       }
  1854.  
  1855.       if (aAppend)
  1856.         folder.appendChild(aNode);
  1857.       else
  1858.         this._insertByNodeType(aNode, folder);
  1859.     }
  1860.   }
  1861.   // If it is a secondary library playlist, it should be
  1862.   // added as a child of that library
  1863.   else
  1864.   {
  1865.     // Find the parent libary in the tree
  1866.     var parentLibraryNode = this.getNodeForLibraryResource(aMediaList.library);
  1867.  
  1868.     // getNodeForLibraryResource will usually return a child of the library
  1869.     // container
  1870.     if (parentLibraryNode && parentLibraryNode.parentNode != this._servicePane.root)
  1871.       parentLibraryNode = parentLibraryNode.parentNode;
  1872.  
  1873.     // If we found a parent library node make the playlist node its child
  1874.     if (parentLibraryNode) {
  1875.       if (aAppend)
  1876.         parentLibraryNode.appendChild(aNode);
  1877.       else
  1878.         this._insertByNodeType(aNode, parentLibraryNode);
  1879.     } else {
  1880.       LOG("sbLibraryServicePane__insertMediaListNode: could not add media list to parent library");
  1881.       this._servicePane.root.appendChild(aNode);
  1882.     }
  1883.   }
  1884. }
  1885.  
  1886. /**
  1887.  * Inserts the given node under the given parent and attempts to keep
  1888.  * children grouped by type.  The node is inserted at the end of the list,
  1889.  * or next to the last child of the same type as the given node, or before
  1890.  * the first of the type with higher priority if no one with the same type
  1891.  * can be found.
  1892.  */
  1893. sbLibraryServicePane.prototype._insertByNodeType =
  1894. function sbLibraryServicePane__insertByNodeType(aNode, aParent) {
  1895.   //logcall(arguments);
  1896.  
  1897.   function getNodeType(aNode) {
  1898.     let type = aNode.getAttributeNS(LSP, "ListType");
  1899.     if (type == "simple" && aNode.getAttributeNS(LSP, "ListSubscription") == "1")
  1900.       return "subscription";
  1901.     else
  1902.       return type;
  1903.   }
  1904.  
  1905.   // Find the best node to insert after. This is ideally the last node with the
  1906.   // same priority as the node we are insertion. If such a node doesn't exist
  1907.   // the last node with a lower priority will do as well.
  1908.   let nodeTypes = { "smart": 1, "simple": 2, "subscription": 3 };
  1909.   let nodePriority = nodeTypes[getNodeType(aNode)];
  1910.   let insertAfter = null;
  1911.   let insertAfterPriority = 0;
  1912.   for (let child = aParent.lastChild; child; child = child.previousSibling) {
  1913.     if (child.hidden || child.contractid != CONTRACTID)
  1914.       continue;
  1915.  
  1916.     let childNodePriority = nodeTypes[getNodeType(child)];
  1917.     if (childNodePriority > insertAfterPriority &&
  1918.         childNodePriority <= nodePriority) {
  1919.       insertAfter = child;
  1920.       insertAfterPriority = childNodePriority;
  1921.  
  1922.       // Break out of the loop if we already found the perfect insertion point
  1923.       if (insertAfterPriority == nodePriority)
  1924.         break;
  1925.     }
  1926.   }
  1927.  
  1928.   if (insertAfter)
  1929.     aParent.insertBefore(aNode, insertAfter.nextSibling);
  1930.   else
  1931.     aParent.insertBefore(aNode, aParent.firstChild);
  1932.  
  1933.   // Ensure that all parent containers are open and the node is visible
  1934.   for (let parent = aParent; parent; parent = parent.parentNode)
  1935.     if (!parent.isOpen)
  1936.       parent.isOpen = true;
  1937. }
  1938.  
  1939. sbLibraryServicePane.prototype._appendMenuItem =
  1940. function sbLibraryServicePane__appendMenuItem(aContextMenu, aLabel, aCallback) {
  1941.   var item = aContextMenu.ownerDocument.createElement("menuitem");
  1942.   item.setAttribute("label", aLabel);
  1943.   item.addEventListener("command", aCallback, false);
  1944.   aContextMenu.appendChild(item);
  1945. }
  1946.  
  1947. sbLibraryServicePane.prototype._appendCommands =
  1948. function sbLibraryServicePane__appendCommands(aContextMenu, aList, aParentWindow) {
  1949.   if (this._lastMenuitems && this._lastMenuitems.destroy) {
  1950.     var pnode = this._lastMenuitems.parentNode;
  1951.     this._lastMenuitems.destroy();
  1952.     this._lastMenuitems = null;
  1953.   }
  1954.   var itemBuilder = aContextMenu.ownerDocument.createElement("sb-commands-menuitems");
  1955.   itemBuilder.setAttribute("id", "playlist-commands");
  1956.   itemBuilder.setAttribute("commandtype", "medialist");
  1957.   itemBuilder.setAttribute("bind", aList.library.guid + ';' + aList.guid);
  1958.   aContextMenu.appendChild(itemBuilder);
  1959.   this._lastMenuitems = itemBuilder;
  1960. }
  1961.  
  1962. /**
  1963.  * This function is a recursive helper for onListCleared (below) that will
  1964.  * remove all the playlist nodes for a given library.
  1965.  */
  1966. sbLibraryServicePane.prototype._removeListNodesForLibrary =
  1967. function sbLibraryServicePane__removeListNodesForLibrary(aStartNode, aLibraryGUID) {
  1968.  
  1969.   var node = aStartNode.firstChild;
  1970.  
  1971.   while (node) {
  1972.  
  1973.     this._removeListNodesForLibrary(node, aLibraryGUID);
  1974.  
  1975.     var nextSibling = node.nextSibling;
  1976.  
  1977.     if (this._getItemGUIDForURN(node.id)) {
  1978.       var nodeLibraryGUID = node.getAttributeNS(LSP, "LibraryGUID");
  1979.       if (nodeLibraryGUID == aLibraryGUID) {
  1980.         node.parentNode.removeChild(node);
  1981.       }
  1982.     }
  1983.  
  1984.     node = nextSibling;
  1985.   }
  1986. }
  1987.  
  1988. sbLibraryServicePane.prototype._addClassNames =
  1989. function sbLibraryServicePane__addClassNames(aNode, aList) {
  1990.   let className = aNode.className || "";
  1991.   let existing = {};
  1992.   for each (let name in className.split(" "))
  1993.     existing[name] = true;
  1994.  
  1995.   for each (let name in aList)
  1996.     if (!existing.hasOwnProperty(name))
  1997.       className += (className ? " " : "") + name;
  1998.  
  1999.   aNode.className = className;
  2000. }
  2001.  
  2002. /**
  2003.  * Turn a partial entity (&foo.bar) into a css property string (foo-bar),
  2004.  * but leaves other strings as they are.
  2005.  */
  2006. sbLibraryServicePane.prototype._makeCSSProperty =
  2007. function sbLibraryServicePane__makeCSSProperty(aString) {
  2008.   if ( aString[0] == "&" ) {
  2009.     aString = aString.substr(1, aString.length);
  2010.     aString = aString.replace(/\./g, "-");
  2011.   }
  2012.   return aString;
  2013. }
  2014.  
  2015. ///////////////////////////////
  2016. // sbILibraryManagerListener //
  2017. ///////////////////////////////
  2018.  
  2019. sbLibraryServicePane.prototype.onLibraryRegistered =
  2020. function sbLibraryServicePane_onLibraryRegistered(aLibrary) {
  2021.   //logcall(arguments);
  2022.   this._libraryAdded(aLibrary);
  2023. }
  2024. sbLibraryServicePane.prototype.onLibraryUnregistered =
  2025. function sbLibraryServicePane_onLibraryUnregistered(aLibrary) {
  2026.   //logcall(arguments);
  2027.   this._libraryRemoved(aLibrary);
  2028. }
  2029.  
  2030. //////////////////////////
  2031. // sbIMediaListListener //
  2032. //////////////////////////
  2033.  
  2034. sbLibraryServicePane.prototype.onItemAdded =
  2035. function sbLibraryServicePane_onItemAdded(aMediaList, aMediaItem, aIndex) {
  2036.   //logcall(arguments);
  2037.   if (this._batch[aMediaList.guid] && this._batch[aMediaList.guid].isActive()) {
  2038.     // We are going to refresh all the nodes once we exit the batch so
  2039.     // we don't need any more of these notifications
  2040.     this._refreshPending = true;
  2041.     return true;
  2042.   }
  2043.   else {
  2044.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  2045.     if (isList) {
  2046.       this._playlistAdded(aMediaItem);
  2047.     }
  2048.     return false;
  2049.   }
  2050. }
  2051. sbLibraryServicePane.prototype.onBeforeItemRemoved =
  2052. function sbLibraryServicePane_onBeforeItemRemoved(aMediaList, aMediaItem, aIndex) {
  2053.   return true;
  2054. }
  2055. sbLibraryServicePane.prototype.onAfterItemRemoved =
  2056. function sbLibraryServicePane_onAfterItemRemoved(aMediaList, aMediaItem, aIndex) {
  2057.   //logcall(arguments);
  2058.   if (this._batch[aMediaList.guid] && this._batch[aMediaList.guid].isActive()) {
  2059.     // We are going to refresh all the nodes once we exit the batch so
  2060.     // we don't need any more of these notifications
  2061.     this._refreshPending = true;
  2062.     return true;
  2063.   }
  2064.   else {
  2065.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  2066.     if (isList) {
  2067.       this._playlistRemoved(aMediaItem);
  2068.     }
  2069.     return false;
  2070.   }
  2071. }
  2072. sbLibraryServicePane.prototype.onItemUpdated =
  2073. function sbLibraryServicePane_onItemUpdated(aMediaList,
  2074.                                             aMediaItem,
  2075.                                             aProperties) {
  2076.   if (this._batch[aMediaList.guid] && this._batch[aMediaList.guid].isActive()) {
  2077.     // We are going to refresh all the nodes once we exit the batch so
  2078.     // we don't need any more of these notifications
  2079.     this._refreshPending = true;
  2080.     return true;
  2081.   }
  2082.   else {
  2083.     var isList = aMediaItem instanceof Ci.sbIMediaList;
  2084.     if (isList) {
  2085.       this._mediaListUpdated(aMediaItem);
  2086.     }
  2087.     return false;
  2088.   }
  2089. }
  2090. sbLibraryServicePane.prototype.onItemMoved =
  2091. function sbLibraryServicePane_onItemMoved(aMediaList,
  2092.                                           aFromIndex,
  2093.                                           aToIndex) {
  2094.   return true;
  2095. }
  2096. sbLibraryServicePane.prototype.onBeforeListCleared =
  2097. function sbLibraryServicePane_onBeforeListCleared(aMediaList,
  2098.                                                   aExcludeLists) {
  2099.   return true;
  2100. }
  2101. sbLibraryServicePane.prototype.onListCleared =
  2102. function sbLibraryServicePane_onListCleared(aMediaList,
  2103.                                             aExcludeLists) {
  2104.   if (this._batch[aMediaList.guid] && this._batch[aMediaList.guid].isActive()) {
  2105.     // We are going to refresh all the nodes once we exit the batch so
  2106.     // we don't need any more of these notifications
  2107.     this._refreshPending = true;
  2108.     return true;
  2109.   }
  2110.   else {
  2111.     if (aMediaList instanceof Ci.sbILibrary) {
  2112.       var libraryGUID = aMediaList.guid;
  2113.  
  2114.       var node = this._servicePane.root;
  2115.       this._removeListNodesForLibrary(node, libraryGUID);
  2116.     }
  2117.     return false;
  2118.   }
  2119. }
  2120. sbLibraryServicePane.prototype.onBatchBegin =
  2121. function sbLibraryServicePane_onBatchBegin(aMediaList) {
  2122.   //logcall(arguments);
  2123.   if (!this._batch[aMediaList.guid]) {
  2124.     this._batch[aMediaList.guid] = new LibraryUtils.BatchHelper();
  2125.   }
  2126.   this._batch[aMediaList.guid].begin();
  2127. }
  2128. sbLibraryServicePane.prototype.onBatchEnd =
  2129. function sbLibraryServicePane_onBatchEnd(aMediaList) {
  2130.   //logcall(arguments);
  2131.   if (!this._batch[aMediaList.guid]) {
  2132.     return;
  2133.   }
  2134.  
  2135.   this._batch[aMediaList.guid].end();
  2136.   if (!this._batch[aMediaList.guid].isActive() && this._refreshPending) {
  2137.     this._refreshLibraryNodes(aMediaList);
  2138.     this._refreshPending = false;
  2139.   }
  2140.  
  2141. }
  2142.  
  2143. sbLibraryServicePane.prototype._initLibraryManager =
  2144. function sbLibraryServicePane__initLibraryManager() {
  2145.   // get the library manager
  2146.   this._libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  2147.                            .getService(Ci.sbILibraryManager);
  2148.  
  2149.   // register for notifications so that we can keep the service pane
  2150.   // in sync with the the libraries
  2151.   this._libraryManager.addListener(this);
  2152.  
  2153.   // Make sure to remove the library manager listener on shutdown
  2154.   var obs = Cc["@mozilla.org/observer-service;1"].
  2155.             getService(Ci.nsIObserverService);
  2156.   obs.addObserver(this, "songbird-library-manager-before-shutdown", false);
  2157.  
  2158.   this._addAllLibraries();
  2159. }
  2160.  
  2161. /////////////////
  2162. // nsIObserver //
  2163. /////////////////
  2164.  
  2165. sbLibraryServicePane.prototype.observe =
  2166. function sbLibraryServicePane_observe(subject, topic, data) {
  2167.  
  2168.   var obs = Cc["@mozilla.org/observer-service;1"]
  2169.               .getService(Ci.nsIObserverService);
  2170.  
  2171.   if (topic == "songbird-library-manager-before-shutdown") {
  2172.     obs.removeObserver(this, "songbird-library-manager-before-shutdown");
  2173.  
  2174.     var libraryManager = Cc['@songbirdnest.com/Songbird/library/Manager;1']
  2175.                            .getService(Ci.sbILibraryManager);
  2176.     libraryManager.removeListener(this);
  2177.     for each (let lib in this._libraries) {
  2178.       lib.removeListener(this);
  2179.     }
  2180.   }
  2181. }
  2182.  
  2183. ///////////
  2184. // XPCOM //
  2185. ///////////
  2186.  
  2187. var NSGetModule = XPCOMUtils.generateNSGetModule([sbLibraryServicePane]);
  2188.