using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.Library { /// /// Class LuceneSearchEngine /// http://www.codeproject.com/Articles/320219/Lucene-Net-ultra-fast-search-for-MVC-or-WebForms /// public class SearchEngine : ISearchEngine { private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly ILogger _logger; public SearchEngine(ILogManager logManager, ILibraryManager libraryManager, IUserManager userManager) { _libraryManager = libraryManager; _userManager = userManager; _logger = logManager.GetLogger("Lucene"); } public async Task> GetSearchHints(SearchQuery query) { var user = _userManager.GetUserById(new Guid(query.UserId)); var inputItems = user.RootFolder.GetRecursiveChildren(user, null); var results = await GetSearchHints(inputItems, query).ConfigureAwait(false); var searchResultArray = results.ToArray(); results = searchResultArray; var count = searchResultArray.Length; if (query.StartIndex.HasValue) { results = results.Skip(query.StartIndex.Value); } if (query.Limit.HasValue) { results = results.Take(query.Limit.Value); } return new QueryResult { TotalRecordCount = count, Items = results.ToArray() }; } /// /// Gets the search hints. /// /// The input items. /// The query. /// IEnumerable{SearchHintResult}. /// searchTerm private Task> GetSearchHints(IEnumerable inputItems, SearchQuery query) { var searchTerm = query.SearchTerm; if (string.IsNullOrEmpty(searchTerm)) { throw new ArgumentNullException("searchTerm"); } var terms = GetWords(searchTerm); var hints = new List>(); var items = inputItems.Where(i => !(i is MusicArtist)).ToList(); if (query.IncludeMedia) { // Add search hints based on item name hints.AddRange(items.Where(i => !string.IsNullOrEmpty(i.Name)).Select(item => { var index = GetIndex(item.Name, searchTerm, terms); return new Tuple(item, index.Item1, index.Item2); })); } if (query.IncludeArtists) { // Find artists var artists = _libraryManager.GetAllArtists(items) .ToList(); foreach (var item in artists) { var index = GetIndex(item, searchTerm, terms); if (index.Item2 != -1) { try { var artist = _libraryManager.GetArtist(item); hints.Add(new Tuple(artist, index.Item1, index.Item2)); } catch (Exception ex) { _logger.ErrorException("Error getting {0}", ex, item); } } } } if (query.IncludeGenres) { // Find genres, from non-audio items var genres = items.Where(i => !(i is IHasMusicGenres) && !(i is Game)) .SelectMany(i => i.Genres) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var item in genres) { var index = GetIndex(item, searchTerm, terms); if (index.Item2 != -1) { try { var genre = _libraryManager.GetGenre(item); hints.Add(new Tuple(genre, index.Item1, index.Item2)); } catch (Exception ex) { _logger.ErrorException("Error getting {0}", ex, item); } } } // Find music genres var musicGenres = items.Where(i => i is IHasMusicGenres) .SelectMany(i => i.Genres) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var item in musicGenres) { var index = GetIndex(item, searchTerm, terms); if (index.Item2 != -1) { try { var genre = _libraryManager.GetMusicGenre(item); hints.Add(new Tuple(genre, index.Item1, index.Item2)); } catch (Exception ex) { _logger.ErrorException("Error getting {0}", ex, item); } } } // Find music genres var gameGenres = items.OfType() .SelectMany(i => i.Genres) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var item in gameGenres) { var index = GetIndex(item, searchTerm, terms); if (index.Item2 != -1) { try { var genre = _libraryManager.GetGameGenre(item); hints.Add(new Tuple(genre, index.Item1, index.Item2)); } catch (Exception ex) { _logger.ErrorException("Error getting {0}", ex, item); } } } } if (query.IncludeStudios) { // Find studios var studios = items.SelectMany(i => i.Studios) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var item in studios) { var index = GetIndex(item, searchTerm, terms); if (index.Item2 != -1) { try { var studio = _libraryManager.GetStudio(item); hints.Add(new Tuple(studio, index.Item1, index.Item2)); } catch (Exception ex) { _logger.ErrorException("Error getting {0}", ex, item); } } } } if (query.IncludePeople) { // Find persons var persons = items.SelectMany(i => i.People) .Select(i => i.Name) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var item in persons) { var index = GetIndex(item, searchTerm, terms); if (index.Item2 != -1) { try { var person = _libraryManager.GetPerson(item); hints.Add(new Tuple(person, index.Item1, index.Item2)); } catch (Exception ex) { _logger.ErrorException("Error getting {0}", ex, item); } } } } var returnValue = hints.Where(i => i.Item3 >= 0).OrderBy(i => i.Item3).Select(i => new SearchHintInfo { Item = i.Item1, MatchedTerm = i.Item2 }); return Task.FromResult(returnValue); } /// /// Gets the index. /// /// The input. /// The search input. /// The search input. /// System.Int32. private Tuple GetIndex(string input, string searchInput, List searchWords) { if (string.IsNullOrEmpty(input)) { throw new ArgumentNullException("input"); } if (string.Equals(input, searchInput, StringComparison.OrdinalIgnoreCase)) { return new Tuple(searchInput, 0); } var index = input.IndexOf(searchInput, StringComparison.OrdinalIgnoreCase); if (index == 0) { return new Tuple(searchInput, 1); } if (index > 0) { return new Tuple(searchInput, 2); } var items = GetWords(input); for (var i = 0; i < searchWords.Count; i++) { var searchTerm = searchWords[i]; for (var j = 0; j < items.Count; j++) { var item = items[j]; if (string.Equals(item, searchTerm, StringComparison.OrdinalIgnoreCase)) { return new Tuple(searchTerm, 3 + (i + 1) * (j + 1)); } index = item.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase); if (index == 0) { return new Tuple(searchTerm, 4 + (i + 1) * (j + 1)); } if (index > 0) { return new Tuple(searchTerm, 5 + (i + 1) * (j + 1)); } } } return new Tuple(null, -1); } /// /// Gets the words. /// /// The term. /// System.String[][]. private List GetWords(string term) { return term.Split().Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); } } }