search hints progress

This commit is contained in:
Luke Pulverenti 2013-04-27 09:05:33 -04:00
parent 3eaf25132c
commit 0bfb755a38
14 changed files with 334 additions and 127 deletions

View File

@ -20,7 +20,7 @@ namespace MediaBrowser.Api
/// </summary> /// </summary>
[Route("/Search/Hints", "GET")] [Route("/Search/Hints", "GET")]
[Api(Description = "Gets search hints based on a search term")] [Api(Description = "Gets search hints based on a search term")]
public class GetSearchHints : IReturn<List<SearchHintResult>> public class GetSearchHints : IReturn<SearchHintResult>
{ {
/// <summary> /// <summary>
/// Skips over a given number of items within the results. Use for paging. /// Skips over a given number of items within the results. Use for paging.
@ -96,7 +96,7 @@ namespace MediaBrowser.Api
/// </summary> /// </summary>
/// <param name="request">The request.</param> /// <param name="request">The request.</param>
/// <returns>Task{IEnumerable{SearchHintResult}}.</returns> /// <returns>Task{IEnumerable{SearchHintResult}}.</returns>
private async Task<IEnumerable<SearchHintResult>> GetSearchHintsAsync(GetSearchHints request) private async Task<SearchHintResult> GetSearchHintsAsync(GetSearchHints request)
{ {
IEnumerable<BaseItem> inputItems; IEnumerable<BaseItem> inputItems;
@ -113,34 +113,48 @@ namespace MediaBrowser.Api
var results = await _searchEngine.GetSearchHints(inputItems, request.SearchTerm).ConfigureAwait(false); var results = await _searchEngine.GetSearchHints(inputItems, request.SearchTerm).ConfigureAwait(false);
var searchResultArray = results.ToArray();
IEnumerable<SearchHintInfo> returnResults = searchResultArray;
if (request.StartIndex.HasValue) if (request.StartIndex.HasValue)
{ {
results = results.Skip(request.StartIndex.Value); returnResults = returnResults.Skip(request.StartIndex.Value);
} }
if (request.Limit.HasValue) if (request.Limit.HasValue)
{ {
results = results.Take(request.Limit.Value); returnResults = returnResults.Take(request.Limit.Value);
} }
return results.Select(GetSearchHintResult); return new SearchHintResult
{
TotalRecordCount = searchResultArray.Length,
SearchHints = returnResults.Select(GetSearchHintResult).ToArray()
};
} }
/// <summary> /// <summary>
/// Gets the search hint result. /// Gets the search hint result.
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="hintInfo">The hint info.</param>
/// <returns>SearchHintResult.</returns> /// <returns>SearchHintResult.</returns>
private SearchHintResult GetSearchHintResult(BaseItem item) private SearchHint GetSearchHintResult(SearchHintInfo hintInfo)
{ {
var result = new SearchHintResult var item = hintInfo.Item;
var result = new SearchHint
{ {
Name = item.Name, Name = item.Name,
IndexNumber = item.IndexNumber, IndexNumber = item.IndexNumber,
ParentIndexNumber = item.ParentIndexNumber, ParentIndexNumber = item.ParentIndexNumber,
ItemId = DtoBuilder.GetClientItemId(item), ItemId = DtoBuilder.GetClientItemId(item),
Type = item.GetType().Name, Type = item.GetType().Name,
MediaType = item.MediaType MediaType = item.MediaType,
MatchedTerm = hintInfo.MatchedTerm,
DisplayMediaType = item.DisplayMediaType,
RunTimeTicks = item.RunTimeTicks
}; };
if (item.HasImage(ImageType.Primary)) if (item.HasImage(ImageType.Primary))
@ -160,14 +174,25 @@ namespace MediaBrowser.Api
if (season != null) if (season != null)
{ {
result.Series = season.Series.Name; result.Series = season.Series.Name;
result.EpisodeCount = season.RecursiveChildren.OfType<Episode>().Count();
}
var series = item as Series;
if (series != null)
{
result.EpisodeCount = series.RecursiveChildren.OfType<Episode>().Count();
} }
var album = item as MusicAlbum; var album = item as MusicAlbum;
if (album != null) if (album != null)
{ {
var songs = album.Children.OfType<Audio>().ToList(); var songs = album.RecursiveChildren.OfType<Audio>().ToList();
result.SongCount = songs.Count;
result.Artists = songs result.Artists = songs
.Select(i => i.Artist) .Select(i => i.Artist)
.Where(i => !string.IsNullOrEmpty(i)) .Where(i => !string.IsNullOrEmpty(i))

View File

@ -431,7 +431,7 @@ namespace MediaBrowser.Controller.Dto
if (album != null) if (album != null)
{ {
var songs = album.Children.OfType<Audio>().ToList(); var songs = album.RecursiveChildren.OfType<Audio>().ToList();
dto.AlbumArtist = songs.Select(i => i.AlbumArtist).FirstOrDefault(i => !string.IsNullOrEmpty(i)); dto.AlbumArtist = songs.Select(i => i.AlbumArtist).FirstOrDefault(i => !string.IsNullOrEmpty(i));

View File

@ -96,5 +96,17 @@ namespace MediaBrowser.Controller.Entities.Audio
{ {
return string.Equals(Artist, name, StringComparison.OrdinalIgnoreCase) || string.Equals(AlbumArtist, name, StringComparison.OrdinalIgnoreCase); return string.Equals(Artist, name, StringComparison.OrdinalIgnoreCase) || string.Equals(AlbumArtist, name, StringComparison.OrdinalIgnoreCase);
} }
public override string DisplayMediaType
{
get
{
return "Song";
}
set
{
base.DisplayMediaType = value;
}
}
} }
} }

View File

@ -147,7 +147,19 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <returns><c>true</c> if the specified artist has artist; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the specified artist has artist; otherwise, <c>false</c>.</returns>
public bool HasArtist(string artist) public bool HasArtist(string artist)
{ {
return Children.OfType<Audio>().Any(i => i.HasArtist(artist)); return RecursiveChildren.OfType<Audio>().Any(i => i.HasArtist(artist));
}
public override string DisplayMediaType
{
get
{
return "Album";
}
set
{
base.DisplayMediaType = value;
}
} }
} }
} }

View File

@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Library
/// </summary> /// </summary>
/// <param name="inputItems">The input items.</param> /// <param name="inputItems">The input items.</param>
/// <param name="searchTerm">The search term.</param> /// <param name="searchTerm">The search term.</param>
/// <returns>Task{IEnumerable{BaseItem}}.</returns> /// <returns>Task{IEnumerable{SearchHintInfo}}.</returns>
Task<IEnumerable<BaseItem>> GetSearchHints(IEnumerable<BaseItem> inputItems, string searchTerm); Task<IEnumerable<SearchHintInfo>> GetSearchHints(IEnumerable<BaseItem> inputItems, string searchTerm);
} }
} }

View File

@ -0,0 +1,22 @@
using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Library
{
/// <summary>
/// Class SearchHintInfo
/// </summary>
public class SearchHintInfo
{
/// <summary>
/// Gets or sets the item.
/// </summary>
/// <value>The item.</value>
public BaseItem Item { get; set; }
/// <summary>
/// Gets or sets the matched term.
/// </summary>
/// <value>The matched term.</value>
public string MatchedTerm { get; set; }
}
}

View File

@ -112,6 +112,7 @@
<Compile Include="IServerApplicationPaths.cs" /> <Compile Include="IServerApplicationPaths.cs" />
<Compile Include="Library\ChildrenChangedEventArgs.cs" /> <Compile Include="Library\ChildrenChangedEventArgs.cs" />
<Compile Include="Dto\DtoBuilder.cs" /> <Compile Include="Dto\DtoBuilder.cs" />
<Compile Include="Library\SearchHintInfo.cs" />
<Compile Include="Providers\IProviderManager.cs" /> <Compile Include="Providers\IProviderManager.cs" />
<Compile Include="Providers\MediaInfo\MediaEncoderHelpers.cs" /> <Compile Include="Providers\MediaInfo\MediaEncoderHelpers.cs" />
<Compile Include="Providers\MetadataProviderPriority.cs" /> <Compile Include="Providers\MetadataProviderPriority.cs" />

View File

@ -167,7 +167,11 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
if (!string.IsNullOrEmpty(val)) if (!string.IsNullOrEmpty(val))
{ {
audio.AddStudios(val.Split(new[] { '/', '|' }, StringSplitOptions.RemoveEmptyEntries)); var studios =
val.Split(new[] {'/', '|'}, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.Equals(i, audio.Artist, StringComparison.OrdinalIgnoreCase) && !string.Equals(i, audio.AlbumArtist, StringComparison.OrdinalIgnoreCase));
audio.AddStudios(studios);
} }
} }

View File

@ -65,7 +65,7 @@ namespace MediaBrowser.Controller.Providers.Music
var folder = (Folder)item; var folder = (Folder)item;
// Get each song, distinct by the combination of AlbumArtist and Album // Get each song, distinct by the combination of AlbumArtist and Album
var songs = folder.Children.OfType<Audio>().DistinctBy(i => (i.AlbumArtist ?? string.Empty) + (i.Album ?? string.Empty), StringComparer.OrdinalIgnoreCase).ToList(); var songs = folder.RecursiveChildren.OfType<Audio>().DistinctBy(i => (i.AlbumArtist ?? string.Empty) + (i.Album ?? string.Empty), StringComparer.OrdinalIgnoreCase).ToList();
foreach (var song in songs.Where(song => !string.IsNullOrEmpty(song.Album) && !string.IsNullOrEmpty(song.AlbumArtist))) foreach (var song in songs.Where(song => !string.IsNullOrEmpty(song.Album) && !string.IsNullOrEmpty(song.AlbumArtist)))
{ {

View File

@ -86,6 +86,7 @@
<Compile Include="Net\NetworkShareType.cs" /> <Compile Include="Net\NetworkShareType.cs" />
<Compile Include="Querying\PersonsQuery.cs" /> <Compile Include="Querying\PersonsQuery.cs" />
<Compile Include="Querying\ThemeSongsResult.cs" /> <Compile Include="Querying\ThemeSongsResult.cs" />
<Compile Include="Search\SearchHint.cs" />
<Compile Include="Search\SearchHintResult.cs" /> <Compile Include="Search\SearchHintResult.cs" />
<Compile Include="Serialization\IJsonSerializer.cs" /> <Compile Include="Serialization\IJsonSerializer.cs" />
<Compile Include="Serialization\IXmlSerializer.cs" /> <Compile Include="Serialization\IXmlSerializer.cs" />

View File

@ -0,0 +1,106 @@
using System;
namespace MediaBrowser.Model.Search
{
/// <summary>
/// Class SearchHintResult
/// </summary>
public class SearchHint
{
/// <summary>
/// Gets or sets the item id.
/// </summary>
/// <value>The item id.</value>
public string ItemId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the matched term.
/// </summary>
/// <value>The matched term.</value>
public string MatchedTerm { get; set; }
/// <summary>
/// Gets or sets the index number.
/// </summary>
/// <value>The index number.</value>
public int? IndexNumber { get; set; }
/// <summary>
/// Gets or sets the parent index number.
/// </summary>
/// <value>The parent index number.</value>
public int? ParentIndexNumber { get; set; }
/// <summary>
/// Gets or sets the image tag.
/// </summary>
/// <value>The image tag.</value>
public Guid? PrimaryImageTag { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public string Type { get; set; }
/// <summary>
/// Gets or sets the run time ticks.
/// </summary>
/// <value>The run time ticks.</value>
public long? RunTimeTicks { get; set; }
/// <summary>
/// Gets or sets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
public string MediaType { get; set; }
/// <summary>
/// Gets or sets the display type of the media.
/// </summary>
/// <value>The display type of the media.</value>
public string DisplayMediaType { get; set; }
/// <summary>
/// Gets or sets the series.
/// </summary>
/// <value>The series.</value>
public string Series { get; set; }
/// <summary>
/// Gets or sets the album.
/// </summary>
/// <value>The album.</value>
public string Album { get; set; }
/// <summary>
/// Gets or sets the album artist.
/// </summary>
/// <value>The album artist.</value>
public string AlbumArtist { get; set; }
/// <summary>
/// Gets or sets the artists.
/// </summary>
/// <value>The artists.</value>
public string[] Artists { get; set; }
/// <summary>
/// Gets or sets the song count.
/// </summary>
/// <value>The song count.</value>
public int? SongCount { get; set; }
/// <summary>
/// Gets or sets the episode count.
/// </summary>
/// <value>The episode count.</value>
public int? EpisodeCount { get; set; }
}
}

View File

@ -1,5 +1,4 @@
using System; 
namespace MediaBrowser.Model.Search namespace MediaBrowser.Model.Search
{ {
/// <summary> /// <summary>
@ -8,69 +7,15 @@ namespace MediaBrowser.Model.Search
public class SearchHintResult public class SearchHintResult
{ {
/// <summary> /// <summary>
/// Gets or sets the item id. /// Gets or sets the search hints.
/// </summary> /// </summary>
/// <value>The item id.</value> /// <value>The search hints.</value>
public string ItemId { get; set; } public SearchHint[] SearchHints { get; set; }
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the total record count.
/// </summary> /// </summary>
/// <value>The name.</value> /// <value>The total record count.</value>
public string Name { get; set; } public int TotalRecordCount { get; set; }
/// <summary>
/// Gets or sets the index number.
/// </summary>
/// <value>The index number.</value>
public int? IndexNumber { get; set; }
/// <summary>
/// Gets or sets the parent index number.
/// </summary>
/// <value>The parent index number.</value>
public int? ParentIndexNumber { get; set; }
/// <summary>
/// Gets or sets the image tag.
/// </summary>
/// <value>The image tag.</value>
public Guid? PrimaryImageTag { get; set; }
/// <summary>
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
public string Type { get; set; }
/// <summary>
/// Gets or sets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
public string MediaType { get; set; }
/// <summary>
/// Gets or sets the series.
/// </summary>
/// <value>The series.</value>
public string Series { get; set; }
/// <summary>
/// Gets or sets the album.
/// </summary>
/// <value>The album.</value>
public string Album { get; set; }
/// <summary>
/// Gets or sets the album artist.
/// </summary>
/// <value>The album artist.</value>
public string AlbumArtist { get; set; }
/// <summary>
/// Gets or sets the artists.
/// </summary>
/// <value>The artists.</value>
public string[] Artists { get; set; }
} }
} }

View File

@ -97,24 +97,26 @@ namespace MediaBrowser.Server.Implementations.Library
/// <param name="searchTerm">The search term.</param> /// <param name="searchTerm">The search term.</param>
/// <returns>IEnumerable{SearchHintResult}.</returns> /// <returns>IEnumerable{SearchHintResult}.</returns>
/// <exception cref="System.ArgumentNullException">searchTerm</exception> /// <exception cref="System.ArgumentNullException">searchTerm</exception>
public async Task<IEnumerable<BaseItem>> GetSearchHints(IEnumerable<BaseItem> inputItems, string searchTerm) public async Task<IEnumerable<SearchHintInfo>> GetSearchHints(IEnumerable<BaseItem> inputItems, string searchTerm)
{ {
if (string.IsNullOrEmpty(searchTerm)) if (string.IsNullOrEmpty(searchTerm))
{ {
throw new ArgumentNullException("searchTerm"); throw new ArgumentNullException("searchTerm");
} }
var hints = new List<Tuple<BaseItem, int>>(); var terms = GetWords(searchTerm);
var hints = new List<Tuple<BaseItem, string, int>>();
var items = inputItems.Where(i => !(i is MusicArtist)).ToList(); var items = inputItems.Where(i => !(i is MusicArtist)).ToList();
foreach (var item in items) foreach (var item in items)
{ {
var index = IndexOf(item.Name, searchTerm); var index = GetIndex(item.Name, searchTerm, terms);
if (index != -1) if (index.Item2 != -1)
{ {
hints.Add(new Tuple<BaseItem, int>(item, index)); hints.Add(new Tuple<BaseItem, string, int>(item, index.Item1, index.Item2));
} }
} }
@ -127,16 +129,23 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var item in artists) foreach (var item in artists)
{ {
var index = IndexOf(item, searchTerm); var index = GetIndex(item, searchTerm, terms);
if (index != -1) if (index.Item2 != -1)
{ {
var artist = await _libraryManager.GetArtist(item).ConfigureAwait(false); try
{
var artist = await _libraryManager.GetArtist(item).ConfigureAwait(false);
hints.Add(new Tuple<BaseItem, int>(artist, index)); hints.Add(new Tuple<BaseItem, string, int>(artist, index.Item1, index.Item2));
}
catch (Exception ex)
{
_logger.ErrorException("Error getting {0}", ex, item);
}
} }
} }
// Find genres // Find genres
var genres = items.SelectMany(i => i.Genres) var genres = items.SelectMany(i => i.Genres)
.Where(i => !string.IsNullOrEmpty(i)) .Where(i => !string.IsNullOrEmpty(i))
@ -145,13 +154,20 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var item in genres) foreach (var item in genres)
{ {
var index = IndexOf(item, searchTerm); var index = GetIndex(item, searchTerm, terms);
if (index != -1) if (index.Item2 != -1)
{ {
var genre = await _libraryManager.GetGenre(item).ConfigureAwait(false); try
{
var genre = await _libraryManager.GetGenre(item).ConfigureAwait(false);
hints.Add(new Tuple<BaseItem, int>(genre, index)); hints.Add(new Tuple<BaseItem, string, int>(genre, index.Item1, index.Item2));
}
catch (Exception ex)
{
_logger.ErrorException("Error getting {0}", ex, item);
}
} }
} }
@ -163,13 +179,20 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var item in studios) foreach (var item in studios)
{ {
var index = IndexOf(item, searchTerm); var index = GetIndex(item, searchTerm, terms);
if (index != -1) if (index.Item2 != -1)
{ {
var studio = await _libraryManager.GetStudio(item).ConfigureAwait(false); try
{
var studio = await _libraryManager.GetStudio(item).ConfigureAwait(false);
hints.Add(new Tuple<BaseItem, int>(studio, index)); hints.Add(new Tuple<BaseItem, string, int>(studio, index.Item1, index.Item2));
}
catch (Exception ex)
{
_logger.ErrorException("Error getting {0}", ex, item);
}
} }
} }
@ -182,17 +205,83 @@ namespace MediaBrowser.Server.Implementations.Library
foreach (var item in persons) foreach (var item in persons)
{ {
var index = IndexOf(item, searchTerm); var index = GetIndex(item, searchTerm, terms);
if (index != -1) if (index.Item2 != -1)
{ {
var person = await _libraryManager.GetPerson(item).ConfigureAwait(false); try
{
var person = await _libraryManager.GetPerson(item).ConfigureAwait(false);
hints.Add(new Tuple<BaseItem, int>(person, index)); hints.Add(new Tuple<BaseItem, string, int>(person, index.Item1, index.Item2));
}
catch (Exception ex)
{
_logger.ErrorException("Error getting {0}", ex, item);
}
} }
} }
return hints.OrderBy(i => i.Item2).Select(i => i.Item1); return hints.OrderBy(i => i.Item3).Select(i => new SearchHintInfo
{
Item = i.Item1,
MatchedTerm = i.Item2
});
}
/// <summary>
/// Gets the index.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="searchInput">The search input.</param>
/// <param name="searchWords">The search input.</param>
/// <returns>System.Int32.</returns>
private Tuple<string, int> GetIndex(string input, string searchInput, string[] searchWords)
{
if (string.Equals(input, searchInput, StringComparison.OrdinalIgnoreCase))
{
return new Tuple<string, int>(searchInput, 0);
}
var index = input.IndexOf(searchInput, StringComparison.OrdinalIgnoreCase);
if (index == 0)
{
return new Tuple<string, int>(searchInput, 1);
}
if (index > 0)
{
return new Tuple<string, int>(searchInput, 2);
}
var items = GetWords(input);
for (var i = 0; i < searchWords.Length; i++)
{
var searchTerm = searchWords[i];
for (var j = 0; j < items.Length; j++)
{
var item = items[j];
if (string.Equals(item, searchTerm, StringComparison.OrdinalIgnoreCase))
{
return new Tuple<string, int>(searchTerm, 3 + (i + 1) * (j + 1));
}
index = item.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase);
if (index == 0)
{
return new Tuple<string, int>(searchTerm, 4 + (i + 1) * (j + 1));
}
if (index > 0)
{
return new Tuple<string, int>(searchTerm, 5 + (i + 1) * (j + 1));
}
}
}
return new Tuple<string, int>(null, -1);
} }
/// <summary> /// <summary>
@ -202,32 +291,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns>System.String[][].</returns> /// <returns>System.String[][].</returns>
private string[] GetWords(string term) private string[] GetWords(string term)
{ {
// TODO: Improve this to be more accurate and respect culture return term.Split().Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
var words = term.Split(' ');
return words;
}
/// <summary>
/// Indexes the of.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="term">The term.</param>
/// <returns>System.Int32.</returns>
private int IndexOf(string input, string term)
{
var index = 0;
foreach (var word in GetWords(input))
{
if (word.IndexOf(term, StringComparison.OrdinalIgnoreCase) != -1)
{
return index;
}
index++;
}
return -1;
} }
} }

View File

@ -93,6 +93,21 @@
<Content Include="dashboard-ui\css\images\bgflip.png"> <Content Include="dashboard-ui\css\images\bgflip.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\css\images\items\searchhints\film.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\items\searchhints\game.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\items\searchhints\music.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\items\searchhints\person.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\items\searchhints\tv.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\searchbutton.png"> <Content Include="dashboard-ui\css\images\searchbutton.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>