home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Multimedia / Songbird / Songbird_2.0.0-2311_windows-i686-msvc8.exe / components / sbDynamicPlaylist.js < prev    next >
Text File  |  2012-06-08  |  24KB  |  723 lines

  1. /*
  2. //
  3. // BEGIN SONGBIRD GPL
  4. //
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2009 POTI, Inc.
  8. // http://songbirdnest.com
  9. //
  10. // This file may be licensed under the terms of of the
  11. // GNU General Public License Version 2 (the "GPL").
  12. //
  13. // Software distributed under the License is distributed
  14. // on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
  15. // express or implied. See the GPL for the specific language
  16. // governing rights and limitations.
  17. //
  18. // You should have received a copy of the GPL along with this
  19. // program. If not, go to http://www.gnu.org/licenses/gpl.html
  20. // or write to the Free Software Foundation, Inc.,
  21. // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. //
  23. // END SONGBIRD GPL
  24. //
  25. */
  26.  
  27. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  28. Components.utils.import("resource://app/jsmodules/ArrayConverter.jsm");
  29. Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
  30. Components.utils.import("resource://app/jsmodules/sbLibraryUtils.jsm");
  31.  
  32. const Cc = Components.classes;
  33. const Ci = Components.interfaces;
  34. const Cr = Components.results;
  35.  
  36. const SB_NS = "http://songbirdnest.com/data/1.0#";
  37. const SB_PROP_ISSUBSCRIPTION       = SB_NS + "isSubscription";
  38. const SB_PROP_SUBSCRIPTIONURL      = SB_NS + "subscriptionURL";
  39. const SB_PROP_SUBSCRIPTIONINTERVAL = SB_NS + "subscriptionInterval";
  40. const SB_PROP_SUBSCRIPTIONNEXTRUN  = SB_NS + "subscriptionNextRun";
  41. const SB_PROP_DESTINATION          = SB_NS + "destination"
  42.  
  43. const SB_FINAL_UI_STARTUP_TOPIC = "final-ui-startup";
  44. const SB_DEVICE_MANAGER_READY_TOPIC = "songbird-device-manager-ready";
  45. const SB_LIBRARY_MANAGER_READY_TOPIC = "songbird-library-manager-ready";
  46. const SB_LIBRARY_MANAGER_BEFORE_SHUTDOWN_TOPIC = "songbird-library-manager-before-shutdown";
  47.  
  48. const UPDATE_INTERVAL = 60 * 1000;
  49.  
  50. function d(s) {
  51.   //dump("------------------> sbDynamicPlaylistService " + s + "\n");
  52. }
  53.  
  54. function TRACE(s) {
  55.   //dump("------------------> " + s + "\n");
  56. }
  57.  
  58. // XXXsteve Any time we get a media item's guid and want to use it as an
  59. // object property, run it through this function first.  This will force the
  60. // string to be a javascript land string rather than a string dependent on the
  61. // buffer held by the media item.  This it to prevent crashes when the atom
  62. // created from that string gets deleted at shutdown.  See bmo 391590
  63. function FIX(s) {
  64.   var g = "x" + s;
  65.   g = g.substr(1);
  66.   return g;
  67. }
  68.  
  69. function sbDynamicPlaylistService()
  70. {
  71.   this._started = false;
  72.   this._scheduledLists = {};
  73.   this._ignoreLibraryNotifications = {};
  74.   this._libraryBatch = new LibraryUtils.MultiBatchHelper();
  75.   this._libraryRefreshPending = {};
  76.  
  77.   var obs = Cc["@mozilla.org/observer-service;1"]
  78.               .getService(Ci.nsIObserverService);
  79.   //obs.addObserver(this, SB_LIBRARY_MANAGER_READY_TOPIC, false);
  80.   obs.addObserver(this, SB_DEVICE_MANAGER_READY_TOPIC, false);
  81.   obs.addObserver(this, SB_LIBRARY_MANAGER_BEFORE_SHUTDOWN_TOPIC, false);
  82. }
  83.  
  84. sbDynamicPlaylistService.prototype = {
  85.   classDescription: "Dynamic Playlist Service",
  86.   classID:    Components.ID("{10a07ef5-8ab6-4728-9172-4e609f65b4a2}"),
  87.   contractID: "@songbirdnest.com/Songbird/Library/DynamicPlaylistService;1"
  88. }
  89.  
  90. sbDynamicPlaylistService.prototype.QueryInterface =
  91. XPCOMUtils.generateQI([
  92.   Ci.sbIDynamicPlaylistService,
  93.   Ci.nsIObserver,
  94.   Ci.nsITimerCallback,
  95.   Ci.sbILibraryManagerListener,
  96.   Ci.sbIMediaListListener
  97. ]);
  98.  
  99. /*
  100.  * Startup method for the dynamic playlist service
  101.  */
  102. sbDynamicPlaylistService.prototype._startup =
  103. function sbDynamicPlaylistService__startup()
  104. {
  105.   TRACE("sbDynamicPlaylistService::_startup\n");
  106.  
  107.   if (this._started) {
  108.     return;
  109.   }
  110.  
  111.   // Register properties
  112.   var propMan = Cc["@songbirdnest.com/Songbird/Properties/PropertyManager;1"]
  113.                   .getService(Ci.sbIPropertyManager);
  114.  
  115.   var prop = Cc["@songbirdnest.com/Songbird/Properties/Info/Boolean;1"]
  116.                .createInstance(Ci.sbIBooleanPropertyInfo);
  117.   prop.id = SB_PROP_ISSUBSCRIPTION;
  118.   prop.userViewable = false;
  119.   prop.userEditable = false;
  120.   prop.remoteReadable = true;
  121.   prop.remoteWritable = true;
  122.   propMan.addPropertyInfo(prop);
  123.  
  124.   prop = Cc["@songbirdnest.com/Songbird/Properties/Info/URI;1"]
  125.            .createInstance(Ci.sbIURIPropertyInfo);
  126.   prop.id = SB_PROP_SUBSCRIPTIONURL;
  127.   prop.userViewable = false;
  128.   prop.userEditable = false;
  129.   prop.remoteReadable = true;
  130.   prop.remoteWritable = true;
  131.   propMan.addPropertyInfo(prop);
  132.  
  133.   var prop = Cc["@songbirdnest.com/Songbird/Properties/Info/Number;1"]
  134.                .createInstance(Ci.sbINumberPropertyInfo);
  135.   prop.id = SB_PROP_SUBSCRIPTIONINTERVAL;
  136.   prop.userViewable = false;
  137.   prop.userEditable = false;
  138.   prop.remoteReadable = true;
  139.   prop.remoteWritable = true;
  140.   prop.minValue = 0;
  141.   propMan.addPropertyInfo(prop);
  142.  
  143.   var prop = Cc["@songbirdnest.com/Songbird/Properties/Info/Number;1"]
  144.                .createInstance(Ci.sbINumberPropertyInfo);
  145.   prop.id = SB_PROP_SUBSCRIPTIONNEXTRUN;
  146.   prop.userViewable = false;
  147.   prop.userEditable = false;
  148.   prop.remoteReadable = true;
  149.   prop.remoteWritable = true;
  150.   prop.minValue = 0;
  151.   propMan.addPropertyInfo(prop);
  152.  
  153.   // Set up our timer
  154.   this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  155.   this._timer.initWithCallback(this,
  156.                                UPDATE_INTERVAL,
  157.                                Ci.nsITimer.TYPE_REPEATING_SLACK);
  158.  
  159.   // Attach an observer so we can track library additions / removals
  160.   var libraryManager = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  161.                          .getService(Ci.sbILibraryManager);
  162.   libraryManager.addListener(this);
  163.  
  164.   // Schedule all of the dynamic media lists in all libraries
  165.   var libraries = libraryManager.getLibraries();
  166.   while (libraries.hasMoreElements()) {
  167.     var library = libraries.getNext();
  168.     this.onLibraryRegistered(library);
  169.   }
  170.  
  171.   // Do a queue run just in care some things should have been refreshed while
  172.   // we were off
  173.   this._updateSubscriptions(false);
  174.  
  175.   this._started = true;
  176. }
  177.  
  178. sbDynamicPlaylistService.prototype._shutdown =
  179. function sbDynamicPlaylistService__shutdown()
  180. {
  181.   var obs = Cc["@mozilla.org/observer-service;1"]
  182.               .getService(Ci.nsIObserverService);
  183.   //obs.removeObserver(this, SB_LIBRARY_MANAGER_READY_TOPIC);
  184.   obs.removeObserver(this, SB_DEVICE_MANAGER_READY_TOPIC);
  185.   obs.removeObserver(this, SB_LIBRARY_MANAGER_BEFORE_SHUTDOWN_TOPIC);
  186.  
  187.   if (this._started) {
  188.     this._timer.cancel();
  189.     this._timer = null;
  190.  
  191.     // Stop listening to the library manager
  192.     var libraryManager = Cc["@songbirdnest.com/Songbird/library/Manager;1"]
  193.                            .getService(Ci.sbILibraryManager);
  194.     libraryManager.removeListener(this);
  195.  
  196.     // Stop listening to each library
  197.     var libraries = libraryManager.getLibraries();
  198.     while (libraries.hasMoreElements()) {
  199.       this.onLibraryUnregistered(libraries.getNext());
  200.     }
  201.   }
  202.  
  203.   this._started = false;
  204. }
  205.  
  206. sbDynamicPlaylistService.prototype._scheduleLibrary =
  207. function sbDynamicPlaylistService__scheduleLibrary(aLibrary)
  208. {
  209.   // Add all the media lists that are subscriptions to the _scheduledList
  210.   // array
  211.   var self = this;
  212.   var listener = {
  213.     onEnumerationBegin: function() {
  214.     },
  215.     onEnumeratedItem: function(list, item) {
  216.       self._scheduledLists[FIX(item.guid)] = item;
  217.     },
  218.     onEnumerationEnd: function() {
  219.     }
  220.   };
  221.  
  222.   var pa = Cc["@songbirdnest.com/Songbird/Properties/MutablePropertyArray;1"]
  223.              .createInstance(Ci.sbIMutablePropertyArray);
  224.   pa.appendProperty(SB_NS + "isList", "1");
  225.   pa.appendProperty(SB_PROP_ISSUBSCRIPTION, "1");
  226.  
  227.   aLibrary.enumerateItemsByProperties(pa, listener);
  228.  
  229. }
  230.  
  231. sbDynamicPlaylistService.prototype._isDynamicPlaylist =
  232. function sbDynamicPlaylistService__isDynamicPlaylist(aMediaList)
  233. {
  234.   return aMediaList.getProperty(SB_PROP_ISSUBSCRIPTION) == "1";
  235. }
  236.  
  237. sbDynamicPlaylistService.prototype._removeListsFromLibrary =
  238. function sbDynamicPlaylistService__removeListsFromLibrary(aLibrary)
  239. {
  240.   for each (var list in this._scheduledLists) {
  241.     if (list.library.equals(aLibrary)) {
  242.         delete this._scheduledLists[FIX(list.guid)];
  243.     }
  244.   }
  245. }
  246.  
  247. sbDynamicPlaylistService.prototype._updateSubscriptions =
  248. function sbDynamicPlaylistService__updateSubscriptions(aForce)
  249. {
  250.   var now = (new Date()).getTime() * 1000;
  251.   for each (var list in this._scheduledLists) {
  252.  
  253.     var nextRun  = list.getProperty(SB_PROP_SUBSCRIPTIONNEXTRUN);
  254.     var interval = list.getProperty(SB_PROP_SUBSCRIPTIONINTERVAL);
  255.  
  256.     // If there is no next run set, set it
  257.     if (!nextRun) {
  258.       nextRun = this._setNextRun(list);
  259.     }
  260.  
  261.     // Update this list if we are forced or if we have an interval and the
  262.     // current time is after the next run
  263.     if (aForce || (interval && now > nextRun)) {
  264.       try {
  265.         this._updateList(list);
  266.       }
  267.       catch (e) {
  268.         // Log and continue
  269.         Components.utils.reportError(e);
  270.       }
  271.     }
  272.   }
  273. }
  274.  
  275. sbDynamicPlaylistService.prototype._updateList =
  276. function sbDynamicPlaylistService__updateList(aList)
  277. {
  278.   // Get the playlist reader and load the tracks from this url
  279.   var manager = Cc["@songbirdnest.com/Songbird/PlaylistReaderManager;1"]
  280.                   .getService(Ci.sbIPlaylistReaderManager);
  281.   var listener = Cc["@songbirdnest.com/Songbird/PlaylistReaderListener;1"]
  282.                    .createInstance(Ci.sbIPlaylistReaderListener);
  283.  
  284.   var ioService = Cc["@mozilla.org/network/io-service;1"]
  285.                     .getService(Ci.nsIIOService);
  286.   var uri = ioService.newURI(aList.getProperty(SB_PROP_SUBSCRIPTIONURL), null, null);
  287.  
  288.   var observer = new sbPlaylistReaderListenerObserver(this, aList);
  289.   listener.observer = observer;
  290.  
  291.   manager.loadPlaylist(uri, aList, null, true, listener);
  292. }
  293.  
  294. sbDynamicPlaylistService.prototype._setNextRun =
  295. function sbDynamicPlaylistService__setNextRun(aList)
  296. {
  297.   var now = (new Date()).getTime() * 1000;
  298.   var interval = aList.getProperty(SB_PROP_SUBSCRIPTIONINTERVAL);
  299.  
  300.   // Interval is in seconds, next run is in micro seconds
  301.   var nextRun = now + (interval * 1000 * 1000);
  302.   aList.setProperty(SB_PROP_SUBSCRIPTIONNEXTRUN, nextRun);
  303.   return nextRun;
  304. }
  305.  
  306. sbDynamicPlaylistService.prototype._beginIgnore =
  307. function sbDynamicPlaylistService__beginIgnore(aLibrary)
  308. {
  309.   var count = this._ignoreLibraryNotifications[FIX(aLibrary.guid)];
  310.   if (count)
  311.     this._ignoreLibraryNotifications[FIX(aLibrary.guid)] = count + 1;
  312.   else
  313.     this._ignoreLibraryNotifications[FIX(aLibrary.guid)] = 1;
  314. }
  315.  
  316. sbDynamicPlaylistService.prototype._endIgnore =
  317. function sbDynamicPlaylistService__endIgnore(aLibrary)
  318. {
  319.   var count = this._ignoreLibraryNotifications[FIX(aLibrary.guid)];
  320.   if (count)
  321.     this._ignoreLibraryNotifications[FIX(aLibrary.guid)] = count - 1;
  322.   else
  323.     throw Cr.NS_ERROR_FAILURE;
  324. }
  325.  
  326. sbDynamicPlaylistService.prototype._ignore =
  327. function sbDynamicPlaylistService__ignore(aLibrary)
  328. {
  329.   var count = this._ignoreLibraryNotifications[FIX(aLibrary.guid)];
  330.   return count && count > 0;
  331. }
  332.  
  333. // sbIDynamicPlaylistService
  334. sbDynamicPlaylistService.prototype.createList =
  335. function sbDynamicPlaylistService_createList(aLibrary,
  336.                                              aUri,
  337.                                              aIntervalSeconds,
  338.                                              aDestinationDirectory)
  339. {
  340.   if (!aLibrary ||
  341.       !aUri ||
  342.       aIntervalSeconds == 0 ||
  343.       (aDestinationDirectory && !aDestinationDirectory.isDirectory()))
  344.     throw Cr.NS_ERROR_INVALID_ARG;
  345.  
  346.   try {
  347.     this._beginIgnore(aLibrary);
  348.     var list = aLibrary.createMediaList("dynamic");
  349.     this._scheduledLists[FIX(list.guid)] = list;
  350.     this.updateList(list, aUri, aIntervalSeconds, aDestinationDirectory);
  351.   }
  352.   finally {
  353.     this._endIgnore(aLibrary);
  354.   }
  355.   return list;
  356. }
  357.  
  358. sbDynamicPlaylistService.prototype.createPodcast =
  359. function sbDynamicPlaylistService_createPodcast(aLibrary,
  360.                                                 aUri,
  361.                                                 aProperties)
  362. {
  363.   if (!aLibrary || !aUri)
  364.     throw Cr.NS_ERROR_INVALID_ARG;
  365.  
  366.   try {
  367.     // Ignore library changes.
  368.     this._beginIgnore(aLibrary);
  369.  
  370.     // Make a deep mutable copy of any properties.
  371.     var properties =
  372.           Cc["@songbirdnest.com/Songbird/Properties/MutablePropertyArray;1"]
  373.             .createInstance(Ci.sbIMutablePropertyArray);
  374.     if (aProperties) {
  375.       var propertyCount = aProperties.length;
  376.       for (var i = 0; i < propertyCount; i++) {
  377.         var property = aProperties.getPropertyAt(i);
  378.         properties.appendProperty(property.id, property.value);
  379.       }
  380.     }
  381.  
  382.     // Create the podcast media list.
  383.     properties.appendProperty(SBProperties.customType, "podcast");
  384.     var list = aLibrary.createMediaList("dynamic", properties);
  385.  
  386.     // Use the URI host for the podcast name for now.
  387.     //XXXeps need to read the podcast name from the feed.
  388.     list.name = aUri.host;
  389.  
  390.     //XXXeps set default podcast settings unless set in aProperties and add to
  391.     //XXXepsscheduled list
  392.   }
  393.   finally {
  394.     // Stop ignoring library changes.
  395.     this._endIgnore(aLibrary);
  396.   }
  397.   return list;
  398. }
  399.  
  400. sbDynamicPlaylistService.prototype.updateList =
  401. function sbDynamicPlaylistService_updateList(aMediaList,
  402.                                              aUri,
  403.                                              aIntervalSeconds,
  404.                                              aDestinationDirectory)
  405. {
  406.   if (!aMediaList ||
  407.       !this._isDynamicPlaylist(aMediaList) ||
  408.       !aUri ||
  409.       aIntervalSeconds == 0 ||
  410.       (aDestinationDirectory && !aDestinationDirectory.isDirectory()))
  411.     throw Cr.NS_ERROR_INVALID_ARG;
  412.  
  413.   try {
  414.     this._beginIgnore(aMediaList.library);
  415.     aMediaList.setProperty(SB_PROP_SUBSCRIPTIONURL, aUri.spec);
  416.     aMediaList.setProperty(SB_PROP_SUBSCRIPTIONINTERVAL, aIntervalSeconds);
  417.  
  418.     if (aDestinationDirectory) {
  419.       var ioService = Cc["@mozilla.org/network/io-service;1"]
  420.                         .getService(Ci.nsIIOService);
  421.       var destinationUri = ioService.newFileURI(aDestinationDirectory);
  422.       aMediaList.setProperty(SB_PROP_DESTINATION, destinationUri.spec);
  423.     }
  424.   }
  425.   finally {
  426.     this._endIgnore(aMediaList.library);
  427.   }
  428.  
  429. }
  430.  
  431. sbDynamicPlaylistService.prototype.updateAllNow =
  432. function sbDynamicPlaylistService_updateAllNow()
  433. {
  434.   this._updateSubscriptions(true);
  435. }
  436.  
  437. sbDynamicPlaylistService.prototype.updateNow =
  438. function sbDynamicPlaylistService_updateNow(aMediaList)
  439. {
  440.   var interval = aMediaList.getProperty(SB_PROP_SUBSCRIPTIONINTERVAL);
  441.   var url = aMediaList.getProperty(SB_PROP_SUBSCRIPTIONURL);
  442.   if (interval && url)
  443.     this._updateList(aMediaList);
  444.   else
  445.     throw Cr.NS_ERROR_INVALID_ARG;
  446. }
  447.  
  448. sbDynamicPlaylistService.prototype.__defineGetter__("scheduledLists",
  449. function sbDynamicPlaylistService_scheduledLists()
  450. {
  451.   var a = [];
  452.   for each (var list in this._scheduledLists) {
  453.     a.push(list);
  454.   }
  455.  
  456.   return ArrayConverter.enumerator(a);
  457. });
  458.  
  459. // sbILibraryManagerListener
  460. sbDynamicPlaylistService.prototype.onLibraryRegistered =
  461. function sbDynamicPlaylistService_onLibraryRegistered(aLibrary)
  462. {
  463.   TRACE("sbDynamicPlaylistService::onLibraryRegistered");
  464.  
  465.   // If a library is registered, attach a listener so we can be notified of
  466.   // media item change notifications.
  467.   var filter = SBProperties.createArray([[SB_PROP_ISSUBSCRIPTION, null]], false);
  468.   aLibrary.addListener(this,
  469.                        false,
  470.                        Ci.sbIMediaList.LISTENER_FLAGS_ALL &
  471.                          ~Ci.sbIMediaList.LISTENER_FLAGS_AFTERITEMREMOVED,
  472.                        filter);
  473.  
  474.   // Schedule all of the dynamic media lists in this library
  475.   this._scheduleLibrary(aLibrary);
  476. }
  477.  
  478. sbDynamicPlaylistService.prototype.onLibraryUnregistered =
  479. function sbDynamicPlaylistService_onLibraryUnregistered(aLibrary)
  480. {
  481.   TRACE("sbDynamicPlaylistService::onLibraryUnregistered");
  482.   // Remove all the lists that are in this library
  483.   if (aLibrary instanceof Ci.sbILibrary) {
  484.     this._removeListsFromLibrary(aLibrary);
  485.  
  486.     // Remove our listener from this library
  487.     aLibrary.removeListener(this);
  488.   }
  489. }
  490.  
  491. // sbIMediaListListener
  492. sbDynamicPlaylistService.prototype.onItemAdded =
  493. function sbDynamicPlaylistService_onItemAdded(aMediaList,
  494.                                               aMediaItem,
  495.                                               aIndex)
  496. {
  497.   if (this._ignore(aMediaList.library))
  498.     return;
  499.  
  500.   // If we are in a batch, we are going to refresh the list of dynamic
  501.   // playlists when the batch ends, so we don't need any more notifications
  502.   if (this._libraryBatch.isActive(aMediaList.library)) {
  503.     this._libraryRefreshPending[FIX(aMediaList.library.guid)] = true;
  504.     return true;
  505.   }
  506.  
  507.   // Is this new item a dynamic media list?
  508.   if (this._isDynamicPlaylist(aMediaItem)) {
  509.  
  510.     // If there is no next run time, set one
  511.     if (aMediaItem.getProperty(SB_PROP_SUBSCRIPTIONNEXTRUN)) {
  512.       this._setNextRun(aMediaItem);
  513.     }
  514.     this._scheduledLists[FIX(aMediaItem.guid)] = aMediaItem;
  515.     d("A new dynamic playlist was added");
  516.   }
  517.  
  518. }
  519.  
  520. sbDynamicPlaylistService.prototype.onBeforeItemRemoved =
  521. function sbDynamicPlaylistService_onBeforeItemRemoved(aMediaList,
  522.                                                       aMediaItem,
  523.                                                       aIndex)
  524. {
  525.   if (this._ignore(aMediaList.library))
  526.     return;
  527.  
  528.   // If we are in a batch, we are going to refresh the list of dynamic
  529.   // playlists when the batch ends, so we don't need any more notifications
  530.   if (this._libraryBatch.isActive(aMediaList.library)) {
  531.     this._libraryRefreshPending[FIX(aMediaList.library.guid)] = true;
  532.     return true;
  533.   }
  534.  
  535.   delete this._scheduledLists[FIX(aMediaItem.guid)];
  536. }
  537.  
  538. sbDynamicPlaylistService.prototype.onAfterItemRemoved =
  539. function sbDynamicPlaylistService_onAfterItemRemoved(aMediaList,
  540.                                                      aMediaItem,
  541.                                                      aIndex)
  542. {
  543.   // do nothing
  544. }
  545.  
  546. sbDynamicPlaylistService.prototype.onItemUpdated =
  547. function sbDynamicPlaylistService_onItemUpdated(aMediaList,
  548.                                                 aMediaItem,
  549.                                                 aProperties)
  550. {
  551.   if (this._ignore(aMediaList.library))
  552.     return;
  553.  
  554.   // If we are in a batch, we are going to refresh the list of dynamic
  555.   // playlists when the batch ends, so we don't need any more notifications
  556.   if (this._libraryBatch.isActive(aMediaList.library)) {
  557.     this._libraryRefreshPending[FIX(aMediaList.library.guid)] = true;
  558.     return true;
  559.   }
  560.  
  561.   // Make sure this list is scheduled
  562.   this._scheduledLists[FIX(aMediaItem.guid)] = aMediaItem;
  563. }
  564.  
  565. sbDynamicPlaylistService.prototype.onItemMoved =
  566. function sbDynamicPlaylistService_onItemMoved(aMediaList,
  567.                                               aFromIndex,
  568.                                               aToIndex)
  569. {
  570. }
  571.  
  572.  
  573. sbDynamicPlaylistService.prototype.onBeforeListCleared =
  574. function sbDynamicPlaylistService_onBeforeListCleared(aMediaList,
  575.                                                       aExcludeLists)
  576. {
  577.   return true;
  578. }
  579.  
  580. sbDynamicPlaylistService.prototype.onListCleared =
  581. function sbDynamicPlaylistService_onListCleared(aMediaList,
  582.                                                 aExcludeLists)
  583. {
  584.   if (this._ignore(aMediaList.library))
  585.     return;
  586.  
  587.   // If we are in a batch, we are going to refresh the list of dynamic
  588.   // playlists when the batch ends, so we don't need any more notifications
  589.   if (this._libraryBatch.isActive(aMediaList.library)) {
  590.     this._libraryRefreshPending[FIX(aMediaList.library.guid)] = true;
  591.     return true;
  592.   }
  593.  
  594.   // Clear all schedueld items from this library
  595.   this._removeListsFromLibrary(aMediaList.library);
  596. }
  597.  
  598. sbDynamicPlaylistService.prototype.onBatchBegin =
  599. function sbDynamicPlaylistService_onBatchBegin(aMediaList)
  600. {
  601.   this._libraryBatch.begin(aMediaList.library);
  602. }
  603.  
  604. sbDynamicPlaylistService.prototype.onBatchEnd =
  605. function sbDynamicPlaylistService_onBatchEnd(aMediaList)
  606. {
  607.   var library = aMediaList.library;
  608.   this._libraryBatch.end(library);
  609.  
  610.   if (!this._libraryBatch.isActive(library)) {
  611.  
  612.     // If there is a refresh pending for this library, do it
  613.     if (this._libraryRefreshPending[FIX(library.guid)]) {
  614.  
  615.       d("Refreshing dynamic playlists in library " + library);
  616.       this._removeListsFromLibrary(library);
  617.       this._scheduleLibrary(library);
  618.  
  619.       this._libraryRefreshPending[FIX(library.guid)] = false;
  620.     }
  621.   }
  622. }
  623.  
  624. // nsITimerCallback
  625. sbDynamicPlaylistService.prototype.notify =
  626. function sbDynamicPlaylistService_notify(aTimer)
  627. {
  628.   this._updateSubscriptions(false);
  629. }
  630.  
  631. // nsIObserver
  632. sbDynamicPlaylistService.prototype.observe =
  633. function sbDynamicPlaylistService_observe(aSubject, aTopic, aData)
  634. {
  635.   if (aTopic == SB_DEVICE_MANAGER_READY_TOPIC) {
  636.     this._startup();
  637.   }
  638.   else if(aTopic == SB_LIBRARY_MANAGER_BEFORE_SHUTDOWN_TOPIC) {
  639.     this._shutdown();
  640.   }
  641. }
  642.  
  643. function sbPlaylistReaderListenerObserver(aService, aList) {
  644.   this._service = aService;
  645.   this._list = aList;
  646.   this._oldLength = aList.length;
  647. }
  648.  
  649. sbPlaylistReaderListenerObserver.prototype.observe =
  650. function sbPlaylistReaderListenerObserver_observe(aSubject, aTopic, aData)
  651. {
  652.   var ioService = Cc["@mozilla.org/network/io-service;1"]
  653.                     .getService(Ci.nsIIOService);
  654.   var uri = ioService.newURI(this._list.getProperty(SB_PROP_SUBSCRIPTIONURL), null, null);
  655.  
  656.   TRACE("Updated list with uri " + uri.spec);
  657.  
  658.   // Schedule the next run for this list
  659.   this._service._setNextRun(this._list);
  660.  
  661.   // Check to see if this list has a custom download destination, and that
  662.   // the destination is a directory
  663.   var destination = this._list.getProperty(SB_PROP_DESTINATION);
  664.   var destinationDir;
  665.   if (destination) {
  666.     try {
  667.       destinationDir = ioService.newURI(destination, null, null)
  668.                                 .QueryInterface(Ci.nsIFileURL).file;
  669.       if (!destinationDir.isDirectory())
  670.         destinationDir = null;
  671.     }
  672.     catch (e) {
  673.       // If we couldn't get a destination dir, use the default
  674.       destinationDir = null;
  675.     }
  676.   }
  677.  
  678.   // Start a metadata job for the newly added items.  If there is a custom
  679.   // destination, update each new item with it
  680.   var array = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"].createInstance(Ci.nsIMutableArray);
  681.   for (var i = this._oldLength; i < this._list.length; i++) {
  682.     var item = this._list.getItemByIndex(i);
  683.     if (destinationDir) {
  684.       var itemUri = item.contentSrc;
  685.  
  686.       // If this is not a nsIURL, let the download manager figure it out
  687.       if (itemUri instanceof Ci.nsIURL) {
  688.         var dest = destinationDir.clone();
  689.         dest.append(itemUri.QueryInterface(Ci.nsIURL).fileName);
  690.         var destUri = ioService.newFileURI(dest);
  691.         item.setProperty(SB_PROP_DESTINATION, destUri.spec);
  692.       }
  693.     }
  694.     array.appendElement(item, false);
  695.   }
  696.   
  697.   var metadataService = Cc["@songbirdnest.com/Songbird/FileMetadataService;1"]
  698.                           .getService(Ci.sbIFileMetadataService);
  699.   var metadataJob = metadataService.read(array);
  700.   
  701.   // Download the new items
  702.   var ddh =
  703.     Cc["@songbirdnest.com/Songbird/DownloadDeviceHelper;1"]
  704.       .getService(Ci.sbIDownloadDeviceHelper);
  705.   ddh.downloadSome(array.enumerate());
  706. }
  707.  
  708. function NSGetModule(compMgr, fileSpec) {
  709.  
  710.   return XPCOMUtils.generateModule([
  711.     sbDynamicPlaylistService
  712.   ],
  713.   function(aCompMgr, aFileSpec, aLocation) {
  714.     XPCOMUtils.categoryManager.addCategoryEntry(
  715.       "app-startup",
  716.       sbDynamicPlaylistService.prototype.classDescription,
  717.       "service," + sbDynamicPlaylistService.prototype.contractID,
  718.       true,
  719.       true);
  720.   });
  721. }
  722.  
  723.