sbSmartMediaListsUpdater.js
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2010 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
25 const Cc = Components.classes;
26 const Ci = Components.interfaces;
27 const Cr = Components.results;
28 const Ce = Components.Exception;
29 
30 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
31 Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
32 Components.utils.import("resource://app/jsmodules/sbLibraryUtils.jsm");
33 Components.utils.import("resource://app/jsmodules/StringUtils.jsm");
34 
35 const debugLog = false;
36 
37 // ----------------------------------------------------------------------------
38 // ----------------------------------------------------------------------------
39 
41  // ask for a callback when the library manager is ready
42  var obs = Cc["@mozilla.org/observer-service;1"]
43  .getService(Ci.nsIObserverService);
44  obs.addObserver(this, 'songbird-library-manager-ready', false);
45 }
46 
48  // This is us.
50  classDescription: "Songbird Smart Medialists Updater Module",
51  classID : Components.ID("{35af253e-c7b0-40d6-a1a2-c747de924639}"),
52  contractID : "@songbirdnest.com/Songbird/SmartMediaListsUpdater;1",
53 
54  // medialistlistener batch count
55  _batchCount : 0,
56 
57  // hash table of properties that have been modified
58  _updatedProperties : {},
59 
60  // hash table of lists to update
61  _updateQueue : {},
62 
63  // hash table of lists to update condition to filter video items
64  _updateVideoQueue : {},
65 
66  // currently updating a batch of smart playlists
67  _updating : false,
68 
69  // Delay between the last db update and the first smart playlist rebuild
70  _updateInitialDelay : 1000,
71 
72  // Maximum delay between first db update and the delayed check (the actual time
73  // may still be more if a batch has not finished, ie, we never check inside batches)
74  _maxInitialDelay : 5000,
75 
76  // Timestamp of the first time we try to run the delayed batch, reset every time
77  // the check happens, but stays the same when it is further delayed)
78  _timerInitTime : null,
79 
80  // Cause one more checkForUpdate at the end of the current update
81  _causeMoreChecks : false,
82 
83  // Delay between each smart playlist rebuild
84  _updateSubsequentDelay : 500,
85 
86  // Query object for db access
87  _dbQuery : null,
88 
89  // db table names
90  _dirtyPropertiesTable : "smartplupd_dirty_properties",
91  _dirtyListsTable : "smartplupd_dirty_lists",
92  _updateVideoListsTable : "smartplupd_update_video_lists",
93  _monitor : null,
94 
95  // --------------------------------------------------------------------------
96  // setup init & shutdown
97  // --------------------------------------------------------------------------
98  observe: function(subject, topic, data) {
99  var obs = Cc["@mozilla.org/observer-service;1"]
100  .getService(Ci.nsIObserverService);
101 
102  if (topic == "songbird-library-manager-ready") {
103  // To register on final-ui-startup directly will break the library
104  // sort data rebuilding during library migration for unknown reason.
105  // Workaround by registering on songbird-library-manager-ready first.
106  obs.removeObserver(this, "songbird-library-manager-ready");
107  obs.addObserver(this, "final-ui-startup", false);
108  } else if (topic == "final-ui-startup") {
109  // Smart Playlist Update should happen after rebuild of sortable value.
110  // So wait for final-ui-startup to do the real update.
111  obs.removeObserver(this, "final-ui-startup");
112  obs.addObserver(this, "songbird-library-manager-before-shutdown", false);
113  this.initialize();
114  } else if (topic == "songbird-library-manager-before-shutdown") {
115  obs.removeObserver(this, "songbird-library-manager-before-shutdown");
116  this.shutdown();
117  }
118  },
119 
120  // --------------------------------------------------------------------------
121  // Initialization
122  // --------------------------------------------------------------------------
123  initialize: function() {
124  // listen for everything
125  this._monitor =
126  new LibraryUtils.GlobalMediaListListener(this,
127  false,
128  Ci.sbIMediaList.LISTENER_FLAGS_ITEMADDED |
129  Ci.sbIMediaList.LISTENER_FLAGS_AFTERITEMREMOVED |
130  Ci.sbIMediaList.LISTENER_FLAGS_ITEMUPDATED |
131  Ci.sbIMediaList.LISTENER_FLAGS_BATCHBEGIN |
132  Ci.sbIMediaList.LISTENER_FLAGS_BATCHEND |
133  Ci.sbIMediaList.LISTENER_FLAGS_LISTCLEARED,
134  null,
135  LibraryUtils.mainLibrary);
136 
137  // Init the dirty properties db and tables
138  this._dbQuery = Cc["@songbirdnest.com/Songbird/DatabaseQuery;1"]
139  .createInstance(Ci.sbIDatabaseQuery);
140  this._dbQuery.setAsyncQuery(false);
141  this._dbQuery.setDatabaseGUID("songbird");
142 
143  // holds the dirty properties
144  this._dbQuery.resetQuery();
145  this._dbQuery.addQuery("CREATE TABLE IF NOT EXISTS " +
146  this._dirtyPropertiesTable +
147  " (propertyid TEXT UNIQUE NOT NULL)");
148  this._dbQuery.execute();
149 
150  // holds the dirty lists
151  this._dbQuery.resetQuery();
152  this._dbQuery.addQuery("CREATE TABLE IF NOT EXISTS " +
153  this._dirtyListsTable +
154  " (listguid TEXT UNIQUE NOT NULL)");
155  this._dbQuery.execute();
156 
157  // holds the update lists
158  this._dbQuery.resetQuery();
159  this._dbQuery.addQuery("CREATE TABLE IF NOT EXISTS " +
160  this._updateVideoListsTable +
161  " (listguid TEXT UNIQUE NOT NULL)");
162  this._dbQuery.execute();
163 
164  // Apply a function to every row value #0 in a db table
165  var query = this._dbQuery;
166  function applyOnTableValues(aTableId, aFunction) {
167  query.resetQuery();
168  query.addQuery("SELECT * FROM " + aTableId);
169  query.execute();
170  var result = query.getResultObject();
171  if (result && result.getRowCount() > 0) {
172  for (var i = 0; i < result.getRowCount(); i++) {
173  var value = result.getRowCell(i, 0);
174  aFunction(value);
175  }
176  }
177  }
178 
179  // remember our context
180  var that = this;
181 
182  // If we have lists in the dirty lists db table, we need to add them to the
183  // _updateQueue js table.
184  function addToUpdateQueue(aListGuid) {
185  // check that the list is still valid, just in case.
186  var mediaList = LibraryUtils.mainLibrary.getMediaItem(aListGuid);
187  if (mediaList instanceof Ci.sbIMediaList &&
188  mediaList.type == "smart") {
189  that._updateQueue[aListGuid] = mediaList;
190  }
191  }
192  applyOnTableValues(this._dirtyListsTable, addToUpdateQueue);
193 
194  // If we have properties in the dirty properties db table, we need to add
195  // them to the _updatedProperties js table.
196  function addToModifiedProperties(aPropertyID) {
197  that._updatedProperties[aPropertyID] = true;
198  }
199  applyOnTableValues(this._dirtyPropertiesTable, addToModifiedProperties);
200 
201  // If we have lists in the update video lists db table, we need to add them
202  // to the _updateVideoQueue js table.
203  var length = 0;
204  function addToUpdateVideoQueue(aListGuid) {
205  // check that the list is still valid, just in case.
206  var mediaList = LibraryUtils.mainLibrary.getMediaItem(aListGuid);
207  if (mediaList instanceof Ci.sbIMediaList &&
208  mediaList.type == "smart") {
209  that._updateVideoQueue[aListGuid] = mediaList;
210  ++length;
211  }
212  }
213  applyOnTableValues(this._updateVideoListsTable, addToUpdateVideoQueue);
214 
215  // Update the smart playlists condition to filter video items.
216  if (length) {
217  this.updateListConditions();
218  this.resetUpdateVideoListsTable();
219  }
220 
221  // Start an update if needed, after a delay. This will update any list
222  // in the update queue, as well as any list whose content is dependent on
223  // one of the dirty properties, if any.
224  this.delayedUpdateCheck();
225  },
226 
227  // --------------------------------------------------------------------------
228  // Shutdown
229  // --------------------------------------------------------------------------
230  shutdown: function() {
231  // Clean up
232  this._timer = null;
233  this._secondaryTimer = null;
234  if (this._monitor) {
235  this._monitor.shutdown();
236  this._monitor = null;
237  }
238  },
239 
240  // --------------------------------------------------------------------------
241  // print a debug message in the console
242  // --------------------------------------------------------------------------
243  /*
244  _log: function(str, isError) {
245  if (debugLog ||
246  isError) {
247  Components.utils.reportError("smartMediaListsUpdater - " + str);
248  }
249  },
250  */
251 
252  // --------------------------------------------------------------------------
253  // Entering batch notification, increment counter
254  // --------------------------------------------------------------------------
255  onBatchBegin: function(aMediaList) {
256  this._batchCount++;
257  },
258 
259  // --------------------------------------------------------------------------
260  // Leaving batch notification, decrement counter.
261  // --------------------------------------------------------------------------
262  onBatchEnd: function(aMediaList) {
263  // If counter is zero, schedule an update check
264  if (--this._batchCount == 0) {
265  this.delayedUpdateCheck();
266  }
267  },
268 
269  // --------------------------------------------------------------------------
270  // Item was added, all its properties are new, so cause all smart
271  // playlists to eventually update
272  // --------------------------------------------------------------------------
273  onItemAdded: function(aMediaList, aMediaItem, aIndex) {
274  if (!aMediaList ||
275  aMediaList instanceof Ci.sbILibrary) {
276  if (aMediaItem instanceof Ci.sbIMediaList) {
277  return true;
278  }
279  // new item imported in library,
280  // record the '*' property in the update table
281  this.recordUpdateProperty('*');
282  } else {
283  // record the fact that this playlist changed
284  this.recordUpdateProperty(aMediaList.guid);
285  }
286  // if we are in a batch, return true so we're not told about item
287  // additions in this batch anymore
288  if (this._batchCount > 0)
289  return true;
290  // if we are not in a batch, schedule an update check
291  this.delayedUpdateCheck();
292  },
293 
294  // --------------------------------------------------------------------------
295  // Item was removed, all its properties are going away, so cause all
296  // smart playlists to eventually update
297  // --------------------------------------------------------------------------
298  onAfterItemRemoved: function(aMediaList, aMediaItem, aIndex) {
299  if (!aMediaList ||
300  aMediaList instanceof Ci.sbILibrary) {
301  if (aMediaItem instanceof Ci.sbIMediaList) {
302  return true;
303  }
304  // item removed from library,
305  // record the '*' property in the update table
306  this.recordUpdateProperty('*');
307  } else {
308  // record the fact that this playlist changed
309  this.recordUpdateProperty(aMediaList.guid);
310  }
311  // if we are in a batch, return true so we're not told about item
312  // additions in this batch anymore
313  if (this._batchCount > 0)
314  return true;
315  // if we are not in a batch, schedule an update check
316  this.delayedUpdateCheck();
317  },
318 
319  // --------------------------------------------------------------------------
320  // Item was updated, add the modified properties to the property table
321  // then cause the corresponding smart playlists to eventually update
322  // --------------------------------------------------------------------------
323  onItemUpdated: function(aMediaList, aMediaItem, aProperties) {
324  // We don't care about property changes on lists
325  if (aMediaItem instanceof Ci.sbIMediaList)
326  return true;
327 
328  // If we are in a batch, and the "update all" flag has been
329  // added to the property list, then there is no need
330  // to keep tracking which properties are dirty.
331  // This can save a huge amount of time when importing and
332  // scanning 10,000+ tracks.
333  if (this._batchCount > 0 && this._updatedProperties["*"]) {
334  return true;
335  }
336 
337  // for all properties in the array...
338  for (var i=0; i<aProperties.length; i++) {
339  var property = aProperties.getPropertyAt(i);
340  // record the property in the updated properties table
341  this.recordUpdateProperty(property.id);
342  }
343  // if we are in a batch, return false so that we keep receiving more
344  // notifications about property changes, since these could be about
345  // other properties than the ones we have been notified about in this
346  // call.
347  if (this._batchCount > 0)
348  return false;
349  // if we are not in a batch, schedule an update check
350  this.delayedUpdateCheck();
351  },
352 
353  // --------------------------------------------------------------------------
354  // list was cleared, add the list to the playlist update table
355  // then cause the corresponding smart playlists to update
356  // --------------------------------------------------------------------------
357  onListCleared: function(list, excludeLists) {
358  // record the fact that this playlist changed
359  this.recordUpdateProperty(list.guid);
360  if (this._batchCount > 0)
361  return false;
362  // if we are not in a batch, schedule an update check
363  this.delayedUpdateCheck();
364  },
365 
366  // --------------------------------------------------------------------------
367  // These do not get called since we don't ask for them, but still implement
368  // the complete interface
369  // --------------------------------------------------------------------------
370  onBeforeListCleared: function(list, excludeLists) {},
371  onBeforeItemRemoved: function(list, item, index) {},
372  onItemMoved: function(list, item, index) {},
373 
374  // --------------------------------------------------------------------------
375  // Add a property to the updated properties table
376  // --------------------------------------------------------------------------
377  recordUpdateProperty: function(aPropertyID) {
378  // if the property is not yet in the table, add it
379  if (!(aPropertyID in this._updatedProperties)) {
380  //this._log("Property change : " + aPropertyID);
381  this._updatedProperties[aPropertyID] = true;
382  // remember that this property is dirty, so that if we are shut down
383  // before the lists are updated, we can still resume the update on the
384  // next startup by calling delayedUpdateCheck, which will determine which
385  // lists need updating.
386  this.addPropertyToDirtyTable(aPropertyID);
387  }
388  },
389 
390  // --------------------------------------------------------------------------
391  // Update the smart playlist condition.
392  // --------------------------------------------------------------------------
393  updateListConditions: function() {
394  var propertyManager =
395  Cc["@songbirdnest.com/Songbird/Properties/PropertyManager;1"]
396  .getService(Ci.sbIPropertyManager);
397  var typePI = propertyManager.getPropertyInfo(SBProperties.contentType);
398 
399  var condition = {
400  property : SBProperties.contentType,
401  operator : typePI.getOperator(typePI.OPERATOR_NOTEQUALS),
402  leftValue : "video",
403  rightValue : null,
404  displayUnit : null,
405  };
406  var defaultSmartPlaylists = [
407  SBString("smart.defaultlist.highestrated", "Highest Rated"),
408  SBString("smart.defaultlist.mostplayed", "Most Played"),
409  SBString("smart.defaultlist.recentlyadded", "Recently Added"),
410  SBString("smart.defaultlist.recentlyplayed", "Recently Played")
411  ];
412  var list;
413 
414  function objectConverter(a) {
415  var obj = {};
416  for (var i = 0; i < a.length; ++i) {
417  obj[a[i]] = '';
418  }
419  return obj;
420  }
421 
422  for (var guid in this._updateVideoQueue) {
423  list = this._updateVideoQueue[guid];
424  // Append the condition to filter video items.
425  if (list.name in objectConverter(defaultSmartPlaylists)) {
426  list.appendCondition(condition.property,
427  condition.operator,
428  condition.leftValue,
429  condition.rightValue,
430  condition.displayUnit);
431  }
432  else
433  continue;
434 
435  list.rebuild();
436  }
437  },
438 
439  // --------------------------------------------------------------------------
440  // Returns an array of all smart playlists
441  // --------------------------------------------------------------------------
442  getSmartPlaylists: function() {
443  var enumListener = {
444  items: [],
445  onEnumerationBegin: function(aMediaList) { },
446  onEnumerationEnd: function(aMediaList) { },
447  onEnumeratedItem: function(aMediaList, aMediaItem) {
448  // if this list is a smart playlist, add it to the array
449  if (aMediaItem.type == 'smart') {
450  this.items.push(aMediaItem);
451  }
452  // continue enumeration
453  return Ci.sbIMediaListEnumerationListener.CONTINUE;
454  },
456  XPCOMUtils.generateQI([Ci.sbIMediaListEnumerationListener])
457  };
458 
459  // create a property array so we can specify that we only want items with
460  // isList == 1, and hidden == 0
461  var pa = Cc["@songbirdnest.com/Songbird/Properties/MutablePropertyArray;1"]
462  .createInstance(Ci.sbIMutablePropertyArray);
463  pa.appendProperty(SBProperties.isList, "1");
464  pa.appendProperty(SBProperties.hidden, "0");
465 
466  // start the enumeration
467  LibraryUtils.mainLibrary.
468  enumerateItemsByProperties(pa,
469  enumListener,
470  Ci.sbIMediaList.ENUMERATIONTYPE_LOCKING);
471 
472  // return the array of smart playlists
473  return enumListener.items;
474  },
475 
476  // --------------------------------------------------------------------------
477  // schedule an update check.
478  // --------------------------------------------------------------------------
479  delayedUpdateCheck: function() {
480  // if the update is already taking place, only update the update queue,
481  // so that the lists that need updating will be processed at the end of
482  // the current queue
483  if (this._updating) {
484  this._causeMoreChecks = true;
485  return;
486  }
487  // if timer has not been created, create it now
488  if (!this._timer)
489  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
490  // we may have triggered this timer already, and not enough time has passed
491  // for it to cause the update to begin, so cancel the timer and set it
492  // again. this has the effect of delaying further the updates as more
493  // medialistlistener notifications are received and until none has been
494  // received for a delay long enough for the timer notification to be issued.
495  // Note that we also check if 'a long time' has passed since we started
496  // delaying the update check, and force it if that's the case, so as to avoid
497  // being indefinitly blocked by something constantly updating properties.
498  var now = new Date().getTime();
499  if (!this._timerInitTime) this._timerInitTime = now;
500  this._timer.cancel();
501  if (this._batchCount == 0 &&
502  now - this._timerInitTime > this._maxInitialDelay) {
503  this.notify(this._timer);
504  } else {
505  // start the timer
506  this._timer.initWithCallback(this,
507  this._updateInitialDelay,
508  Ci.nsITimer.TYPE_ONE_SHOT);
509  }
510  },
511 
512  // --------------------------------------------------------------------------
513  // timer notification
514  // --------------------------------------------------------------------------
515  notify: function(aTimer) {
516  // The delayed update timer has expired... if there are no new batches in
517  // progress, check to see if we need to update the smart playlists
518  if (this._batchCount == 0) {
519  if (aTimer == this._timer) {
520  // reset first delayed update check time
521  this._timerInitTime = null;
522  // properties have been changed some time ago, we need to go through the
523  // list of smart playlists, and schedule an update for the ones whose
524  // content has potentially changed
525  this.checkForUpdates();
526  } else if (aTimer == this._secondaryTimer) {
527  // perform the update for the next smart playlist in the queue
528  this.performUpdates();
529  }
530  } else {
531  // Otherwise, defer the check for a while longer
532  this.delayedUpdateCheck();
533  }
534  },
535 
536  // --------------------------------------------------------------------------
537  // Check to see if we need to add any smart playlist in the update queue
538  // due to a property change that may affect the list's content.
539  // Note that this function may run while the updates are in progress (ie,
540  // a property has changed while the update was already started, and it is
541  // not finished yet). This is fine because all this function does is check
542  // the list of modified properties and add the appropriate lists to the
543  // update queue, so if the update is running, the newly added lists will
544  // run after the ones that were already scheduled.
545  // --------------------------------------------------------------------------
546  checkForUpdates: function() {
547  //this._log("checkForUpdates");
548  // if no properties have been modified, no list need to be added to
549  // the queue (this does not mean that the queue is empty).
550  if (!this.emptyOfProperties(this._updatedProperties)) {
551  // get all smart playlists
552  var lists = this.getSmartPlaylists();
553  // for all smart playlists...
554  for each (var list in lists) {
555  // if the list is already in the update queue, continue with the
556  // next one
557  if (list.guid in this._updateQueue)
558  continue;
559  // fetch the interface we need
560  list.QueryInterface(Ci.sbILocalDatabaseSmartMediaList);
561  // if this list is not auto updating, skip it
562  if (!list.autoUpdate)
563  continue;
564  // if we have a limit with a matching select property, add the list to
565  // the update queue and to the dirty lists table, otherwise, check
566  // individual conditions for a property match
567  if (list.limit != Ci.sbILocalDatabaseSmartMediaList.LIMIT_TYPE_NONE &&
568  ("*" in this._updatedProperties ||
569  list.selectPropertyID in this._updatedProperties)) {
570  this._updateQueue[list.guid] = list;
571  this.addListToDirtyTable(list);
572  } else {
573  // for all smart playlist conditions...
574  for (var c=0; c<list.conditionCount; c++) {
575  // get condition at index c
576  var condition = list.getConditionAt(c);
577  // if the condition property is in the table, or "*" is in the table,
578  // add the list to the update queue and to the dirty lists table
579  if ("*" in this._updatedProperties ||
580  condition.propertyID in this._updatedProperties ||
581  this.isPlaylistConditionMatch(condition.propertyID, condition.leftValue, this._updatedProperties)) {
582  this._updateQueue[list.guid] = list;
583  this.addListToDirtyTable(list);
584  // and continue on with the next list
585  break;
586  }
587  }
588  }
589  }
590  // reset the list of modified properties. If more properties are changed
591  // while we are in the process of updating, we only want those lists that
592  // match those new properties to be added to the queue (and they will only
593  // be added if they are either not in there already, ie, they were not
594  // scheduled for update before, or they have already been updated, in which
595  // case they need to be updated again).
596  this._updatedProperties = {};
597  // empty the dirty properties table, since we're about to populate the
598  // dirty lists table (they're no longer needed, since they only serve to
599  // let us figure out which lists need updating on next startup in the case
600  // where we never got here)
601  this.resetDirtyPropertiesTable();
602  }
603  // if we're not updating yet, check if we need to start doing so
604  if (!this._updating &&
605  !this.emptyOfProperties(this._updateQueue)) {
606  // start updating, this will update the first list, and schedule the
607  // next one.
608  this.performUpdates();
609  }
610  },
611 
612  // --------------------------------------------------------------------------
613  // Test whether a condition uses a rule on a dirty playlist
614  // --------------------------------------------------------------------------
615  isPlaylistConditionMatch: function(prop, value, dirtyprops) {
616  if (prop != "http://songbirdnest.com/dummy/smartmedialists/1.0#playlist")
617  return false;
618  return (value in dirtyprops);
619  },
620 
621  // --------------------------------------------------------------------------
622  // actually performs the update for one list, then schedule the next
623  // --------------------------------------------------------------------------
624  performUpdates: function() {
625  // extract and remove the first list from the queue.
626  // is there a better way to do this with a map ?
627  var remaining = {};
628  var list;
629  for (var v in this._updateQueue) {
630  if (!list)
631  list = this._updateQueue[v];
632  else
633  remaining[v] = this._updateQueue[v];
634  }
635  this._updateQueue = remaining;
636 
637  // this should really not be happening, but test anyway
638  if (!list) {
639  // print a console message since this is not supposed to happen
640  //this._log("list is null in sbSmartMediaListsUpdater.js", true);
641  // go back to idle mode
642  this._updating = false;
643  // make sure the dirty lists table is empty
644  this.resetDirtylistsTable();
645  return;
646  }
647 
648  //this._log("Updating list " +
649  // list.name + " (" +
650  // list.type + ", " +
651  // list.guid + ")");
652 
653  // we are now updating a whole bunch of playlists, one at a time
654  this._updating = true;
655 
656  // get a notification when the playlist is done rebuilding. The rebuild
657  // is actually synchronous for now, but going via a callback means that this
658  // code will not need any change if/when we use asynchronous updates instead
659  list.addSmartMediaListListener(this);
660 
661  // cause the rebuild
662  list.rebuild();
663  },
664 
665  // --------------------------------------------------------------------------
666  // Called when the smart playlist is done rebuilding.
667  // --------------------------------------------------------------------------
668  onRebuild: function(aSmartMediaList) {
669  // remove listener
670  aSmartMediaList.removeSmartMediaListListener(this);
671 
672  // Remove this list from the dirty db table, so we don't rebuild it on
673  // next startup, even if the app is shut down before we finish to update
674  // all the lists in the queue
675  this.removeListFromDirtyTable(aSmartMediaList);
676 
677  // if there are any more lists to update, start the timer again,
678  // with a shorter delay than the initial one
679  if (!this.emptyOfProperties(this._updateQueue)) {
680  if (!this._secondaryTimer)
681  this._secondaryTimer =
682  Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
683 
684  this._secondaryTimer.initWithCallback(this,
685  this._updateSubsequentDelay,
686  Ci.nsITimer.TYPE_ONE_SHOT);
687  } else {
688  // if nothing more to do, then go back to idle mode.
689  this._updating = false;
690  // the dirty lists table should be empty by now, but it doesn't hurt
691  // to make sure
692  this.resetDirtyListsTable();
693  // if we got more changes during the update, check them now
694  if (this._causeMoreChecks) {
695  this._causeMoreChecks = false;
696  this.delayedUpdateCheck();
697  }
698  }
699  this._currentListUpdate = null;
700  },
701 
702  // --------------------------------------------------------------------------
703  // returns true if a map is empty. is there a better way to do this ?
704  // --------------------------------------------------------------------------
705  emptyOfProperties: function(obj) {
706  var hasItems = false;
707  for each (var v in obj) {
708  hasItems = true;
709  break;
710  }
711  return !hasItems;
712  },
713 
714  // --------------------------------------------------------------------------
715  // add one list to the dirty lists db table
716  // --------------------------------------------------------------------------
717  addListToDirtyTable: function(aMediaList) {
718  this._dbQuery.resetQuery();
719  this._dbQuery.addQuery("INSERT OR REPLACE INTO " +
720  this._dirtyListsTable +
721  " VALUES (\"" +
722  aMediaList.guid +
723  "\")");
724  this._dbQuery.execute();
725  },
726 
727  // --------------------------------------------------------------------------
728  // add one property to the dirty properties db table
729  // --------------------------------------------------------------------------
730  addPropertyToDirtyTable: function(aPropertyID) {
731  this._dbQuery.resetQuery();
732  this._dbQuery.addQuery("INSERT OR REPLACE INTO " +
733  this._dirtyPropertiesTable +
734  " VALUES (\"" +
735  aPropertyID +
736  "\")");
737  this._dbQuery.execute();
738  },
739 
740  // --------------------------------------------------------------------------
741  // remove one list from the dirty lists db table
742  // --------------------------------------------------------------------------
743  removeListFromDirtyTable: function(aMediaList) {
744  this._dbQuery.resetQuery();
745  this._dbQuery.addQuery("DELETE FROM " +
746  this._dirtyListsTable +
747  " WHERE listguid = \"" +
748  aMediaList.guid +
749  "\"");
750  this._dbQuery.execute();
751  },
752 
753  // --------------------------------------------------------------------------
754  // remove all rows from the dirty lists db table
755  // --------------------------------------------------------------------------
756  resetDirtyListsTable: function() {
757  this._dbQuery.resetQuery();
758  this._dbQuery.addQuery("DELETE FROM " + this._dirtyListsTable);
759  this._dbQuery.execute();
760  },
761 
762  // --------------------------------------------------------------------------
763  // remove all rows from the dirty properties db table
764  // --------------------------------------------------------------------------
765  resetDirtyPropertiesTable: function() {
766  this._dbQuery.resetQuery();
767  this._dbQuery.addQuery("DELETE FROM " + this._dirtyPropertiesTable);
768  this._dbQuery.execute();
769  },
770 
771  // --------------------------------------------------------------------------
772  // remove all rows from the update video lists db table
773  // --------------------------------------------------------------------------
774  resetUpdateVideoListsTable: function() {
775  this._dbQuery.resetQuery();
776  this._dbQuery.addQuery("DELETE FROM " + this._updateVideoListsTable);
777  this._dbQuery.execute();
778  },
779 
780  // --------------------------------------------------------------------------
781  // QueryInterface
782  // --------------------------------------------------------------------------
784  XPCOMUtils.generateQI([Ci.sbIMediaListListener,
785  Ci.nsITimerCallback,
786  Ci.sbILocalDatabaseSmartMediaListListener])
787 
788 }; // SmartMediaListsUpdater.prototype
789 
790 // ----------------------------------------------------------------------------
791 // ----------------------------------------------------------------------------
792 
793 function postRegister(aCompMgr, aFileSpec, aLocation) {
794  // Get instantiated on startup
795  XPCOMUtils.categoryManager
796  .addCategoryEntry('app-startup',
797  'smartplaylists-updater',
798  'service,@songbirdnest.com/Songbird/SmartMediaListsUpdater;1',
799  true,
800  true);
801 }
802 
803 // module
805  XPCOMUtils.generateNSGetModule([SmartMediaListsUpdater],
806  postRegister);
function Fx prototype initialize
function checkForUpdates()
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
sbDownloadDeviceServicePaneModule prototype shutdown
function SmartMediaListsUpdater()
function SBString(aKey, aDefault, aStringBundle)
Definition: StringUtils.jsm:93
sbDeviceFirmwareAutoCheckForUpdate prototype _timer
TimerLoop prototype notify
DataRemote prototype constructor
const debugLog
return null
Definition: FeedWriter.js:1143
countRef value
Definition: FeedWriter.js:1423
observe topic
Definition: FeedWriter.js:1326
sbDeviceFirmwareAutoCheckForUpdate prototype classID
Javascript wrappers for common library tasks.
function now()
observe data
Definition: FeedWriter.js:1329
function postRegister(aCompMgr, aFileSpec, aLocation)
_getSelectedPageStyle s i
sbDeviceFirmwareAutoCheckForUpdate prototype observe