diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2013-05-25 19:52:41 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2013-05-25 19:52:41 -0400 |
| commit | 9026af7550a3b01956f12f03794b92cb3af03430 (patch) | |
| tree | bde81507959cc76eb126247bca6c44f71d86afb0 | |
| parent | 084557ca46d252b53b9535dde464b88369760c82 (diff) | |
unwrapped similar items api into separate endpoints for each type
| -rw-r--r-- | MediaBrowser.Api/AlbumsService.cs | 87 | ||||
| -rw-r--r-- | MediaBrowser.Api/GamesService.cs | 66 | ||||
| -rw-r--r-- | MediaBrowser.Api/LibraryService.cs | 209 | ||||
| -rw-r--r-- | MediaBrowser.Api/MediaBrowser.Api.csproj | 5 | ||||
| -rw-r--r-- | MediaBrowser.Api/MoviesService.cs | 67 | ||||
| -rw-r--r-- | MediaBrowser.Api/SimilarItemsHelper.cs | 184 | ||||
| -rw-r--r-- | MediaBrowser.Api/TrailersService.cs | 67 | ||||
| -rw-r--r-- | MediaBrowser.Api/TvShowsService.cs | 23 | ||||
| -rw-r--r-- | MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs | 62 | ||||
| -rw-r--r-- | MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs | 2 | ||||
| -rw-r--r-- | MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs | 66 | ||||
| -rw-r--r-- | MediaBrowser.WebDashboard/ApiClient.js | 48 | ||||
| -rw-r--r-- | MediaBrowser.WebDashboard/packages.config | 2 |
13 files changed, 637 insertions, 251 deletions
diff --git a/MediaBrowser.Api/AlbumsService.cs b/MediaBrowser.Api/AlbumsService.cs new file mode 100644 index 000000000..7ffe8b600 --- /dev/null +++ b/MediaBrowser.Api/AlbumsService.cs @@ -0,0 +1,87 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using ServiceStack.ServiceHost; +using System; +using System.Linq; + +namespace MediaBrowser.Api +{ + [Route("/Albums/{Id}/Similar", "GET")] + [Api(Description = "Finds albums similar to a given album.")] + public class GetSimilarAlbums : BaseGetSimilarItems + { + } + + public class AlbumsService : BaseApiService + { + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _user data repository + /// </summary> + private readonly IUserDataRepository _userDataRepository; + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + public AlbumsService(IUserManager userManager, IUserDataRepository userDataRepository, ILibraryManager libraryManager) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetSimilarAlbums request) + { + var result = SimilarItemsHelper.GetSimilarItems(_userManager, + _libraryManager, + _userDataRepository, + Logger, + request, item => item is MusicAlbum, + GetAlbumSimilarityScore); + + return ToOptimizedResult(result); + } + + /// <summary> + /// Gets the album similarity score. + /// </summary> + /// <param name="item1">The item1.</param> + /// <param name="item2">The item2.</param> + /// <returns>System.Int32.</returns> + private int GetAlbumSimilarityScore(BaseItem item1, BaseItem item2) + { + var points = SimilarItemsHelper.GetSimiliarityScore(item1, item2); + + var album1 = (MusicAlbum)item1; + var album2 = (MusicAlbum)item2; + + var artists1 = album1.RecursiveChildren + .OfType<Audio>() + .SelectMany(i => new[] { i.AlbumArtist, i.Artist }) + .Where(i => !string.IsNullOrEmpty(i)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var artists2 = album2.RecursiveChildren + .OfType<Audio>() + .SelectMany(i => new[] { i.AlbumArtist, i.Artist }) + .Where(i => !string.IsNullOrEmpty(i)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + return points + artists1.Where(i => artists2.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5); + } + } +} diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs new file mode 100644 index 000000000..7dc19a937 --- /dev/null +++ b/MediaBrowser.Api/GamesService.cs @@ -0,0 +1,66 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using ServiceStack.ServiceHost; + +namespace MediaBrowser.Api +{ + /// <summary> + /// Class GetSimilarGames + /// </summary> + [Route("/Games/{Id}/Similar", "GET")] + [Api(Description = "Finds games similar to a given game.")] + public class GetSimilarGames : BaseGetSimilarItems + { + } + + /// <summary> + /// Class GamesService + /// </summary> + public class GamesService : BaseApiService + { + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _user data repository + /// </summary> + private readonly IUserDataRepository _userDataRepository; + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="GamesService"/> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="userDataRepository">The user data repository.</param> + /// <param name="libraryManager">The library manager.</param> + public GamesService(IUserManager userManager, IUserDataRepository userDataRepository, ILibraryManager libraryManager) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetSimilarGames request) + { + var result = SimilarItemsHelper.GetSimilarItems(_userManager, + _libraryManager, + _userDataRepository, + Logger, + request, item => item is BaseGame, + SimilarItemsHelper.GetSimiliarityScore); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs index e3b93c340..681bbd851 100644 --- a/MediaBrowser.Api/LibraryService.cs +++ b/MediaBrowser.Api/LibraryService.cs @@ -127,32 +127,6 @@ namespace MediaBrowser.Api public Guid? UserId { get; set; } } - [Route("/Items/{Id}/Similar", "GET")] - [Api(Description = "Gets items similar to a given input item.")] - public class GetSimilarItems : IReturn<ItemsResult> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid? UserId { get; set; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - /// <summary> - /// The maximum number of items to return - /// </summary> - /// <value>The limit.</value> - [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? Limit { get; set; } - } - /// <summary> /// Class LibraryService /// </summary> @@ -420,6 +394,11 @@ namespace MediaBrowser.Api Task.WaitAll(task); } + /// <summary> + /// Refreshes the item. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>Task.</returns> private async Task RefreshItem(RefreshItem request) { var item = DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager); @@ -441,183 +420,5 @@ namespace MediaBrowser.Api Logger.ErrorException("Error refreshing library", ex); } } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetSimilarItems request) - { - var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; - - var item = string.IsNullOrEmpty(request.Id) ? - (request.UserId.HasValue ? user.RootFolder : - (Folder)_libraryManager.RootFolder) : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, request.UserId); - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList(); - - var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository); - - var inputItems = user == null - ? _libraryManager.RootFolder.RecursiveChildren - : user.RootFolder.GetRecursiveChildren(user); - - var items = GetSimilaritems(item, inputItems).ToArray(); - - var result = new ItemsResult - { - Items = items.Take(request.Limit ?? items.Length).Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)).Select(t => t.Result).ToArray(), - - TotalRecordCount = items.Length - }; - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the similiar items. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="inputItems">The input items.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - private IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems) - { - if (item is Movie || item is Trailer) - { - inputItems = inputItems.Where(i => i is Movie || i is Trailer); - } - else if (item is Series) - { - inputItems = inputItems.Where(i => i is Series); - } - else if (item is BaseGame) - { - inputItems = inputItems.Where(i => i is BaseGame); - } - else if (item is MusicAlbum) - { - inputItems = inputItems.Where(i => i is MusicAlbum); - } - else if (item is Audio) - { - inputItems = inputItems.Where(i => i is Audio); - } - - // Avoid implicitly captured closure - var currentItem = item; - - return inputItems.Where(i => i.Id != currentItem.Id) - .Select(i => new Tuple<BaseItem, int>(i, GetSimiliarityScore(item, i))) - .Where(i => i.Item2 > 0) - .OrderByDescending(i => i.Item2) - .ThenByDescending(i => i.Item1.CriticRating ?? 0) - .Select(i => i.Item1); - } - - /// <summary> - /// Gets the similiarity score. - /// </summary> - /// <param name="item1">The item1.</param> - /// <param name="item2">The item2.</param> - /// <returns>System.Int32.</returns> - private int GetSimiliarityScore(BaseItem item1, BaseItem item2) - { - var points = 0; - - if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase)) - { - points += 1; - } - - // Find common genres - points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5); - - // Find common tags - points += item1.Tags.Where(i => item2.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5); - - // Find common studios - points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 3); - - var item2PeopleNames = item2.People.Select(i => i.Name).ToList(); - - points += item1.People.Where(i => item2PeopleNames.Contains(i.Name, StringComparer.OrdinalIgnoreCase)).Sum(i => - { - if (string.Equals(i.Name, PersonType.Director, StringComparison.OrdinalIgnoreCase)) - { - return 5; - } - if (string.Equals(i.Name, PersonType.Actor, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - if (string.Equals(i.Name, PersonType.Composer, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - if (string.Equals(i.Name, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)) - { - return 3; - } - if (string.Equals(i.Name, PersonType.Writer, StringComparison.OrdinalIgnoreCase)) - { - return 2; - } - - return 1; - }); - - if (item1.ProductionYear.HasValue && item2.ProductionYear.HasValue) - { - var diff = Math.Abs(item1.ProductionYear.Value - item2.ProductionYear.Value); - - // Add if they came out within the same decade - if (diff < 10) - { - points += 3; - } - - // And more if within five years - if (diff < 5) - { - points += 3; - } - } - - var album = item1 as MusicAlbum; - - if (album != null) - { - points += GetAlbumSimilarityScore(album, (MusicAlbum)item2); - } - - return points; - } - - /// <summary> - /// Gets the album similarity score. - /// </summary> - /// <param name="item1">The item1.</param> - /// <param name="item2">The item2.</param> - /// <returns>System.Int32.</returns> - private int GetAlbumSimilarityScore(MusicAlbum item1, MusicAlbum item2) - { - var artists1 = item1.RecursiveChildren - .OfType<Audio>() - .SelectMany(i => new[] { i.AlbumArtist, i.Artist }) - .Where(i => !string.IsNullOrEmpty(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - var artists2 = item2.RecursiveChildren - .OfType<Audio>() - .SelectMany(i => new[] { i.AlbumArtist, i.Artist }) - .Where(i => !string.IsNullOrEmpty(i)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - return artists1.Where(i => artists2.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5); - } } } diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index aa5572b56..fca49d7d1 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -64,9 +64,11 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> + <Compile Include="AlbumsService.cs" /> <Compile Include="BaseApiService.cs" /> <Compile Include="DisplayPreferencesService.cs" /> <Compile Include="EnvironmentService.cs" /> + <Compile Include="GamesService.cs" /> <Compile Include="Images\ImageByNameService.cs" /> <Compile Include="Images\ImageRequest.cs" /> <Compile Include="Images\ImageService.cs" /> @@ -76,6 +78,7 @@ <Compile Include="Library\LibraryService.cs" /> <Compile Include="Library\LibraryStructureService.cs" /> <Compile Include="LocalizationService.cs" /> + <Compile Include="MoviesService.cs" /> <Compile Include="PackageService.cs" /> <Compile Include="Playback\Hls\AudioHlsService.cs" /> <Compile Include="Playback\Hls\BaseHlsService.cs" /> @@ -94,7 +97,9 @@ <Compile Include="ApiEntryPoint.cs" /> <Compile Include="SearchService.cs" /> <Compile Include="SessionsService.cs" /> + <Compile Include="SimilarItemsHelper.cs" /> <Compile Include="SystemService.cs" /> + <Compile Include="TrailersService.cs" /> <Compile Include="TvShowsService.cs" /> <Compile Include="UserLibrary\ArtistsService.cs" /> <Compile Include="UserLibrary\BaseItemsByNameService.cs" /> diff --git a/MediaBrowser.Api/MoviesService.cs b/MediaBrowser.Api/MoviesService.cs new file mode 100644 index 000000000..23a803b20 --- /dev/null +++ b/MediaBrowser.Api/MoviesService.cs @@ -0,0 +1,67 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using ServiceStack.ServiceHost; + +namespace MediaBrowser.Api +{ + /// <summary> + /// Class GetSimilarMovies + /// </summary> + [Route("/Movies/{Id}/Similar", "GET")] + [Api(Description = "Finds movies and trailers similar to a given movie.")] + public class GetSimilarMovies : BaseGetSimilarItems + { + } + + /// <summary> + /// Class MoviesService + /// </summary> + public class MoviesService : BaseApiService + { + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _user data repository + /// </summary> + private readonly IUserDataRepository _userDataRepository; + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="MoviesService"/> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="userDataRepository">The user data repository.</param> + /// <param name="libraryManager">The library manager.</param> + public MoviesService(IUserManager userManager, IUserDataRepository userDataRepository, ILibraryManager libraryManager) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetSimilarMovies request) + { + var result = SimilarItemsHelper.GetSimilarItems(_userManager, + _libraryManager, + _userDataRepository, + Logger, + request, item => item is Movie || item is Trailer, + SimilarItemsHelper.GetSimiliarityScore); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs new file mode 100644 index 000000000..e64ccb11d --- /dev/null +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -0,0 +1,184 @@ +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Api +{ + /// <summary> + /// Class BaseGetSimilarItems + /// </summary> + public class BaseGetSimilarItems : IReturn<ItemsResult> + { + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? UserId { get; set; } + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + /// <summary> + /// The maximum number of items to return + /// </summary> + /// <value>The limit.</value> + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + } + + /// <summary> + /// Class SimilarItemsHelper + /// </summary> + public static class SimilarItemsHelper + { + /// <summary> + /// Gets the similar items. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="libraryManager">The library manager.</param> + /// <param name="userDataRepository">The user data repository.</param> + /// <param name="logger">The logger.</param> + /// <param name="request">The request.</param> + /// <param name="includeInSearch">The include in search.</param> + /// <param name="getSimilarityScore">The get similarity score.</param> + /// <returns>ItemsResult.</returns> + internal static ItemsResult GetSimilarItems(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, ILogger logger, BaseGetSimilarItems request, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, int> getSimilarityScore) + { + var user = request.UserId.HasValue ? userManager.GetUserById(request.UserId.Value) : null; + + var item = string.IsNullOrEmpty(request.Id) ? + (request.UserId.HasValue ? user.RootFolder : + (Folder)libraryManager.RootFolder) : DtoBuilder.GetItemByClientId(request.Id, userManager, libraryManager, request.UserId); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList(); + + var dtoBuilder = new DtoBuilder(logger, libraryManager, userDataRepository); + + var inputItems = user == null + ? libraryManager.RootFolder.RecursiveChildren + : user.RootFolder.GetRecursiveChildren(user); + + var items = GetSimilaritems(item, inputItems, includeInSearch, getSimilarityScore).ToArray(); + + var result = new ItemsResult + { + Items = items.Take(request.Limit ?? items.Length).Select(i => dtoBuilder.GetBaseItemDto(i, fields, user)).Select(t => t.Result).ToArray(), + + TotalRecordCount = items.Length + }; + + return result; + } + + /// <summary> + /// Gets the similaritems. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="inputItems">The input items.</param> + /// <param name="includeInSearch">The include in search.</param> + /// <param name="getSimilarityScore">The get similarity score.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + private static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, int> getSimilarityScore) + { + inputItems = inputItems.Where(includeInSearch); + + // Avoid implicitly captured closure + var currentItem = item; + + return inputItems.Where(i => i.Id != currentItem.Id) + .Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, i))) + .Where(i => i.Item2 > 0) + .OrderByDescending(i => i.Item2) + .ThenByDescending(i => i.Item1.CriticRating ?? 0) + .Select(i => i.Item1); + } + + /// <summary> + /// Gets the similiarity score. + /// </summary> + /// <param name="item1">The item1.</param> + /// <param name="item2">The item2.</param> + /// <returns>System.Int32.</returns> + internal static int GetSimiliarityScore(BaseItem item1, BaseItem item2) + { + var points = 0; + + if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase)) + { + points += 1; + } + + // Find common genres + points += item1.Genres.Where(i => item2.Genres.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5); + + // Find common tags + points += item1.Tags.Where(i => item2.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 5); + + // Find common studios + points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 3); + + var item2PeopleNames = item2.People.Select(i => i.Name).ToList(); + + points += item1.People.Where(i => item2PeopleNames.Contains(i.Name, StringComparer.OrdinalIgnoreCase)).Sum(i => + { + if (string.Equals(i.Name, PersonType.Director, StringComparison.OrdinalIgnoreCase)) + { + return 5; + } + if (string.Equals(i.Name, PersonType.Actor, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + if (string.Equals(i.Name, PersonType.Composer, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + if (string.Equals(i.Name, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)) + { + return 3; + } + if (string.Equals(i.Name, PersonType.Writer, StringComparison.OrdinalIgnoreCase)) + { + return 2; + } + + return 1; + }); + + if (item1.ProductionYear.HasValue && item2.ProductionYear.HasValue) + { + var diff = Math.Abs(item1.ProductionYear.Value - item2.ProductionYear.Value); + + // Add if they came out within the same decade + if (diff < 10) + { + points += 3; + } + + // And more if within five years + if (diff < 5) + { + points += 3; + } + } + + return points; + } + + } +} diff --git a/MediaBrowser.Api/TrailersService.cs b/MediaBrowser.Api/TrailersService.cs new file mode 100644 index 000000000..bc6313de0 --- /dev/null +++ b/MediaBrowser.Api/TrailersService.cs @@ -0,0 +1,67 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using ServiceStack.ServiceHost; + +namespace MediaBrowser.Api +{ + /// <summary> + /// Class GetSimilarTrailers + /// </summary> + [Route("/Trailers/{Id}/Similar", "GET")] + [Api(Description = "Finds movies and trailers similar to a given trailer.")] + public class GetSimilarTrailers : BaseGetSimilarItems + { + } + + /// <summary> + /// Class TrailersService + /// </summary> + public class TrailersService : BaseApiService + { + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _user data repository + /// </summary> + private readonly IUserDataRepository _userDataRepository; + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="TrailersService"/> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="userDataRepository">The user data repository.</param> + /// <param name="libraryManager">The library manager.</param> + public TrailersService(IUserManager userManager, IUserDataRepository userDataRepository, ILibraryManager libraryManager) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetSimilarTrailers request) + { + var result = SimilarItemsHelper.GetSimilarItems(_userManager, + _libraryManager, + _userDataRepository, + Logger, + request, item => item is Movie || item is Trailer, + SimilarItemsHelper.GetSimiliarityScore); + + return ToOptimizedResult(result); + } + } +} diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 5c843cc08..04a18e40e 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -65,6 +65,12 @@ namespace MediaBrowser.Api } } + [Route("/Shows/{Id}/Similar", "GET")] + [Api(Description = "Finds tv shows similar to a given one.")] + public class GetSimilarShows : BaseGetSimilarItems + { + } + /// <summary> /// Class TvShowsService /// </summary> @@ -102,6 +108,23 @@ namespace MediaBrowser.Api /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> + public object Get(GetSimilarShows request) + { + var result = SimilarItemsHelper.GetSimilarItems(_userManager, + _libraryManager, + _userDataRepository, + Logger, + request, item => item is Series, + SimilarItemsHelper.GetSimiliarityScore); + + return ToOptimizedResult(result); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> public object Get(GetNextUpEpisodes request) { var result = GetNextUpEpisodes(request).Result; diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs index 93b246380..2df377cbc 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs @@ -5,6 +5,7 @@ using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; using System; using System.Data; +using System.Data.SQLite; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -43,6 +44,8 @@ namespace MediaBrowser.Server.Implementations.Sqlite /// </summary> private readonly IApplicationPaths _appPaths; + private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); + /// <summary> /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class. /// </summary> @@ -118,34 +121,51 @@ namespace MediaBrowser.Server.Implementations.Sqlite var serialized = _jsonSerializer.SerializeToBytes(displayPreferences); - cancellationToken.ThrowIfCancellationRequested(); + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + SQLiteTransaction transaction = null; - using (var cmd = Connection.CreateCommand()) + try { - cmd.CommandText = "replace into displaypreferences (id, data) values (@1, @2)"; - cmd.AddParam("@1", displayPreferences.Id); - cmd.AddParam("@2", serialized); + transaction = Connection.BeginTransaction(); - using (var tran = Connection.BeginTransaction()) + using (var cmd = Connection.CreateCommand()) { - try - { - cmd.Transaction = tran; + cmd.CommandText = "replace into displaypreferences (id, data) values (@1, @2)"; + cmd.AddParam("@1", displayPreferences.Id); + cmd.AddParam("@2", serialized); - await cmd.ExecuteNonQueryAsync(cancellationToken); + cmd.Transaction = transaction; - tran.Commit(); - } - catch (OperationCanceledException) - { - tran.Rollback(); - } - catch (Exception e) - { - Logger.ErrorException("Failed to commit transaction.", e); - tran.Rollback(); - } + await cmd.ExecuteNonQueryAsync(cancellationToken); } + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + } + catch (Exception e) + { + Logger.ErrorException("Failed to save display preferences:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); } } diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs index 0ca4dda77..33e9c7fe3 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteItemRepository.cs @@ -227,7 +227,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite await _saveItemCommand.ExecuteNonQueryAsync(cancellationToken); } - + transaction.Commit(); } catch (OperationCanceledException) diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs index d20b59035..284ef10fb 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Configuration; +using System.Data.SQLite; +using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Logging; @@ -19,6 +20,8 @@ namespace MediaBrowser.Server.Implementations.Sqlite { private readonly ConcurrentDictionary<string, Task<UserItemData>> _userData = new ConcurrentDictionary<string, Task<UserItemData>>(); + private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1); + /// <summary> /// The repository name /// </summary> @@ -172,34 +175,53 @@ namespace MediaBrowser.Server.Implementations.Sqlite cancellationToken.ThrowIfCancellationRequested(); - using (var cmd = Connection.CreateCommand()) + await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false); + + SQLiteTransaction transaction = null; + + try { - cmd.CommandText = "replace into userdata (key, userId, data) values (@1, @2, @3)"; - cmd.AddParam("@1", key); - cmd.AddParam("@2", userId); - cmd.AddParam("@3", serialized); + transaction = Connection.BeginTransaction(); - using (var tran = Connection.BeginTransaction()) + using (var cmd = Connection.CreateCommand()) { - try - { - cmd.Transaction = tran; + cmd.CommandText = "replace into userdata (key, userId, data) values (@1, @2, @3)"; + cmd.AddParam("@1", key); + cmd.AddParam("@2", userId); + cmd.AddParam("@3", serialized); - await cmd.ExecuteNonQueryAsync(cancellationToken); + cmd.Transaction = transaction; - tran.Commit(); - } - catch (OperationCanceledException) - { - tran.Rollback(); - } - catch (Exception e) - { - Logger.ErrorException("Failed to commit transaction.", e); - tran.Rollback(); - } + await cmd.ExecuteNonQueryAsync(cancellationToken); + } + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); } } + catch (Exception e) + { + Logger.ErrorException("Failed to save user data:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } } /// <summary> diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index d33731b70..799c267eb 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -257,9 +257,53 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { }); }; - self.getSimilarItems = function (itemId, options) { + self.getSimilarMovies = function (itemId, options) { - var url = self.getUrl("Items/" + itemId + "/Similar", options); + var url = self.getUrl("Movies/" + itemId + "/Similar", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getSimilarTrailers = function (itemId, options) { + + var url = self.getUrl("Trailers/" + itemId + "/Similar", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getSimilarShows = function (itemId, options) { + + var url = self.getUrl("Shows/" + itemId + "/Similar", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getSimilarAlbums = function (itemId, options) { + + var url = self.getUrl("Albums/" + itemId + "/Similar", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + self.getSimilarGames = function (itemId, options) { + + var url = self.getUrl("Games/" + itemId + "/Similar", options); return self.ajax({ type: "GET", diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index de8912c0e..fcfa61b2b 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.118" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.119" targetFramework="net45" /> <package id="ServiceStack.Common" version="3.9.46" targetFramework="net45" /> <package id="ServiceStack.Text" version="3.9.45" targetFramework="net45" /> </packages>
\ No newline at end of file |
