sbLibrarySearchSuggester.js
Go to the documentation of this file.
1 
47 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
48 Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
49 
50 const Cc = Components.classes;
51 const Ci = Components.interfaces;
52 const Cr = Components.results;
53 
54 const CONTRACTID = "@mozilla.org/autocomplete/search;1?name=library-distinct-properties";
55 const DESCRIPTION = "Songbird Library Search Suggestions";
56 const CID = Components.ID("{1ed101bc-a11c-4e03-83af-514672bd3a70}");
57 
58 const XPCOM_SHUTDOWN_TOPIC = "xpcom-shutdown";
59 
60 
64 var gDefaultValues = {};
65 gDefaultValues["audio"] = [
66  "Alternative", "Blues/R&B", "Books&Spoken", "Children's Music",
67  "Classical", "Comedy", "Country", "Dance", "Easy Listening", "World",
68  "Electronic", "Folk", "Hip Hop/Rap", "Holiday", "House", "Industrial",
69  "Jazz", "New Age", "Nerdcore", "Podcast", "Pop", "Reggae", "Religious",
70  "Rock", "Science", "Soundtrack", "Techno", "Trance", "Unclassifiable",
71 ];
72 
73 gDefaultValues["video"] = [
74  "Children's", "Comedy", "Drama", "Entertainment", "Healthcare & Fitness",
75  "Travel", "Unclassifiable",
76 ];
77 
84 function AutoCompleteResult(searchString,
85  defaultIndex,
86  errorDescription,
87  results) {
88  this._searchString = searchString;
89  this._defaultIndex = defaultIndex;
90  this._errorDescription = errorDescription;
91  this._results = results;
92 }
93 AutoCompleteResult.prototype = {
98  _searchString: "",
99 
104  _defaultIndex: 0,
105 
110  _errorDescription: "",
111 
116  _results: [],
117 
121  get searchString() {
122  return this._searchString;
123  },
124 
132  get searchResult() {
133  if (this._results.length > 0) {
134  return Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
135  } else {
136  return Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
137  }
138  },
139 
143  get defaultIndex() {
144  return this._defaultIndex;
145  },
146 
150  get errorDescription() {
151  return this._errorDescription;
152  },
153 
157  get matchCount() {
158  return this._results.length;
159  },
160 
166  getValueAt: function(index) {
167  return this._results[index];
168  },
169 
175  getCommentAt: function(index) {
176  return "";
177  },
178 
184  getStyleAt: function(index) {
185  if (!this._results[index])
186  return null; // not a category label, so no special styling
187 
188  if (index == 0)
189  return "suggestfirst"; // category label on first line of results
190 
191  return "suggesthint"; // category label on any other line of results
192  },
193 
199  getImageAt: function(index) {
200  return "";
201  },
202 
207  removeValueAt: function(index, removeFromDatabase) {
208  this._results.splice(index, 1);
209  },
210 
217  QueryInterface: function(iid) {
218  if (!iid.equals(Ci.nsIAutoCompleteResult) &&
219  !iid.equals(Ci.nsISupports))
220  throw Cr.NS_ERROR_NO_INTERFACE;
221  return this;
222  }
223 };
224 
225 
226 
227 
228 
229 
240  var os = Cc["@mozilla.org/observer-service;1"]
241  .getService(Ci.nsIObserverService);
242  os.addObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
243 }
244 
245 LibrarySearchSuggester.prototype = {
247  classID: Components.ID(CID),
249 
255  _listener: null,
257  _lastSearch: null,
258  _timer: null,
259  _distinctValues: null,
260  _cacheParam: null,
261 
262 
269  onSearchResult: function(searchString, results) {
270  if (this._listener) {
271  var result = new AutoCompleteResult(
272  searchString,
273  0,
274  "",
275  results);
276 
277  this._listener.onSearchResult(this, result);
278 
279  // Null out listener to make sure we don't notify it twice, in case our
280  // timer callback still hasn't run.
281  this._listener = null;
282  }
283  },
284 
285 
297  startSearch: function(searchString, searchParam, previousResult, listener) {
298 
299  // If no property was specified, we can't perform a search, abort now
300  if (!searchParam ||
301  searchParam == "") {
302  // notify empty result, probably not needed but hey, why not.
303  this.onSearchResult(searchString, []);
304  return;
305  }
306 
307  // If there's an existing request, stop it
308  this.stopSearch();
309 
310  // remember the listener
311  this._listener = listener;
312 
313  // if we do not yet have the distinct values, or if they
314  // have been invalidated, get them again now.
315  if (!this._distinctValues ||
316  this._cacheParam != searchParam) {
317 
318  // remember current search param
319  this._cacheParam = searchParam;
320 
321  // discard previous results no matter what,
322  // we want to use the new data
323  previousResult = null;
324 
325  // start anew
326  this._distinctValues = {};
327 
328  // get the library manager if needed
329  if (!this._libraryManager) {
330  this._libraryManager =
331  Cc["@songbirdnest.com/Songbird/library/Manager;1"]
332  .getService(Ci.sbILibraryManager);
333  }
334 
335  // parse search parameters
336  var params = searchParam.split(";");
337 
338  var properties = params[0].split("$");
339  this._prop = properties[0];
340  this._type = properties[1] || "audio";
341 
342  var guid = params[1];
343  var additionalValues = params[2];
344  this._conversionUnit = params[3];
345 
346  if (this._type != "video") {
347  // Record distinct values for a library
348  function getDistinctValues(aLibrary, prop, obj) {
349  if (!aLibrary)
350  return;
351  var values = aLibrary.getDistinctValuesForProperty(prop);
352  while (values.hasMore()) {
353  // is there a way to assert a key without doing an assignment ?
354  obj._distinctValues[values.getNext()] = true;
355  }
356  }
357 
358  // If we have a guid in the params, get the distinct values
359  // from a library with that guid, otherwise, get them from
360  // all libraries
361  if (guid && guid.length > 0) {
362  getDistinctValues(this._libraryManager.getLibrary(guid),
363  this._prop, this);
364  } else {
365  var libs = this._libraryManager.getLibraries();
366  while (libs.hasMoreElements()) {
367  getDistinctValues(libs.getNext(), this._prop, this);
368  }
369  }
370  }
371 
372  // If we have additional values, add them to the
373  // distinct values array
374  if (additionalValues && additionalValues.length > 0) {
375  var values = additionalValues.split(",");
376  for each (var value in values) {
377  this._distinctValues[value] = true;
378  }
379  }
380 
381  // Add hardcoded values, if any
382  this._addDefaultDistinctValues();
383 
384  // set this cache to expire in 5s
385 
386  if (!this._timer)
387  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
388 
389  this._timer.cancel();
390  this._timer.initWithCallback(this, 5000, this._timer.TYPE_ONE_SHOT);
391  }
392 
393  // our search is case insensitive
394  var search = searchString.toLowerCase();
395 
396  // matching function
397  function startsWith(aString, aPartial) {
398  return (!aPartial ||
399  aPartial == "" ||
400  aString.toLowerCase().slice(0, aPartial.length) == aPartial);
401  }
402 
403  var results = [];
404 
405  // if this is a narrowing down of the previous search,
406  // use the previousResults array, otherwise,
407  // use the full distinctValues array
408 
409  if (previousResult &&
410  startsWith(search, this._lastSearch)) {
411  for (var i = 0 ; i < previousResult.matchCount ; i++) {
412  var value = previousResult.getValueAt(i);
413  if (startsWith(value, search))
414  results.push(value);
415  }
416  } else {
417 
418  var converter = null;
419  if (this._conversionUnit && this._conversionUnit != "") {
420  var propertyManager =
421  Cc["@songbirdnest.com/Songbird/Properties/PropertyManager;1"]
422  .getService(Ci.sbIPropertyManager);
423  var info = propertyManager.getPropertyInfo(this._prop);
424  converter = info.unitConverter;
425  }
426 
427  for (var value in this._distinctValues) {
428  if (converter) {
429  value = converter.convert(value,
430  converter.nativeUnitId,
431  this._conversionUnit,
432  -1, -1 /* no min/max decimals */);
433  }
434  if (startsWith(value, search))
435  results.push(value);
436  }
437  }
438 
439  // remember the last search string, to see if
440  // we can use previousResult next time.
441  this._lastSearch = search;
442 
443  // Notify the listener that we got results
444  this.onSearchResult(searchString, results);
445  },
446 
447  // one shot timer notification method
448  notify: function(timer) {
449  this._lastSearch = null;
450  this._distinctValues = null;
451  },
452 
457  stopSearch: function() {
458  // Nothing to do since we return our searches immediately.
459  },
460 
464  _addDefaultDistinctValues: function() {
465  var defaults = gDefaultValues[this._type];
466  if (defaults) {
467  for each (var value in defaults) {
468  this._distinctValues[value] = true;
469  }
470  }
471  },
472 
476  observe: function SAC_observe(aSubject, aTopic, aData) {
477  switch (aTopic) {
479  this.stopSearch();
480  this._libraryManager = null;
481  if (this._timer) {
482  this._timer.cancel();
483  this._timer = null;
484  }
485  var os = Cc["@mozilla.org/observer-service;1"]
486  .getService(Ci.nsIObserverService);
487  os.removeObserver(this, XPCOM_SHUTDOWN_TOPIC);
488  break;
489  }
490  },
491 
499  XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch,
500  Ci.nsIObserver])
501 };
502 
503 function NSGetModule(compMgr, fileSpec) {
504  return XPCOMUtils.generateModule([LibrarySearchSuggester]);
505 }
const XPCOM_SHUTDOWN_TOPIC
const DESCRIPTION
sbDeviceFirmwareAutoCheckForUpdate prototype contractID
var converter
sbOSDControlService prototype QueryInterface
sbDeviceFirmwareAutoCheckForUpdate prototype classDescription
function NSGetModule(compMgr, fileSpec)
sbDeviceFirmwareAutoCheckForUpdate prototype _timer
var gDefaultValues
TimerLoop prototype notify
function search(aFolderId, aSearchStr, aExpectedScopeButtonId)
const CONTRACTID
function AutoCompleteResult(searchString, defaultIndex, errorDescription, results)
return null
Definition: FeedWriter.js:1143
var os
countRef value
Definition: FeedWriter.js:1423
sbAutoDownloader prototype _libraryManager
sbDeviceFirmwareAutoCheckForUpdate prototype classID
function LibrarySearchSuggester()
_getSelectedPageStyle s i
_updateTextAndScrollDataForFrame aData
sbDeviceFirmwareAutoCheckForUpdate prototype observe