LuceneSearchEngine.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. using MediaBrowser.Controller;
  2. using MediaBrowser.Controller.Entities;
  3. using MediaBrowser.Controller.Entities.Audio;
  4. using MediaBrowser.Controller.Library;
  5. using MediaBrowser.Model.Logging;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Linq;
  9. using System.Threading.Tasks;
  10. namespace MediaBrowser.Server.Implementations.Library
  11. {
  12. /// <summary>
  13. /// Class LuceneSearchEngine
  14. /// http://www.codeproject.com/Articles/320219/Lucene-Net-ultra-fast-search-for-MVC-or-WebForms
  15. /// </summary>
  16. public class LuceneSearchEngine : ILibrarySearchEngine, IDisposable
  17. {
  18. private readonly ILibraryManager _libraryManager;
  19. private readonly ILogger _logger;
  20. public LuceneSearchEngine(IServerApplicationPaths serverPaths, ILogManager logManager, ILibraryManager libraryManager)
  21. {
  22. _libraryManager = libraryManager;
  23. _logger = logManager.GetLogger("Lucene");
  24. }
  25. /// <summary>
  26. /// Searches items and returns them in order of relevance.
  27. /// </summary>
  28. /// <param name="items">The items.</param>
  29. /// <param name="searchTerm">The search term.</param>
  30. /// <returns>IEnumerable{BaseItem}.</returns>
  31. /// <exception cref="System.ArgumentNullException">searchTerm</exception>
  32. public IEnumerable<BaseItem> Search(IEnumerable<BaseItem> items, string searchTerm)
  33. {
  34. return items;
  35. }
  36. public void Dispose()
  37. {
  38. }
  39. /// <summary>
  40. /// Gets the search hints.
  41. /// </summary>
  42. /// <param name="inputItems">The input items.</param>
  43. /// <param name="searchTerm">The search term.</param>
  44. /// <returns>IEnumerable{SearchHintResult}.</returns>
  45. /// <exception cref="System.ArgumentNullException">searchTerm</exception>
  46. public Task<IEnumerable<SearchHintInfo>> GetSearchHints(IEnumerable<BaseItem> inputItems, string searchTerm)
  47. {
  48. if (string.IsNullOrEmpty(searchTerm))
  49. {
  50. throw new ArgumentNullException("searchTerm");
  51. }
  52. var terms = GetWords(searchTerm);
  53. var hints = new List<Tuple<BaseItem, string, int>>();
  54. var items = inputItems.Where(i => !(i is MusicArtist)).ToList();
  55. // Add search hints based on item name
  56. hints.AddRange(items.Where(i => !string.IsNullOrEmpty(i.Name)).Select(item =>
  57. {
  58. var index = GetIndex(item.Name, searchTerm, terms);
  59. return new Tuple<BaseItem, string, int>(item, index.Item1, index.Item2);
  60. }));
  61. // Find artists
  62. var artists = _libraryManager.GetAllArtists(items)
  63. .ToList();
  64. foreach (var item in artists)
  65. {
  66. var index = GetIndex(item, searchTerm, terms);
  67. if (index.Item2 != -1)
  68. {
  69. try
  70. {
  71. var artist = _libraryManager.GetArtist(item);
  72. hints.Add(new Tuple<BaseItem, string, int>(artist, index.Item1, index.Item2));
  73. }
  74. catch (Exception ex)
  75. {
  76. _logger.ErrorException("Error getting {0}", ex, item);
  77. }
  78. }
  79. }
  80. // Find genres, from non-audio items
  81. var genres = items.Where(i => !(i is IHasMusicGenres) && !(i is Game))
  82. .SelectMany(i => i.Genres)
  83. .Where(i => !string.IsNullOrEmpty(i))
  84. .Distinct(StringComparer.OrdinalIgnoreCase)
  85. .ToList();
  86. foreach (var item in genres)
  87. {
  88. var index = GetIndex(item, searchTerm, terms);
  89. if (index.Item2 != -1)
  90. {
  91. try
  92. {
  93. var genre = _libraryManager.GetGenre(item);
  94. hints.Add(new Tuple<BaseItem, string, int>(genre, index.Item1, index.Item2));
  95. }
  96. catch (Exception ex)
  97. {
  98. _logger.ErrorException("Error getting {0}", ex, item);
  99. }
  100. }
  101. }
  102. // Find music genres
  103. var musicGenres = items.Where(i => i is IHasMusicGenres)
  104. .SelectMany(i => i.Genres)
  105. .Where(i => !string.IsNullOrEmpty(i))
  106. .Distinct(StringComparer.OrdinalIgnoreCase)
  107. .ToList();
  108. foreach (var item in musicGenres)
  109. {
  110. var index = GetIndex(item, searchTerm, terms);
  111. if (index.Item2 != -1)
  112. {
  113. try
  114. {
  115. var genre = _libraryManager.GetMusicGenre(item);
  116. hints.Add(new Tuple<BaseItem, string, int>(genre, index.Item1, index.Item2));
  117. }
  118. catch (Exception ex)
  119. {
  120. _logger.ErrorException("Error getting {0}", ex, item);
  121. }
  122. }
  123. }
  124. // Find music genres
  125. var gameGenres = items.OfType<Game>()
  126. .SelectMany(i => i.Genres)
  127. .Where(i => !string.IsNullOrEmpty(i))
  128. .Distinct(StringComparer.OrdinalIgnoreCase)
  129. .ToList();
  130. foreach (var item in gameGenres)
  131. {
  132. var index = GetIndex(item, searchTerm, terms);
  133. if (index.Item2 != -1)
  134. {
  135. try
  136. {
  137. var genre = _libraryManager.GetGameGenre(item);
  138. hints.Add(new Tuple<BaseItem, string, int>(genre, index.Item1, index.Item2));
  139. }
  140. catch (Exception ex)
  141. {
  142. _logger.ErrorException("Error getting {0}", ex, item);
  143. }
  144. }
  145. }
  146. // Find studios
  147. var studios = items.SelectMany(i => i.Studios)
  148. .Where(i => !string.IsNullOrEmpty(i))
  149. .Distinct(StringComparer.OrdinalIgnoreCase)
  150. .ToList();
  151. foreach (var item in studios)
  152. {
  153. var index = GetIndex(item, searchTerm, terms);
  154. if (index.Item2 != -1)
  155. {
  156. try
  157. {
  158. var studio = _libraryManager.GetStudio(item);
  159. hints.Add(new Tuple<BaseItem, string, int>(studio, index.Item1, index.Item2));
  160. }
  161. catch (Exception ex)
  162. {
  163. _logger.ErrorException("Error getting {0}", ex, item);
  164. }
  165. }
  166. }
  167. // Find persons
  168. var persons = items.SelectMany(i => i.People)
  169. .Select(i => i.Name)
  170. .Where(i => !string.IsNullOrEmpty(i))
  171. .Distinct(StringComparer.OrdinalIgnoreCase)
  172. .ToList();
  173. foreach (var item in persons)
  174. {
  175. var index = GetIndex(item, searchTerm, terms);
  176. if (index.Item2 != -1)
  177. {
  178. try
  179. {
  180. var person = _libraryManager.GetPerson(item);
  181. hints.Add(new Tuple<BaseItem, string, int>(person, index.Item1, index.Item2));
  182. }
  183. catch (Exception ex)
  184. {
  185. _logger.ErrorException("Error getting {0}", ex, item);
  186. }
  187. }
  188. }
  189. var returnValue = hints.Where(i => i.Item3 >= 0).OrderBy(i => i.Item3).Select(i => new SearchHintInfo
  190. {
  191. Item = i.Item1,
  192. MatchedTerm = i.Item2
  193. });
  194. return Task.FromResult(returnValue);
  195. }
  196. /// <summary>
  197. /// Gets the index.
  198. /// </summary>
  199. /// <param name="input">The input.</param>
  200. /// <param name="searchInput">The search input.</param>
  201. /// <param name="searchWords">The search input.</param>
  202. /// <returns>System.Int32.</returns>
  203. private Tuple<string, int> GetIndex(string input, string searchInput, List<string> searchWords)
  204. {
  205. if (string.IsNullOrEmpty(input))
  206. {
  207. throw new ArgumentNullException("input");
  208. }
  209. if (string.Equals(input, searchInput, StringComparison.OrdinalIgnoreCase))
  210. {
  211. return new Tuple<string, int>(searchInput, 0);
  212. }
  213. var index = input.IndexOf(searchInput, StringComparison.OrdinalIgnoreCase);
  214. if (index == 0)
  215. {
  216. return new Tuple<string, int>(searchInput, 1);
  217. }
  218. if (index > 0)
  219. {
  220. return new Tuple<string, int>(searchInput, 2);
  221. }
  222. var items = GetWords(input);
  223. for (var i = 0; i < searchWords.Count; i++)
  224. {
  225. var searchTerm = searchWords[i];
  226. for (var j = 0; j < items.Count; j++)
  227. {
  228. var item = items[j];
  229. if (string.Equals(item, searchTerm, StringComparison.OrdinalIgnoreCase))
  230. {
  231. return new Tuple<string, int>(searchTerm, 3 + (i + 1) * (j + 1));
  232. }
  233. index = item.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase);
  234. if (index == 0)
  235. {
  236. return new Tuple<string, int>(searchTerm, 4 + (i + 1) * (j + 1));
  237. }
  238. if (index > 0)
  239. {
  240. return new Tuple<string, int>(searchTerm, 5 + (i + 1) * (j + 1));
  241. }
  242. }
  243. }
  244. return new Tuple<string, int>(null, -1);
  245. }
  246. /// <summary>
  247. /// Gets the words.
  248. /// </summary>
  249. /// <param name="term">The term.</param>
  250. /// <returns>System.String[][].</returns>
  251. private List<string> GetWords(string term)
  252. {
  253. return term.Split().Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
  254. }
  255. }
  256. }