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 >
Text File  |  2012-06-08  |  15KB  |  506 lines

  1. /**
  2. //
  3. // BEGIN SONGBIRD GPL
  4. // 
  5. // This file is part of the Songbird web player.
  6. //
  7. // Copyright(c) 2005-2008 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. /**
  28.  * \file sbLibrarySearchSuggester.js
  29.  * Provides autocomplete suggestions based on distinct values for a property
  30.  * Originally based on the Mozilla nsSearchSuggestions.js implementation.
  31.  *
  32.  * The format of the searchparam attribute is the following:
  33.  *
  34.  *  property;libraryguid;defaultvalues;unit
  35.  *
  36.  *  - 'property' is a property id, such as http://songbirdnest.com/data/1.0#artistName
  37.  *  - 'libraryguid' is the guid of a library from which to get distinct values,
  38.  *    or no value to get from all libraries
  39.  *  - 'defaultvalues' is a comma separated list of additional default values
  40.  *    which are always matched against this input even if they are not part of
  41.  *    the distinct values set
  42.  *  - 'unit' is the unit into which the distinct values should be converted to
  43.  *    before being matched against the input and inserted in the suggestion
  44.  *    result set.
  45.  */ 
  46.  
  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.  
  61. /**
  62.  * Map of properties to hard-coded default values
  63.  */
  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.  
  78. /**
  79.  * AutoCompleteResult contains the results returned by the Suggest
  80.  * service - it implements nsIAutoCompleteResult and is used by the auto-
  81.  * complete controller to populate the front end.
  82.  * @constructor
  83.  */
  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 = {
  94.   /**
  95.    * The user's query string
  96.    * @private
  97.    */
  98.   _searchString: "",
  99.  
  100.   /**
  101.    * The default item that should be entered if none is selected
  102.    * @private
  103.    */
  104.   _defaultIndex: 0,
  105.  
  106.   /**
  107.    * The reason the search failed
  108.    * @private
  109.    */
  110.   _errorDescription: "",
  111.  
  112.   /**
  113.    * The list of words returned by the Suggest Service
  114.    * @private
  115.    */
  116.   _results: [],
  117.  
  118.   /**
  119.    * @return the user's query string
  120.    */
  121.   get searchString() {
  122.     return this._searchString;
  123.   },
  124.  
  125.   /**
  126.    * @return the result code of this result object, either:
  127.    *         RESULT_IGNORED   (invalid searchString)
  128.    *         RESULT_FAILURE   (failure)
  129.    *         RESULT_NOMATCH   (no matches found)
  130.    *         RESULT_SUCCESS   (matches found)
  131.    */
  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.  
  140.   /**
  141.    * @return the default item that should be entered if none is selected
  142.    */
  143.   get defaultIndex() {
  144.     return this._defaultIndex;
  145.   },
  146.  
  147.   /**
  148.    * @return the reason the search failed
  149.    */
  150.   get errorDescription() {
  151.     return this._errorDescription;
  152.   },
  153.  
  154.   /**
  155.    * @return the number of results
  156.    */
  157.   get matchCount() {
  158.     return this._results.length;
  159.   },
  160.  
  161.   /**
  162.    * Retrieves a result
  163.    * @param  index    the index of the result requested
  164.    * @return          the result at the specified index
  165.    */
  166.   getValueAt: function(index) {
  167.     return this._results[index];
  168.   },
  169.  
  170.   /**
  171.    * Retrieves a comment (metadata instance)
  172.    * @param  index    the index of the comment requested
  173.    * @return          the comment at the specified index
  174.    */
  175.   getCommentAt: function(index) {
  176.     return "";
  177.   },
  178.  
  179.   /**
  180.    * Retrieves a style hint specific to a particular index.
  181.    * @param  index    the index of the style hint requested
  182.    * @return          the style hint at the specified index
  183.    */
  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.  
  194.   /** 
  195.    * Retrieves an image url. 
  196.    * @param  index    the index of the image url requested 
  197.    * @return          the image url at the specified index 
  198.    */ 
  199.   getImageAt: function(index) { 
  200.     return ""; 
  201.   },
  202.      
  203.   /**
  204.    * Removes a result from the resultset
  205.    * @param  index    the index of the result to remove
  206.    */
  207.   removeValueAt: function(index, removeFromDatabase) {
  208.     this._results.splice(index, 1);
  209.   },
  210.  
  211.   /**
  212.    * Part of nsISupports implementation.
  213.    * @param   iid     requested interface identifier
  214.    * @return  this object (XPConnect handles the magic of telling the caller that
  215.    *                       we're the type it requested)
  216.    */
  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.  
  230. /**
  231.  * Implements nsIAutoCompleteSearch to provide suggestions based 
  232.  * on Songbird's state.
  233.  *
  234.  * To access this suggester set autocompletesearch="library-distinct-properties"
  235.  * on an autocomplete textbox.  See the search.xml binding for details.
  236.  *
  237.  * @constructor
  238.  */
  239. function LibrarySearchSuggester() {
  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 = {
  246.   classDescription: DESCRIPTION,
  247.   classID:          Components.ID(CID),
  248.   contractID:       CONTRACTID,
  249.  
  250.   /**
  251.    * The object implementing nsIAutoCompleteObserver that we notify when
  252.    * we have found results
  253.    * @private
  254.    */
  255.   _listener: null,
  256.   _libraryManager: null,
  257.   _lastSearch: null,
  258.   _timer: null,
  259.   _distinctValues: null,
  260.   _cacheParam: null,
  261.  
  262.  
  263.   /**
  264.    * Notifies the front end of new results.
  265.    * @param searchString  the user's query string
  266.    * @param results       an array of results to the search
  267.    * @private
  268.    */
  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.  
  286.   /**
  287.    * Initiates the search result gathering process. Part of
  288.    * nsIAutoCompleteSearch implementation.
  289.    *
  290.    * @param searchString    the user's query string
  291.    * @param searchParam     the search parameter
  292.    * @param previousResult  unused, a client-cached store of the previous
  293.    *                        generated resultset for faster searching.
  294.    * @param listener        object implementing nsIAutoCompleteObserver which
  295.    *                        we notify when results are ready.
  296.    */
  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.  
  453.   /**
  454.    * Ends the search result gathering process. Part of nsIAutoCompleteSearch
  455.    * implementation.
  456.    */
  457.   stopSearch: function() {
  458.     // Nothing to do since we return our searches immediately.
  459.   },
  460.  
  461.   /**
  462.    * Add hardcoded default values for the current property
  463.    */
  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.  
  473.   /**
  474.    * nsIObserver
  475.    */
  476.   observe: function SAC_observe(aSubject, aTopic, aData) {
  477.     switch (aTopic) {
  478.       case XPCOM_SHUTDOWN_TOPIC:
  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.  
  492.   /**
  493.    * Part of nsISupports implementation.
  494.    * @param   iid     requested interface identifier
  495.    * @return  this object (XPConnect handles the magic of telling the caller that
  496.    *                       we're the type it requested)
  497.    */
  498.   QueryInterface:
  499.     XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch,
  500.                            Ci.nsIObserver])
  501. };
  502.  
  503. function NSGetModule(compMgr, fileSpec) {
  504.   return XPCOMUtils.generateModule([LibrarySearchSuggester]);
  505. }
  506.