home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Freelog 112
/
FreelogNo112-NovembreDecembre2012.iso
/
Multimedia
/
Songbird
/
Songbird_2.0.0-2311_windows-i686-msvc8.exe
/
components
/
sbLibrarySearchSuggester.js
< prev
next >
Wrap
Text File
|
2012-06-08
|
15KB
|
506 lines
/**
//
// BEGIN SONGBIRD GPL
//
// This file is part of the Songbird web player.
//
// Copyright(c) 2005-2008 POTI, Inc.
// http://songbirdnest.com
//
// This file may be licensed under the terms of of the
// GNU General Public License Version 2 (the "GPL").
//
// Software distributed under the License is distributed
// on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
// express or implied. See the GPL for the specific language
// governing rights and limitations.
//
// You should have received a copy of the GPL along with this
// program. If not, go to http://www.gnu.org/licenses/gpl.html
// or write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// END SONGBIRD GPL
//
*/
/**
* \file sbLibrarySearchSuggester.js
* Provides autocomplete suggestions based on distinct values for a property
* Originally based on the Mozilla nsSearchSuggestions.js implementation.
*
* The format of the searchparam attribute is the following:
*
* property;libraryguid;defaultvalues;unit
*
* - 'property' is a property id, such as http://songbirdnest.com/data/1.0#artistName
* - 'libraryguid' is the guid of a library from which to get distinct values,
* or no value to get from all libraries
* - 'defaultvalues' is a comma separated list of additional default values
* which are always matched against this input even if they are not part of
* the distinct values set
* - 'unit' is the unit into which the distinct values should be converted to
* before being matched against the input and inserted in the suggestion
* result set.
*/
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://app/jsmodules/sbProperties.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const CONTRACTID = "@mozilla.org/autocomplete/search;1?name=library-distinct-properties";
const DESCRIPTION = "Songbird Library Search Suggestions";
const CID = Components.ID("{1ed101bc-a11c-4e03-83af-514672bd3a70}");
const XPCOM_SHUTDOWN_TOPIC = "xpcom-shutdown";
/**
* Map of properties to hard-coded default values
*/
var gDefaultValues = {};
gDefaultValues["audio"] = [
"Alternative", "Blues/R&B", "Books&Spoken", "Children's Music",
"Classical", "Comedy", "Country", "Dance", "Easy Listening", "World",
"Electronic", "Folk", "Hip Hop/Rap", "Holiday", "House", "Industrial",
"Jazz", "New Age", "Nerdcore", "Podcast", "Pop", "Reggae", "Religious",
"Rock", "Science", "Soundtrack", "Techno", "Trance", "Unclassifiable",
];
gDefaultValues["video"] = [
"Children's", "Comedy", "Drama", "Entertainment", "Healthcare & Fitness",
"Travel", "Unclassifiable",
];
/**
* AutoCompleteResult contains the results returned by the Suggest
* service - it implements nsIAutoCompleteResult and is used by the auto-
* complete controller to populate the front end.
* @constructor
*/
function AutoCompleteResult(searchString,
defaultIndex,
errorDescription,
results) {
this._searchString = searchString;
this._defaultIndex = defaultIndex;
this._errorDescription = errorDescription;
this._results = results;
}
AutoCompleteResult.prototype = {
/**
* The user's query string
* @private
*/
_searchString: "",
/**
* The default item that should be entered if none is selected
* @private
*/
_defaultIndex: 0,
/**
* The reason the search failed
* @private
*/
_errorDescription: "",
/**
* The list of words returned by the Suggest Service
* @private
*/
_results: [],
/**
* @return the user's query string
*/
get searchString() {
return this._searchString;
},
/**
* @return the result code of this result object, either:
* RESULT_IGNORED (invalid searchString)
* RESULT_FAILURE (failure)
* RESULT_NOMATCH (no matches found)
* RESULT_SUCCESS (matches found)
*/
get searchResult() {
if (this._results.length > 0) {
return Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
} else {
return Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
}
},
/**
* @return the default item that should be entered if none is selected
*/
get defaultIndex() {
return this._defaultIndex;
},
/**
* @return the reason the search failed
*/
get errorDescription() {
return this._errorDescription;
},
/**
* @return the number of results
*/
get matchCount() {
return this._results.length;
},
/**
* Retrieves a result
* @param index the index of the result requested
* @return the result at the specified index
*/
getValueAt: function(index) {
return this._results[index];
},
/**
* Retrieves a comment (metadata instance)
* @param index the index of the comment requested
* @return the comment at the specified index
*/
getCommentAt: function(index) {
return "";
},
/**
* Retrieves a style hint specific to a particular index.
* @param index the index of the style hint requested
* @return the style hint at the specified index
*/
getStyleAt: function(index) {
if (!this._results[index])
return null; // not a category label, so no special styling
if (index == 0)
return "suggestfirst"; // category label on first line of results
return "suggesthint"; // category label on any other line of results
},
/**
* Retrieves an image url.
* @param index the index of the image url requested
* @return the image url at the specified index
*/
getImageAt: function(index) {
return "";
},
/**
* Removes a result from the resultset
* @param index the index of the result to remove
*/
removeValueAt: function(index, removeFromDatabase) {
this._results.splice(index, 1);
},
/**
* Part of nsISupports implementation.
* @param iid requested interface identifier
* @return this object (XPConnect handles the magic of telling the caller that
* we're the type it requested)
*/
QueryInterface: function(iid) {
if (!iid.equals(Ci.nsIAutoCompleteResult) &&
!iid.equals(Ci.nsISupports))
throw Cr.NS_ERROR_NO_INTERFACE;
return this;
}
};
/**
* Implements nsIAutoCompleteSearch to provide suggestions based
* on Songbird's state.
*
* To access this suggester set autocompletesearch="library-distinct-properties"
* on an autocomplete textbox. See the search.xml binding for details.
*
* @constructor
*/
function LibrarySearchSuggester() {
var os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.addObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
}
LibrarySearchSuggester.prototype = {
classDescription: DESCRIPTION,
classID: Components.ID(CID),
contractID: CONTRACTID,
/**
* The object implementing nsIAutoCompleteObserver that we notify when
* we have found results
* @private
*/
_listener: null,
_libraryManager: null,
_lastSearch: null,
_timer: null,
_distinctValues: null,
_cacheParam: null,
/**
* Notifies the front end of new results.
* @param searchString the user's query string
* @param results an array of results to the search
* @private
*/
onSearchResult: function(searchString, results) {
if (this._listener) {
var result = new AutoCompleteResult(
searchString,
0,
"",
results);
this._listener.onSearchResult(this, result);
// Null out listener to make sure we don't notify it twice, in case our
// timer callback still hasn't run.
this._listener = null;
}
},
/**
* Initiates the search result gathering process. Part of
* nsIAutoCompleteSearch implementation.
*
* @param searchString the user's query string
* @param searchParam the search parameter
* @param previousResult unused, a client-cached store of the previous
* generated resultset for faster searching.
* @param listener object implementing nsIAutoCompleteObserver which
* we notify when results are ready.
*/
startSearch: function(searchString, searchParam, previousResult, listener) {
// If no property was specified, we can't perform a search, abort now
if (!searchParam ||
searchParam == "") {
// notify empty result, probably not needed but hey, why not.
this.onSearchResult(searchString, []);
return;
}
// If there's an existing request, stop it
this.stopSearch();
// remember the listener
this._listener = listener;
// if we do not yet have the distinct values, or if they
// have been invalidated, get them again now.
if (!this._distinctValues ||
this._cacheParam != searchParam) {
// remember current search param
this._cacheParam = searchParam;
// discard previous results no matter what,
// we want to use the new data
previousResult = null;
// start anew
this._distinctValues = {};
// get the library manager if needed
if (!this._libraryManager) {
this._libraryManager =
Cc["@songbirdnest.com/Songbird/library/Manager;1"]
.getService(Ci.sbILibraryManager);
}
// parse search parameters
var params = searchParam.split(";");
var properties = params[0].split("$");
this._prop = properties[0];
this._type = properties[1] || "audio";
var guid = params[1];
var additionalValues = params[2];
this._conversionUnit = params[3];
if (this._type != "video") {
// Record distinct values for a library
function getDistinctValues(aLibrary, prop, obj) {
if (!aLibrary)
return;
var values = aLibrary.getDistinctValuesForProperty(prop);
while (values.hasMore()) {
// is there a way to assert a key without doing an assignment ?
obj._distinctValues[values.getNext()] = true;
}
}
// If we have a guid in the params, get the distinct values
// from a library with that guid, otherwise, get them from
// all libraries
if (guid && guid.length > 0) {
getDistinctValues(this._libraryManager.getLibrary(guid),
this._prop, this);
} else {
var libs = this._libraryManager.getLibraries();
while (libs.hasMoreElements()) {
getDistinctValues(libs.getNext(), this._prop, this);
}
}
}
// If we have additional values, add them to the
// distinct values array
if (additionalValues && additionalValues.length > 0) {
var values = additionalValues.split(",");
for each (var value in values) {
this._distinctValues[value] = true;
}
}
// Add hardcoded values, if any
this._addDefaultDistinctValues();
// set this cache to expire in 5s
if (!this._timer)
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._timer.cancel();
this._timer.initWithCallback(this, 5000, this._timer.TYPE_ONE_SHOT);
}
// our search is case insensitive
var search = searchString.toLowerCase();
// matching function
function startsWith(aString, aPartial) {
return (!aPartial ||
aPartial == "" ||
aString.toLowerCase().slice(0, aPartial.length) == aPartial);
}
var results = [];
// if this is a narrowing down of the previous search,
// use the previousResults array, otherwise,
// use the full distinctValues array
if (previousResult &&
startsWith(search, this._lastSearch)) {
for (var i = 0 ; i < previousResult.matchCount ; i++) {
var value = previousResult.getValueAt(i);
if (startsWith(value, search))
results.push(value);
}
} else {
var converter = null;
if (this._conversionUnit && this._conversionUnit != "") {
var propertyManager =
Cc["@songbirdnest.com/Songbird/Properties/PropertyManager;1"]
.getService(Ci.sbIPropertyManager);
var info = propertyManager.getPropertyInfo(this._prop);
converter = info.unitConverter;
}
for (var value in this._distinctValues) {
if (converter) {
value = converter.convert(value,
converter.nativeUnitId,
this._conversionUnit,
-1, -1 /* no min/max decimals */);
}
if (startsWith(value, search))
results.push(value);
}
}
// remember the last search string, to see if
// we can use previousResult next time.
this._lastSearch = search;
// Notify the listener that we got results
this.onSearchResult(searchString, results);
},
// one shot timer notification method
notify: function(timer) {
this._lastSearch = null;
this._distinctValues = null;
},
/**
* Ends the search result gathering process. Part of nsIAutoCompleteSearch
* implementation.
*/
stopSearch: function() {
// Nothing to do since we return our searches immediately.
},
/**
* Add hardcoded default values for the current property
*/
_addDefaultDistinctValues: function() {
var defaults = gDefaultValues[this._type];
if (defaults) {
for each (var value in defaults) {
this._distinctValues[value] = true;
}
}
},
/**
* nsIObserver
*/
observe: function SAC_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case XPCOM_SHUTDOWN_TOPIC:
this.stopSearch();
this._libraryManager = null;
if (this._timer) {
this._timer.cancel();
this._timer = null;
}
var os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.removeObserver(this, XPCOM_SHUTDOWN_TOPIC);
break;
}
},
/**
* Part of nsISupports implementation.
* @param iid requested interface identifier
* @return this object (XPConnect handles the magic of telling the caller that
* we're the type it requested)
*/
QueryInterface:
XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch,
Ci.nsIObserver])
};
function NSGetModule(compMgr, fileSpec) {
return XPCOMUtils.generateModule([LibrarySearchSuggester]);
}