sbLibraryServicePaneService.js
Go to the documentation of this file.
1 
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 
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 
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 
103 // nsISupports / XPCOM //
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
123 }];
125 // sbIServicePaneModule //
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,
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,
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,
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 
733 sbLibraryServicePane.prototype.onBeforeRename =
734 function sbLibraryServicePane_onBeforeRename(aNode) {
735 }
736 
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 
752 // sbILibraryServicePaneService //
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 
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 
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 
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 
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 
1098 // Private Methods //
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 
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 
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 
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 
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 
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 
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 
1334 sbLibraryServicePane.prototype._playlistAdded =
1335 function sbLibraryServicePane__playlistAdded(aMediaList) {
1336  //logcall(arguments);
1337  this._ensureMediaListNodeExists(aMediaList);
1338 }
1339 
1340 
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 
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 
1375 sbLibraryServicePane.prototype._itemURN =
1376 function sbLibraryServicePane__itemURN(aMediaItem) {
1377  return URN_PREFIX_ITEM + aMediaItem.guid;
1378 }
1379 
1380 
1384 sbLibraryServicePane.prototype._libraryURN =
1385 function sbLibraryServicePane__libraryURN(aLibrary) {
1386  return URN_PREFIX_LIBRARY + aLibrary.guid;
1387 }
1388 
1389 
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 
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 
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 
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 
1476 sbLibraryServicePane.prototype._ensureLibraryNodeExists =
1477 function sbLibraryServicePane__ensureLibraryNodeExists(aLibrary) {
1478  //logcall(arguments);
1479  var self = this;
1480 
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 
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 
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 
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 
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 
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 
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 
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 
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 
2016 // sbILibraryManagerListener //
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 
2031 // sbIMediaListListener //
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 
2162 // nsIObserver //
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 
2184 // XPCOM //
2186 
2187 var NSGetModule = XPCOMUtils.generateNSGetModule([sbLibraryServicePane]);
classDescription entry
Definition: FeedWriter.js:1427
const Cc
#define LOG(args)
function SBFormattedString(aKey, aParams, aDefault, aStringBundle)
function logcall(parentArgs)
menuItem id
Definition: FeedWriter.js:971
sbOSDControlService prototype className
var event
var DNDUtils
Definition: DropHelper.jsm:80
sbOSDControlService prototype QueryInterface
const TYPE_X_SB_TRANSFER_MEDIA_ITEMS
Definition: DropHelper.jsm:414
const char * types
ui plugin add("draggable","cursor",{start:function(e, ui){var t=$('body');if(t.css("cursor")) ui.options._cursor=t.css("cursor");t.css("cursor", ui.options.cursor);}, stop:function(e, ui){if(ui.options._cursor)$('body').css("cursor", ui.options._cursor);}})
function handle(request, response)
getService(Ci.sbIFaceplateManager)
function SBString(aKey, aDefault, aStringBundle)
Definition: StringUtils.jsm:93
const TYPE_X_SB_TRANSFER_MEDIA_LIST
Definition: DropHelper.jsm:413
this _contentSandbox label
Definition: FeedWriter.js:814
var count
Definition: test_bug7406.js:32
const CONTRACTID
var libraryManager
return null
Definition: FeedWriter.js:1143
let node
countRef value
Definition: FeedWriter.js:1423
const Cr
sbAutoDownloader prototype _libraryManager
observe topic
Definition: FeedWriter.js:1326
const Ci
Javascript wrappers for common library tasks.
var hidden
observe data
Definition: FeedWriter.js:1329
_getSelectedPageStyle s i
Array filter(tab.attributes, function(aAttr){return(_this.xulAttributes.indexOf(aAttr.name) >-1);}).forEach(tab.removeAttribute
var group