diff options
52 files changed, 1558 insertions, 582 deletions
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 1f69183cc..19cb14bbd 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -48,6 +48,19 @@ namespace MediaBrowser.Api.Images public string Name { get; set; } } + [Route("/Artists/{Name}/Images/{Type}", "GET")] + [Route("/Artists/{Name}/Images/{Type}/{Index}", "GET")] + [Api(Description = "Gets an artist image")] + public class GetArtistImage : ImageRequest + { + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + [ApiMember(Name = "Name", Description = "Artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Name { get; set; } + } + /// <summary> /// Class GetStudioImage /// </summary> @@ -238,6 +251,18 @@ namespace MediaBrowser.Api.Images /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> + public object Get(GetArtistImage request) + { + var item = _libraryManager.GetArtist(request.Name).Result; + + return GetImage(request, item); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> public object Get(GetGenreImage request) { var item = _libraryManager.GetGenre(request.Name).Result; diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 5b34119de..c7b4fae5f 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -1,10 +1,6 @@ using MediaBrowser.Common; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; @@ -37,66 +33,6 @@ namespace MediaBrowser.Api.Library } /// <summary> - /// Class GetPerson - /// </summary> - [Route("/Persons/{Name}", "GET")] - [Api(Description = "Gets a person, by name")] - public class GetPerson : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The person name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - /// <summary> - /// Class GetStudio - /// </summary> - [Route("/Studios/{Name}", "GET")] - [Api(Description = "Gets a studio, by name")] - public class GetStudio : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The studio name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - /// <summary> - /// Class GetGenre - /// </summary> - [Route("/Genres/{Name}", "GET")] - [Api(Description = "Gets a genre, by name")] - public class GetGenre : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - /// <summary> - /// Class GetYear - /// </summary> - [Route("/Years/{Year}", "GET")] - [Api(Description = "Gets a year")] - public class GetYear : IReturn<BaseItemDto> - { - /// <summary> - /// Gets or sets the year. - /// </summary> - /// <value>The year.</value> - [ApiMember(Name = "Year", Description = "The year", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Year { get; set; } - } - - /// <summary> /// Class LibraryService /// </summary> public class LibraryService : BaseApiService @@ -106,16 +42,14 @@ namespace MediaBrowser.Api.Library /// </summary> private readonly IApplicationHost _appHost; private readonly ILibraryManager _libraryManager; - private readonly IUserDataRepository _userDataRepository; /// <summary> /// Initializes a new instance of the <see cref="LibraryService" /> class. /// </summary> /// <param name="appHost">The app host.</param> /// <param name="libraryManager">The library manager.</param> - /// <param name="userDataRepository">The user data repository.</param> /// <exception cref="System.ArgumentNullException">appHost</exception> - public LibraryService(IApplicationHost appHost, ILibraryManager libraryManager, IUserDataRepository userDataRepository) + public LibraryService(IApplicationHost appHost, ILibraryManager libraryManager) { if (appHost == null) { @@ -124,75 +58,6 @@ namespace MediaBrowser.Api.Library _appHost = appHost; _libraryManager = libraryManager; - _userDataRepository = userDataRepository; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPerson request) - { - var item = _libraryManager.GetPerson(request.Name).Result; - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); - - var result = new DtoBuilder(Logger, _libraryManager, _userDataRepository).GetBaseItemDto(item, fields.ToList()).Result; - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetGenre request) - { - var item = _libraryManager.GetGenre(request.Name).Result; - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); - - var result = new DtoBuilder(Logger, _libraryManager, _userDataRepository).GetBaseItemDto(item, fields.ToList()).Result; - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetStudio request) - { - var item = _libraryManager.GetStudio(request.Name).Result; - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); - - var result = new DtoBuilder(Logger, _libraryManager, _userDataRepository).GetBaseItemDto(item, fields.ToList()).Result; - - return ToOptimizedResult(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetYear request) - { - var item = _libraryManager.GetYear(request.Year).Result; - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); - - var result = new DtoBuilder(Logger, _libraryManager, _userDataRepository).GetBaseItemDto(item, fields.ToList()).Result; - - return ToOptimizedResult(result); } /// <summary> diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 710c159e7..32c1bfaf5 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -87,6 +87,7 @@ <Compile Include="ScheduledTasks\ScheduledTasksWebSocketListener.cs" /> <Compile Include="ApiEntryPoint.cs" /> <Compile Include="SystemService.cs" /> + <Compile Include="UserLibrary\ArtistsService.cs" /> <Compile Include="UserLibrary\BaseItemsByNameService.cs" /> <Compile Include="UserLibrary\BaseItemsRequest.cs" /> <Compile Include="UserLibrary\GenresService.cs" /> diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs new file mode 100644 index 000000000..2622f0bd1 --- /dev/null +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -0,0 +1,187 @@ +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; +using ServiceStack.ServiceHost; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.UserLibrary +{ + /// <summary> + /// Class GetArtists + /// </summary> + [Route("/Artists", "GET")] + [Api(Description = "Gets all artists from a given item, folder, or the entire library")] + public class GetArtists : GetItemsByName + { + } + + /// <summary> + /// Class GetArtistsItemCounts + /// </summary> + [Route("/Artists/{Name}/Counts", "GET")] + [Api(Description = "Gets item counts of library items that an artist appears in")] + public class GetArtistsItemCounts : IReturn<ItemByNameCounts> + { + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public Guid UserId { get; set; } + + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + [ApiMember(Name = "Name", Description = "The artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Name { get; set; } + } + + [Route("/Artists/{Name}", "GET")] + [Api(Description = "Gets an artist, by name")] + public class GetArtist : IReturn<BaseItemDto> + { + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + [ApiMember(Name = "Name", Description = "The artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Name { get; set; } + + /// <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> + /// Class ArtistsService + /// </summary> + public class ArtistsService : BaseItemsByNameService<Artist> + { + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsService"/> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="libraryManager">The library manager.</param> + /// <param name="userDataRepository">The user data repository.</param> + public ArtistsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository) + : base(userManager, libraryManager, userDataRepository) + { + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetArtist request) + { + var result = GetItem(request).Result; + + return ToOptimizedResult(result); + } + + /// <summary> + /// Gets the item. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>Task{BaseItemDto}.</returns> + private async Task<BaseItemDto> GetItem(GetArtist request) + { + var item = await LibraryManager.GetArtist(request.Name).ConfigureAwait(false); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); + + var builder = new DtoBuilder(Logger, LibraryManager, UserDataRepository); + + if (request.UserId.HasValue) + { + var user = UserManager.GetUserById(request.UserId.Value); + + return await builder.GetBaseItemDto(item, user, fields.ToList()).ConfigureAwait(false); + } + + return await builder.GetBaseItemDto(item, fields.ToList()).ConfigureAwait(false); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetArtistsItemCounts request) + { + var items = GetItems(request.UserId).OfType<Audio>().Where(i => i.HasArtist(request.Name)).ToList(); + + var counts = new ItemByNameCounts + { + TotalCount = items.Count, + + SongCount = items.Count(), + + AlbumCount = items.Select(i => i.Parent).OfType<MusicAlbum>().Distinct().Count() + }; + + return ToOptimizedResult(counts); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetArtists request) + { + var result = GetResult(request).Result; + + return ToOptimizedResult(result); + } + + /// <summary> + /// Gets all items. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="items">The items.</param> + /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> + protected override IEnumerable<IbnStub<Artist>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) + { + var itemsList = items.OfType<Audio>().ToList(); + + return itemsList + .SelectMany(i => + { + var list = i.Artists.ToList(); + + if (!string.IsNullOrEmpty(i.AlbumArtist)) + { + list.Add(i.AlbumArtist); + } + + return list; + }) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(name => new IbnStub<Artist>(name, () => itemsList.Where(i => i.HasArtist(name)), GetEntity)); + } + + /// <summary> + /// Gets the entity. + /// </summary> + /// <param name="name">The name.</param> + /// <returns>Task{Artist}.</returns> + protected Task<Artist> GetEntity(string name) + { + return LibraryManager.GetArtist(name); + } + } +} diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 0a2d6453a..8f2264c6a 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -50,9 +50,18 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>Task{ItemsResult}.</returns> protected async Task<ItemsResult> GetResult(GetItemsByName request) { - var user = UserManager.GetUserById(request.UserId); + User user = null; + BaseItem item; - var item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.ParentId, UserManager, LibraryManager, user.Id); + if (request.UserId.HasValue) + { + user = UserManager.GetUserById(request.UserId.Value); + item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.ParentId, UserManager, LibraryManager, user.Id); + } + else + { + item = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : DtoBuilder.GetItemByClientId(request.ParentId, UserManager, LibraryManager); + } IEnumerable<BaseItem> items; @@ -60,16 +69,23 @@ namespace MediaBrowser.Api.UserLibrary { var folder = (Folder)item; - items = request.Recursive ? folder.GetRecursiveChildren(user) : folder.GetChildren(user); + if (request.UserId.HasValue) + { + items = request.Recursive ? folder.GetRecursiveChildren(user) : folder.GetChildren(user); + } + else + { + items = request.Recursive ? folder.RecursiveChildren: folder.Children; + } } else { items = new[] { item }; } - items = FilterItems(request, items, user); + items = FilterItems(request, items); - var extractedItems = GetAllItems(request, items, user); + var extractedItems = GetAllItems(request, items); extractedItems = FilterItems(request, extractedItems, user); extractedItems = SortItems(request, extractedItems); @@ -187,9 +203,8 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <param name="items">The items.</param> - /// <param name="user">The user.</param> /// <returns>IEnumerable{BaseItem}.</returns> - private IEnumerable<BaseItem> FilterItems(GetItemsByName request, IEnumerable<BaseItem> items, User user) + private IEnumerable<BaseItem> FilterItems(GetItemsByName request, IEnumerable<BaseItem> items) { // Exclude item types if (!string.IsNullOrEmpty(request.ExcludeItemTypes)) @@ -213,9 +228,8 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <param name="items">The items.</param> - /// <param name="user">The user.</param> /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected abstract IEnumerable<IbnStub<TItemType>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items, User user); + protected abstract IEnumerable<IbnStub<TItemType>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items); /// <summary> /// Gets the dto. @@ -238,18 +252,36 @@ namespace MediaBrowser.Api.UserLibrary return null; } - var dto = await new DtoBuilder(Logger, LibraryManager, UserDataRepository).GetBaseItemDto(item, user, fields).ConfigureAwait(false); + var dto = user == null ? await new DtoBuilder(Logger, LibraryManager, UserDataRepository).GetBaseItemDto(item, fields).ConfigureAwait(false) : + await new DtoBuilder(Logger, LibraryManager, UserDataRepository).GetBaseItemDto(item, user, fields).ConfigureAwait(false); if (fields.Contains(ItemFields.ItemCounts)) { var items = stub.Items; dto.ChildCount = items.Count; - dto.RecentlyAddedItemCount = items.Count(i => i.IsRecentlyAdded(user)); + dto.RecentlyAddedItemCount = items.Count(i => i.IsRecentlyAdded()); } return dto; } + + /// <summary> + /// Gets the items. + /// </summary> + /// <param name="userId">The user id.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + protected IEnumerable<BaseItem> GetItems(Guid? userId) + { + if (userId.HasValue) + { + var user = UserManager.GetUserById(userId.Value); + + return UserManager.GetUserById(userId.Value).RootFolder.GetRecursiveChildren(user); + } + + return LibraryManager.RootFolder.RecursiveChildren; + } } /// <summary> @@ -258,11 +290,23 @@ namespace MediaBrowser.Api.UserLibrary public class GetItemsByName : BaseItemsRequest, 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> /// What to sort the results by /// </summary> /// <value>The sort by.</value> [ApiMember(Name = "SortBy", Description = "Optional. Options: SortName", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string SortBy { get; set; } + + public GetItemsByName() + { + Recursive = true; + } } public class IbnStub<T> diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index afe93a086..7dcce53b2 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -10,13 +10,6 @@ namespace MediaBrowser.Api.UserLibrary public abstract class BaseItemsRequest { /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> /// Skips over a given number of items within the results. Use for paging. /// </summary> /// <value>The start index.</value> diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 4f87b154e..30094d4f3 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -1,11 +1,12 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; @@ -17,17 +18,13 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class GetGenres /// </summary> - [Route("/Users/{UserId}/Items/{ParentId}/Genres", "GET")] - [Route("/Users/{UserId}/Items/Root/Genres", "GET")] + [Route("/Genres", "GET")] [Api(Description = "Gets all genres from a given item, folder, or the entire library")] public class GetGenres : GetItemsByName { } - /// <summary> - /// Class GetGenreItemCounts - /// </summary> - [Route("/Users/{UserId}/Genres/{Name}/Counts", "GET")] + [Route("/Genres/{Name}/Counts", "GET")] [Api(Description = "Gets item counts of library items that a genre appears in")] public class GetGenreItemCounts : IReturn<ItemByNameCounts> { @@ -35,8 +32,8 @@ namespace MediaBrowser.Api.UserLibrary /// Gets or sets the user id. /// </summary> /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? UserId { get; set; } /// <summary> /// Gets or sets the name. @@ -45,6 +42,28 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } } + + /// <summary> + /// Class GetGenre + /// </summary> + [Route("/Genres/{Name}", "GET")] + [Api(Description = "Gets a genre, by name")] + public class GetGenre : IReturn<BaseItemDto> + { + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Name { get; set; } + + /// <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> /// Class GenresService @@ -61,32 +80,37 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public object Get(GetGenreItemCounts request) + public object Get(GetGenre request) { - var user = UserManager.GetUserById(request.UserId); - - var items = user.RootFolder.GetRecursiveChildren(user).Where(i => i.Genres != null && i.Genres.Contains(request.Name, StringComparer.OrdinalIgnoreCase)).ToList(); + var result = GetItem(request).Result; - var counts = new ItemByNameCounts - { - TotalCount = items.Count, - - TrailerCount = items.OfType<Trailer>().Count(), + return ToOptimizedResult(result); + } - MovieCount = items.OfType<Movie>().Count(), + /// <summary> + /// Gets the item. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>Task{BaseItemDto}.</returns> + private async Task<BaseItemDto> GetItem(GetGenre request) + { + var item = await LibraryManager.GetGenre(request.Name).ConfigureAwait(false); - SeriesCount = items.OfType<Series>().Count(), + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); - GameCount = items.OfType<BaseGame>().Count(), + var builder = new DtoBuilder(Logger, LibraryManager, UserDataRepository); - SongCount = items.OfType<Audio>().Count(), + if (request.UserId.HasValue) + { + var user = UserManager.GetUserById(request.UserId.Value); - AlbumCount = items.OfType<MusicAlbum>().Count() - }; + return await builder.GetBaseItemDto(item, user, fields.ToList()).ConfigureAwait(false); + } - return ToOptimizedResult(counts); + return await builder.GetBaseItemDto(item, fields.ToList()).ConfigureAwait(false); } - + /// <summary> /// Gets the specified request. /// </summary> @@ -104,9 +128,8 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <param name="items">The items.</param> - /// <param name="user">The user.</param> /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<IbnStub<Genre>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items, User user) + protected override IEnumerable<IbnStub<Genre>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) { var itemsList = items.Where(i => i.Genres != null).ToList(); @@ -125,5 +148,34 @@ namespace MediaBrowser.Api.UserLibrary { return LibraryManager.GetGenre(name); } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetGenreItemCounts request) + { + var items = GetItems(request.UserId).Where(i => i.Genres != null && i.Genres.Contains(request.Name, StringComparer.OrdinalIgnoreCase)).ToList(); + + var counts = new ItemByNameCounts + { + TotalCount = items.Count, + + TrailerCount = items.OfType<Trailer>().Count(), + + MovieCount = items.OfType<Movie>().Count(), + + SeriesCount = items.OfType<Series>().Count(), + + GameCount = items.OfType<BaseGame>().Count(), + + SongCount = items.OfType<Audio>().Count(), + + AlbumCount = items.OfType<MusicAlbum>().Count() + }; + + return ToOptimizedResult(counts); + } } } diff --git a/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs b/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs index e068eb216..a3f36afe9 100644 --- a/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs @@ -1,6 +1,8 @@ -using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using ServiceStack.ServiceHost; +using ServiceStack.Text.Controller; using System; using System.Threading; using System.Threading.Tasks; @@ -8,31 +10,12 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.UserLibrary { /// <summary> - /// Class GetItemByNameUserData - /// </summary> - [Route("/Users/{UserId}/ItemsByName/{Name}/UserData", "GET")] - [Api(Description = "Gets user data for an item")] - public class GetItemByNameUserData : IReturnVoid - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public Guid UserId { get; set; } - - /// <summary> - /// Gets or sets the name. - /// </summary> - /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The item name (genre, person, year, studio, artist, album)", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - /// <summary> /// Class MarkItemByNameFavorite /// </summary> - [Route("/Users/{UserId}/ItemsByName/Favorites/{Name}", "POST")] + [Route("/Users/{UserId}/Favorites/Artists/{Name}", "POST")] + [Route("/Users/{UserId}/Favorites/Persons/{Name}", "POST")] + [Route("/Users/{UserId}/Favorites/Studios/{Name}", "POST")] + [Route("/Users/{UserId}/Favorites/Genres/{Name}", "POST")] [Api(Description = "Marks something as a favorite")] public class MarkItemByNameFavorite : IReturnVoid { @@ -47,14 +30,17 @@ namespace MediaBrowser.Api.UserLibrary /// Gets or sets the name. /// </summary> /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The item name (genre, person, year, studio, artist, album)", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "Name", Description = "The name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Name { get; set; } } /// <summary> /// Class UnmarkItemByNameFavorite /// </summary> - [Route("/Users/{UserId}/ItemsByName/Favorites/{Name}", "DELETE")] + [Route("/Users/{UserId}/Favorites/Artists/{Name}", "DELETE")] + [Route("/Users/{UserId}/Favorites/Persons/{Name}", "DELETE")] + [Route("/Users/{UserId}/Favorites/Studios/{Name}", "DELETE")] + [Route("/Users/{UserId}/Favorites/Genres/{Name}", "DELETE")] [Api(Description = "Unmarks something as a favorite")] public class UnmarkItemByNameFavorite : IReturnVoid { @@ -69,11 +55,17 @@ namespace MediaBrowser.Api.UserLibrary /// Gets or sets the name. /// </summary> /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The item name (genre, person, year, studio, artist, album)", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + [ApiMember(Name = "Name", Description = "The name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Name { get; set; } } - [Route("/Users/{UserId}/ItemsByName/{Name}/Rating", "POST")] + /// <summary> + /// Class UpdateItemByNameRating + /// </summary> + [Route("/Users/{UserId}/Ratings/Artists/{Name}", "POST")] + [Route("/Users/{UserId}/Ratings/Persons/{Name}", "POST")] + [Route("/Users/{UserId}/Ratings/Studios/{Name}", "POST")] + [Route("/Users/{UserId}/Ratings/Genres/{Name}", "POST")] [Api(Description = "Updates a user's rating for an item")] public class UpdateItemByNameRating : IReturnVoid { @@ -88,7 +80,7 @@ namespace MediaBrowser.Api.UserLibrary /// Gets or sets the name. /// </summary> /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The item name (genre, person, year, studio, artist, album)", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + [ApiMember(Name = "Name", Description = "The name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] public string Name { get; set; } /// <summary> @@ -99,7 +91,13 @@ namespace MediaBrowser.Api.UserLibrary public bool Likes { get; set; } } - [Route("/Users/{UserId}/ItemsByName/{Name}/Rating", "DELETE")] + /// <summary> + /// Class DeleteItemByNameRating + /// </summary> + [Route("/Users/{UserId}/Ratings/Artists/{Name}", "DELETE")] + [Route("/Users/{UserId}/Ratings/Persons/{Name}", "DELETE")] + [Route("/Users/{UserId}/Ratings/Studios/{Name}", "DELETE")] + [Route("/Users/{UserId}/Ratings/Genres/{Name}", "DELETE")] [Api(Description = "Deletes a user's saved personal rating for an item")] public class DeleteItemByNameRating : IReturnVoid { @@ -114,10 +112,10 @@ namespace MediaBrowser.Api.UserLibrary /// Gets or sets the name. /// </summary> /// <value>The name.</value> - [ApiMember(Name = "Name", Description = "The item name (genre, person, year, studio, artist, album)", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + [ApiMember(Name = "Name", Description = "The item name (genre, person, year, studio, artist)", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] public string Name { get; set; } } - + /// <summary> /// Class ItemByNameUserDataService /// </summary> @@ -129,34 +127,31 @@ namespace MediaBrowser.Api.UserLibrary protected readonly IUserDataRepository UserDataRepository; /// <summary> + /// The library manager + /// </summary> + protected readonly ILibraryManager LibraryManager; + + /// <summary> /// Initializes a new instance of the <see cref="ItemByNameUserDataService" /> class. /// </summary> /// <param name="userDataRepository">The user data repository.</param> - public ItemByNameUserDataService(IUserDataRepository userDataRepository) + /// <param name="libraryManager">The library manager.</param> + public ItemByNameUserDataService(IUserDataRepository userDataRepository, ILibraryManager libraryManager) { UserDataRepository = userDataRepository; + LibraryManager = libraryManager; } /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetItemByNameUserData request) - { - // Get the user data for this item - var data = UserDataRepository.GetUserData(request.UserId, request.Name).Result; - - return ToOptimizedResult(DtoBuilder.GetUserItemDataDto(data)); - } - - /// <summary> /// Posts the specified request. /// </summary> /// <param name="request">The request.</param> public void Post(MarkItemByNameFavorite request) { - var task = MarkFavorite(request.UserId, request.Name, true); + var pathInfo = PathInfo.Parse(RequestContext.PathInfo); + var type = pathInfo.GetArgumentValue<string>(3); + + var task = MarkFavorite(request.UserId, type, request.Name, true); Task.WaitAll(task); } @@ -167,18 +162,24 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> public void Post(UpdateItemByNameRating request) { - var task = MarkLike(request.UserId, request.Name, request.Likes); + var pathInfo = PathInfo.Parse(RequestContext.PathInfo); + var type = pathInfo.GetArgumentValue<string>(3); + + var task = MarkLike(request.UserId, type, request.Name, request.Likes); Task.WaitAll(task); } - + /// <summary> /// Deletes the specified request. /// </summary> /// <param name="request">The request.</param> public void Delete(UnmarkItemByNameFavorite request) { - var task = MarkFavorite(request.UserId, request.Name, false); + var pathInfo = PathInfo.Parse(RequestContext.PathInfo); + var type = pathInfo.GetArgumentValue<string>(3); + + var task = MarkFavorite(request.UserId, type, request.Name, false); Task.WaitAll(task); } @@ -189,7 +190,10 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> public void Delete(DeleteItemByNameRating request) { - var task = MarkLike(request.UserId, request.Name, null); + var pathInfo = PathInfo.Parse(RequestContext.PathInfo); + var type = pathInfo.GetArgumentValue<string>(3); + + var task = MarkLike(request.UserId, type, request.Name, null); Task.WaitAll(task); } @@ -198,11 +202,37 @@ namespace MediaBrowser.Api.UserLibrary /// Marks the favorite. /// </summary> /// <param name="userId">The user id.</param> - /// <param name="key">The key.</param> + /// <param name="type">The type.</param> + /// <param name="name">The name.</param> /// <param name="isFavorite">if set to <c>true</c> [is favorite].</param> /// <returns>Task.</returns> - protected async Task MarkFavorite(Guid userId, string key, bool isFavorite) + protected async Task MarkFavorite(Guid userId, string type, string name, bool isFavorite) { + BaseItem item; + + if (string.Equals(type, "Persons")) + { + item = await LibraryManager.GetPerson(name).ConfigureAwait(false); + } + else if (string.Equals(type, "Artists")) + { + item = await LibraryManager.GetArtist(name).ConfigureAwait(false); + } + else if (string.Equals(type, "Genres")) + { + item = await LibraryManager.GetGenre(name).ConfigureAwait(false); + } + else if (string.Equals(type, "Studios")) + { + item = await LibraryManager.GetStudio(name).ConfigureAwait(false); + } + else + { + throw new ArgumentException(); + } + + var key = item.GetUserDataKey(); + // Get the user data for this item var data = await UserDataRepository.GetUserData(userId, key).ConfigureAwait(false); @@ -216,11 +246,37 @@ namespace MediaBrowser.Api.UserLibrary /// Marks the like. /// </summary> /// <param name="userId">The user id.</param> - /// <param name="key">The key.</param> + /// <param name="type">The type.</param> + /// <param name="name">The name.</param> /// <param name="likes">if set to <c>true</c> [likes].</param> /// <returns>Task.</returns> - protected async Task MarkLike(Guid userId, string key, bool? likes) + protected async Task MarkLike(Guid userId, string type, string name, bool? likes) { + BaseItem item; + + if (string.Equals(type, "Persons")) + { + item = await LibraryManager.GetPerson(name).ConfigureAwait(false); + } + else if (string.Equals(type, "Artists")) + { + item = await LibraryManager.GetArtist(name).ConfigureAwait(false); + } + else if (string.Equals(type, "Genres")) + { + item = await LibraryManager.GetGenre(name).ConfigureAwait(false); + } + else if (string.Equals(type, "Studios")) + { + item = await LibraryManager.GetStudio(name).ConfigureAwait(false); + } + else + { + throw new ArgumentException(); + } + + var key = item.GetUserDataKey(); + // Get the user data for this item var data = await UserDataRepository.GetUserData(userId, key).ConfigureAwait(false); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 79e00998f..7d3581846 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -22,6 +22,13 @@ namespace MediaBrowser.Api.UserLibrary public class GetItems : BaseItemsRequest, IReturn<ItemsResult> { /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public Guid UserId { get; set; } + + /// <summary> /// Limit results to items containing a specific person /// </summary> /// <value>The person.</value> @@ -328,7 +335,7 @@ namespace MediaBrowser.Api.UserLibrary }); case ItemFilter.IsRecentlyAdded: - return items.Where(item => item.IsRecentlyAdded(currentUser)); + return items.Where(item => item.IsRecentlyAdded()); case ItemFilter.IsResumable: return items.Where(item => diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index fb623e953..ee16a986e 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; @@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; @@ -17,8 +19,7 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class GetPersons /// </summary> - [Route("/Users/{UserId}/Items/{ParentId}/Persons", "GET")] - [Route("/Users/{UserId}/Items/Root/Persons", "GET")] + [Route("/Persons", "GET")] [Api(Description = "Gets all persons from a given item, folder, or the entire library")] public class GetPersons : GetItemsByName { @@ -32,7 +33,7 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class GetPersonItemCounts /// </summary> - [Route("/Users/{UserId}/Persons/{Name}/Counts", "GET")] + [Route("/Persons/{Name}/Counts", "GET")] [Api(Description = "Gets item counts of library items that a person appears in")] public class GetPersonItemCounts : IReturn<ItemByNameCounts> { @@ -52,6 +53,28 @@ namespace MediaBrowser.Api.UserLibrary } /// <summary> + /// Class GetPerson + /// </summary> + [Route("/Persons/{Name}", "GET")] + [Api(Description = "Gets a person, by name")] + public class GetPerson : IReturn<BaseItemDto> + { + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + [ApiMember(Name = "Name", Description = "The person name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Name { get; set; } + + /// <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> /// Class PersonsService /// </summary> public class PersonsService : BaseItemsByNameService<Person> @@ -72,6 +95,42 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> + public object Get(GetPerson request) + { + var result = GetItem(request).Result; + + return ToOptimizedResult(result); + } + + /// <summary> + /// Gets the item. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>Task{BaseItemDto}.</returns> + private async Task<BaseItemDto> GetItem(GetPerson request) + { + var item = await LibraryManager.GetPerson(request.Name).ConfigureAwait(false); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); + + var builder = new DtoBuilder(Logger, LibraryManager, UserDataRepository); + + if (request.UserId.HasValue) + { + var user = UserManager.GetUserById(request.UserId.Value); + + return await builder.GetBaseItemDto(item, user, fields.ToList()).ConfigureAwait(false); + } + + return await builder.GetBaseItemDto(item, fields.ToList()).ConfigureAwait(false); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> public object Get(GetPersons request) { var result = GetResult(request).Result; @@ -86,9 +145,7 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>System.Object.</returns> public object Get(GetPersonItemCounts request) { - var user = UserManager.GetUserById(request.UserId); - - var items = user.RootFolder.GetRecursiveChildren(user).Where(i => i.People != null && i.People.Any(p => string.Equals(p.Name, request.Name, StringComparison.OrdinalIgnoreCase))).ToList(); + var items = GetItems(request.UserId).Where(i => i.People != null && i.People.Any(p => string.Equals(p.Name, request.Name, StringComparison.OrdinalIgnoreCase))).ToList(); var counts = new ItemByNameCounts { @@ -117,9 +174,8 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <param name="items">The items.</param> - /// <param name="user">The user.</param> /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<IbnStub<Person>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items, User user) + protected override IEnumerable<IbnStub<Person>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) { var inputPersonTypes = ((GetPersons)request).PersonTypes; var personTypes = string.IsNullOrEmpty(inputPersonTypes) ? new string[] { } : inputPersonTypes.Split(','); diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index 94c604685..4e3a2d42a 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -1,10 +1,12 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; using System; using System.Collections.Generic; @@ -16,14 +18,13 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class GetStudios /// </summary> - [Route("/Users/{UserId}/Items/{ParentId}/Studios", "GET")] - [Route("/Users/{UserId}/Items/Root/Studios", "GET")] + [Route("/Studios", "GET")] [Api(Description = "Gets all studios from a given item, folder, or the entire library")] public class GetStudios : GetItemsByName { } - [Route("/Users/{UserId}/Studios/{Name}/Counts", "GET")] + [Route("/Studios/{Name}/Counts", "GET")] [Api(Description = "Gets item counts of library items that a studio appears in")] public class GetStudioItemCounts : IReturn<ItemByNameCounts> { @@ -43,6 +44,28 @@ namespace MediaBrowser.Api.UserLibrary } /// <summary> + /// Class GetStudio + /// </summary> + [Route("/Studios/{Name}", "GET")] + [Api(Description = "Gets a studio, by name")] + public class GetStudio : IReturn<BaseItemDto> + { + /// <summary> + /// Gets or sets the name. + /// </summary> + /// <value>The name.</value> + [ApiMember(Name = "Name", Description = "The studio name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Name { get; set; } + + /// <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> /// Class StudiosService /// </summary> public class StudiosService : BaseItemsByNameService<Studio> @@ -57,11 +80,45 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public object Get(GetStudioItemCounts request) + public object Get(GetStudio request) + { + var result = GetItem(request).Result; + + return ToOptimizedResult(result); + } + + /// <summary> + /// Gets the item. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>Task{BaseItemDto}.</returns> + private async Task<BaseItemDto> GetItem(GetStudio request) { - var user = UserManager.GetUserById(request.UserId); + var item = await LibraryManager.GetStudio(request.Name).ConfigureAwait(false); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); + + var builder = new DtoBuilder(Logger, LibraryManager, UserDataRepository); + + if (request.UserId.HasValue) + { + var user = UserManager.GetUserById(request.UserId.Value); + + return await builder.GetBaseItemDto(item, user, fields.ToList()).ConfigureAwait(false); + } - var items = user.RootFolder.GetRecursiveChildren(user).Where(i => i.Studios != null && i.Studios.Contains(request.Name, StringComparer.OrdinalIgnoreCase)).ToList(); + return await builder.GetBaseItemDto(item, fields.ToList()).ConfigureAwait(false); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetStudioItemCounts request) + { + var items = GetItems(request.UserId).Where(i => i.Studios != null && i.Studios.Contains(request.Name, StringComparer.OrdinalIgnoreCase)).ToList(); var counts = new ItemByNameCounts { @@ -94,15 +151,14 @@ namespace MediaBrowser.Api.UserLibrary return ToOptimizedResult(result); } - + /// <summary> /// Gets all items. /// </summary> /// <param name="request">The request.</param> /// <param name="items">The items.</param> - /// <param name="user">The user.</param> /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<IbnStub<Studio>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items, User user) + protected override IEnumerable<IbnStub<Studio>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) { var itemsList = items.Where(i => i.Studios != null).ToList(); diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index b22a8dac3..79cb08ef9 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -1,7 +1,11 @@ -using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Querying; using ServiceStack.ServiceHost; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -12,14 +16,35 @@ namespace MediaBrowser.Api.UserLibrary /// <summary> /// Class GetYears /// </summary> - [Route("/Users/{UserId}/Items/{ParentId}/Years", "GET")] - [Route("/Users/{UserId}/Items/Root/Years", "GET")] + [Route("/Years", "GET")] [Api(Description = "Gets all years from a given item, folder, or the entire library")] public class GetYears : GetItemsByName { } /// <summary> + /// Class GetYear + /// </summary> + [Route("/Years/{Year}", "GET")] + [Api(Description = "Gets a year")] + public class GetYear : IReturn<BaseItemDto> + { + /// <summary> + /// Gets or sets the year. + /// </summary> + /// <value>The year.</value> + [ApiMember(Name = "Year", Description = "The year", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] + public int Year { get; set; } + + /// <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> /// Class YearsService /// </summary> public class YearsService : BaseItemsByNameService<Year> @@ -39,6 +64,42 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> + public object Get(GetYear request) + { + var result = GetItem(request).Result; + + return ToOptimizedResult(result); + } + + /// <summary> + /// Gets the item. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>Task{BaseItemDto}.</returns> + private async Task<BaseItemDto> GetItem(GetYear request) + { + var item = await LibraryManager.GetYear(request.Year).ConfigureAwait(false); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)); + + var builder = new DtoBuilder(Logger, LibraryManager, UserDataRepository); + + if (request.UserId.HasValue) + { + var user = UserManager.GetUserById(request.UserId.Value); + + return await builder.GetBaseItemDto(item, user, fields.ToList()).ConfigureAwait(false); + } + + return await builder.GetBaseItemDto(item, fields.ToList()).ConfigureAwait(false); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> public object Get(GetYears request) { var result = GetResult(request).Result; @@ -51,9 +112,8 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <param name="items">The items.</param> - /// <param name="user">The user.</param> /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> - protected override IEnumerable<IbnStub<Year>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items, User user) + protected override IEnumerable<IbnStub<Year>> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) { var itemsList = items.Where(i => i.ProductionYear != null).ToList(); diff --git a/MediaBrowser.Controller/Dto/DtoBuilder.cs b/MediaBrowser.Controller/Dto/DtoBuilder.cs index 2631488a5..371241d1b 100644 --- a/MediaBrowser.Controller/Dto/DtoBuilder.cs +++ b/MediaBrowser.Controller/Dto/DtoBuilder.cs @@ -528,7 +528,7 @@ namespace MediaBrowser.Controller.Dto recursiveItemCount++; // Check is recently added - if (child.IsRecentlyAdded(user)) + if (child.IsRecentlyAdded()) { rcentlyAddedItemCount++; } diff --git a/MediaBrowser.Controller/Entities/Audio/Artist.cs b/MediaBrowser.Controller/Entities/Audio/Artist.cs index dcd6af92d..567b67868 100644 --- a/MediaBrowser.Controller/Entities/Audio/Artist.cs +++ b/MediaBrowser.Controller/Entities/Audio/Artist.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <returns>System.String.</returns> public override string GetUserDataKey() { - return Name; + return "Artist-" + Name; } } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 9deb8241d..01bdd84ac 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -113,5 +113,15 @@ namespace MediaBrowser.Controller.Entities.Audio return (ProductionYear != null ? ProductionYear.Value.ToString("000-") : "") + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name; } + + /// <summary> + /// Determines whether the specified name has artist. + /// </summary> + /// <param name="name">The name.</param> + /// <returns><c>true</c> if the specified name has artist; otherwise, <c>false</c>.</returns> + public bool HasArtist(string name) + { + return Artists.Contains(name, StringComparer.OrdinalIgnoreCase) || string.Equals(AlbumArtist, name, StringComparison.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 7b64c0e85..7d6577b4e 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// The unknwon artist /// </summary> - private static readonly MusicArtist UnknwonArtist = new MusicArtist {Name = "<Unknown>"}; + private static readonly MusicArtist UnknwonArtist = new MusicArtist { Name = "<Unknown>" }; /// <summary> /// Override this to return the folder that should be used to construct a container diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index b5627e061..1f1d5e083 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - namespace MediaBrowser.Controller.Entities.Audio { /// <summary> @@ -8,12 +6,6 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> public class MusicArtist : Folder { - public Dictionary<string, string> AlbumCovers { get; set; } - public override void ClearMetaValues() - { - AlbumCovers = null; - base.ClearMetaValues(); - } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index f7963c6e6..1d803ea45 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -23,6 +23,14 @@ namespace MediaBrowser.Controller.Entities /// </summary> public abstract class BaseItem : IHasProviderIds { + protected BaseItem() + { + Genres = new List<string>(); + TrailerUrls = new List<string>(); + Studios = new List<string>(); + People = new List<PersonInfo>(); + } + /// <summary> /// The trailer folder name /// </summary> @@ -925,16 +933,10 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Determines if the item is considered new based on user settings /// </summary> - /// <param name="user">The user.</param> /// <returns><c>true</c> if [is recently added] [the specified user]; otherwise, <c>false</c>.</returns> /// <exception cref="System.ArgumentNullException"></exception> - public bool IsRecentlyAdded(User user) + public bool IsRecentlyAdded() { - if (user == null) - { - throw new ArgumentNullException(); - } - return (DateTime.UtcNow - DateCreated).TotalDays < ConfigurationManager.Configuration.RecentItemDays; } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 619c7a12b..b2b465353 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>System.String.</returns> public override string GetUserDataKey() { - return Name; + return "Genre-" + Name; } } } diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index f5570448d..0f2803744 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>System.String.</returns> public override string GetUserDataKey() { - return Name; + return "Person-" + Name; } } diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 06511d959..0dec024f8 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>System.String.</returns> public override string GetUserDataKey() { - return Name; + return "Studio-" + Name; } } } diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 1e4e6cb06..307ce306b 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities /// <returns>System.String.</returns> public override string GetUserDataKey() { - return Name; + return "Year-" + Name; } } } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 86fd25e66..060f52e37 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -213,5 +213,13 @@ namespace MediaBrowser.Controller.Library /// <param name="parent">The parent.</param> /// <returns>IEnumerable{BaseItem}.</returns> IEnumerable<BaseItem> RetrieveChildren(Folder parent); + + /// <summary> + /// Validates the artists. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + Task ValidateArtists(CancellationToken cancellationToken, IProgress<double> progress); } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 926639d0c..09a59286e 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -53,8 +53,12 @@ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet> </PropertyGroup> <ItemGroup> + <Reference Include="MoreLinq"> + <HintPath>..\packages\morelinq.1.0.15631-beta\lib\net35\MoreLinq.dll</HintPath> + </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> + <Reference Include="System.Data" /> <Reference Include="System.Drawing" /> <Reference Include="System.Net" /> <Reference Include="System.Runtime.Serialization" /> @@ -111,9 +115,11 @@ <Compile Include="Providers\IProviderManager.cs" /> <Compile Include="Providers\MediaInfo\MediaEncoderHelpers.cs" /> <Compile Include="Providers\MetadataProviderPriority.cs" /> + <Compile Include="Providers\Music\FanArtArtistByNameProvider.cs" /> <Compile Include="Providers\Music\LastfmAlbumProvider.cs" /> <Compile Include="Providers\Music\FanArtAlbumProvider.cs" /> <Compile Include="Providers\Music\FanArtArtistProvider.cs" /> + <Compile Include="Providers\Music\LastfmArtistByNameProvider.cs" /> <Compile Include="Providers\Music\LastfmArtistProvider.cs" /> <Compile Include="Providers\Music\LastfmHelper.cs" /> <Compile Include="Providers\Music\MusicArtistProviderFromJson.cs" /> @@ -197,6 +203,9 @@ <Name>MediaBrowser.Model</Name> </ProjectReference> </ItemGroup> + <ItemGroup> + <None Include="packages.config" /> + </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <PropertyGroup> <PostBuildEvent>if $(ConfigurationName) == Release ( diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index cb7237a9d..fa10c8585 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -14,11 +14,12 @@ namespace MediaBrowser.Controller.Providers /// <param name="item">The item.</param> /// <param name="source">The source.</param> /// <param name="targetName">Name of the target.</param> + /// <param name="saveLocally">if set to <c>true</c> [save locally].</param> /// <param name="resourcePool">The resource pool.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.String}.</returns> /// <exception cref="System.ArgumentNullException">item</exception> - Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, SemaphoreSlim resourcePool, CancellationToken cancellationToken); + Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, bool saveLocally, SemaphoreSlim resourcePool, CancellationToken cancellationToken); /// <summary> /// Saves to library filesystem. diff --git a/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs b/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs index 94fe38680..b6155f612 100644 --- a/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs +++ b/MediaBrowser.Controller/Providers/Movies/FanArtMovieProvider.cs @@ -150,7 +150,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting ClearLogo for " + movie.Name); try { - movie.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(movie, path, LOGO_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(movie, path, LOGO_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -176,7 +176,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting ClearArt for " + movie.Name); try { - movie.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(movie, path, ART_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(movie, path, ART_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -199,7 +199,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting DiscArt for " + movie.Name); try { - movie.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(movie, path, DISC_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(movie, path, DISC_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -223,7 +223,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting Banner for " + movie.Name); try { - movie.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(movie, path, BANNER_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(movie, path, BANNER_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -247,7 +247,7 @@ namespace MediaBrowser.Controller.Providers.Movies Logger.Debug("FanArtProvider getting Banner for " + movie.Name); try { - movie.SetImage(ImageType.Thumb, await _providerManager.DownloadAndSaveImage(movie, path, THUMB_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + movie.SetImage(ImageType.Thumb, await _providerManager.DownloadAndSaveImage(movie, path, THUMB_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { diff --git a/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs index c086aab77..69023c339 100644 --- a/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Controller/Providers/Movies/MovieDbProvider.cs @@ -253,7 +253,7 @@ namespace MediaBrowser.Controller.Providers.Movies new Regex(@"(?<name>.*)") // last resort matches the whole string as the name }; - public const string LOCAL_META_FILE_NAME = "MBMovie.json"; + public const string LOCAL_META_FILE_NAME = "mbmovie.js"; public const string ALT_META_FILE_NAME = "movie.xml"; protected string ItemType = "movie"; @@ -270,7 +270,7 @@ namespace MediaBrowser.Controller.Providers.Movies } - if (providerInfo.LastRefreshStatus == ProviderRefreshStatus.CompletedWithErrors) + if (providerInfo.LastRefreshStatus != ProviderRefreshStatus.Success) { Logger.Debug("MovieProvider for {0} - last attempt had errors. Will try again.", item.Path); return true; @@ -283,9 +283,6 @@ namespace MediaBrowser.Controller.Providers.Movies return false; } - if (DateTime.Today.Subtract(item.DateCreated).TotalDays > 180 && downloadDate != DateTime.MinValue) - return false; // don't trigger a refresh data for item that are more than 6 months old and have been refreshed before - if (DateTime.Today.Subtract(downloadDate).TotalDays < ConfigurationManager.Configuration.MetadataRefreshDays) // only refresh every n days return false; @@ -1028,7 +1025,7 @@ namespace MediaBrowser.Controller.Providers.Movies { try { - item.PrimaryImagePath = await ProviderManager.DownloadAndSaveImage(item, tmdbImageUrl + poster.file_path, "folder" + Path.GetExtension(poster.file_path), MovieDbResourcePool, cancellationToken).ConfigureAwait(false); + item.PrimaryImagePath = await ProviderManager.DownloadAndSaveImage(item, tmdbImageUrl + poster.file_path, "folder" + Path.GetExtension(poster.file_path), ConfigurationManager.Configuration.SaveLocalMeta, MovieDbResourcePool, cancellationToken).ConfigureAwait(false); } catch (HttpException) { @@ -1060,7 +1057,7 @@ namespace MediaBrowser.Controller.Providers.Movies { try { - item.BackdropImagePaths.Add(await ProviderManager.DownloadAndSaveImage(item, tmdbImageUrl + images.backdrops[i].file_path, bdName + Path.GetExtension(images.backdrops[i].file_path), MovieDbResourcePool, cancellationToken).ConfigureAwait(false)); + item.BackdropImagePaths.Add(await ProviderManager.DownloadAndSaveImage(item, tmdbImageUrl + images.backdrops[i].file_path, bdName + Path.GetExtension(images.backdrops[i].file_path), ConfigurationManager.Configuration.SaveLocalMeta, MovieDbResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { diff --git a/MediaBrowser.Controller/Providers/Movies/TmdbPersonProvider.cs b/MediaBrowser.Controller/Providers/Movies/TmdbPersonProvider.cs index 583e0bb97..affd4757a 100644 --- a/MediaBrowser.Controller/Providers/Movies/TmdbPersonProvider.cs +++ b/MediaBrowser.Controller/Providers/Movies/TmdbPersonProvider.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Providers.Movies /// <summary> /// The meta file name /// </summary> - protected const string MetaFileName = "MBPerson.json"; + protected const string MetaFileName = "mbperson.js"; protected readonly IProviderManager ProviderManager; diff --git a/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs index f850722d1..4d7f78413 100644 --- a/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/FanArtAlbumProvider.cs @@ -1,29 +1,34 @@ -using System.Collections.Generic; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; using System; +using System.IO; using System.Threading; using System.Threading.Tasks; +using System.Xml; namespace MediaBrowser.Controller.Providers.Music { public class FanArtAlbumProvider : FanartBaseProvider { private readonly IProviderManager _providerManager; - - public FanArtAlbumProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) + + protected IHttpClient HttpClient { get; private set; } + + public FanArtAlbumProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) : base(logManager, configurationManager) { _providerManager = providerManager; + HttpClient = httpClient; } public override bool Supports(BaseItem item) { - return item is MusicAlbum && item.Parent is MusicArtist; + return item is MusicAlbum; } /// <summary> @@ -35,7 +40,7 @@ namespace MediaBrowser.Controller.Providers.Music protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) { //we fetch if image needed and haven't already tried recently - return string.IsNullOrEmpty(item.PrimaryImagePath) && + return (string.IsNullOrEmpty(item.PrimaryImagePath) || !item.HasImage(ImageType.Disc)) && DateTime.Today.Subtract(providerInfo.LastRefreshed).TotalDays > ConfigurationManager.Configuration.MetadataRefreshDays; } @@ -45,46 +50,81 @@ namespace MediaBrowser.Controller.Providers.Music if (mbid == null) { Logger.Warn("No Musicbrainz id associated with album {0}", item.Name); - SetLastRefreshed(item, DateTime.UtcNow, ProviderRefreshStatus.CompletedWithErrors); - return false; + SetLastRefreshed(item, DateTime.UtcNow); + return true; } cancellationToken.ThrowIfCancellationRequested(); - //Look at our parent for our album cover - var artist = (MusicArtist)item.Parent; + var url = string.Format("http://api.fanart.tv/webservice/album/{0}/{1}/xml/all/1/1", APIKey, item.GetProviderId(MetadataProviders.Musicbrainz)); - var cover = artist.AlbumCovers != null ? GetValueOrDefault(artist.AlbumCovers, mbid, null) : null; + var doc = new XmlDocument(); - if (cover == null) + try + { + using (var xml = await HttpClient.Get(url, FanArtResourcePool, cancellationToken).ConfigureAwait(false)) + { + doc.Load(xml); + } + } + catch (HttpException) { - Logger.Warn("Unable to find cover art for {0}", item.Name); - SetLastRefreshed(item, DateTime.UtcNow, ProviderRefreshStatus.CompletedWithErrors); - return false; } - item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, cover, "folder.jpg", FanArtResourcePool, cancellationToken).ConfigureAwait(false)); - return true; - } + cancellationToken.ThrowIfCancellationRequested(); - /// <summary> - /// Helper method for Dictionaries since they throw on not-found keys - /// </summary> - /// <typeparam name="T"></typeparam> - /// <typeparam name="U"></typeparam> - /// <param name="dictionary">The dictionary.</param> - /// <param name="key">The key.</param> - /// <param name="defaultValue">The default value.</param> - /// <returns>``1.</returns> - private static U GetValueOrDefault<T, U>(Dictionary<T, U> dictionary, T key, U defaultValue) - { - U val; - if (!dictionary.TryGetValue(key, out val)) + if (doc.HasChildNodes) { - val = defaultValue; + if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Disc && !item.ResolveArgs.ContainsMetaFileByName(DISC_FILE)) + { + var node = doc.SelectSingleNode("//fanart/music/albums/album//cdart/@url"); + + var path = node != null ? node.Value : null; + + if (!string.IsNullOrEmpty(path)) + { + Logger.Debug("FanArtProvider getting Disc for " + item.Name); + try + { + item.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(item, path, DISC_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + } + catch (HttpException) + { + } + catch (IOException) + { + + } + } + } + + if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary && !item.ResolveArgs.ContainsMetaFileByName(PRIMARY_FILE)) + { + var node = doc.SelectSingleNode("//fanart/music/albums/album//albumcover/@url"); + + var path = node != null ? node.Value : null; + + if (!string.IsNullOrEmpty(path)) + { + Logger.Debug("FanArtProvider getting albumcover for " + item.Name); + try + { + item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, path, PRIMARY_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + } + catch (HttpException) + { + } + catch (IOException) + { + + } + } + } } - return val; + SetLastRefreshed(item, DateTime.UtcNow); + + return true; } } } diff --git a/MediaBrowser.Controller/Providers/Music/FanArtArtistByNameProvider.cs b/MediaBrowser.Controller/Providers/Music/FanArtArtistByNameProvider.cs new file mode 100644 index 000000000..58200a458 --- /dev/null +++ b/MediaBrowser.Controller/Providers/Music/FanArtArtistByNameProvider.cs @@ -0,0 +1,48 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Controller.Providers.Music +{ + /// <summary> + /// Class FanArtArtistByNameProvider + /// </summary> + public class FanArtArtistByNameProvider : FanArtArtistProvider + { + /// <summary> + /// Initializes a new instance of the <see cref="FanArtArtistByNameProvider" /> class. + /// </summary> + /// <param name="httpClient">The HTTP client.</param> + /// <param name="logManager">The log manager.</param> + /// <param name="configurationManager">The configuration manager.</param> + /// <param name="providerManager">The provider manager.</param> + public FanArtArtistByNameProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) + : base(httpClient, logManager, configurationManager, providerManager) + { + } + + /// <summary> + /// Supportses the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + public override bool Supports(BaseItem item) + { + return item is Artist; + } + + /// <summary> + /// Gets a value indicating whether [save local meta]. + /// </summary> + /// <value><c>true</c> if [save local meta]; otherwise, <c>false</c>.</value> + protected override bool SaveLocalMeta + { + get + { + return true; + } + } + } +} diff --git a/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs index 1dd5c7cd2..ec99a78a7 100644 --- a/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/FanArtArtistProvider.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Providers.Music /// <summary> /// Class FanArtArtistProvider /// </summary> - class FanArtArtistProvider : FanartBaseProvider + public class FanArtArtistProvider : FanartBaseProvider { /// <summary> /// Gets the HTTP client. @@ -54,6 +54,11 @@ namespace MediaBrowser.Controller.Providers.Music return item is MusicArtist; } + protected virtual bool SaveLocalMeta + { + get { return ConfigurationManager.Configuration.SaveLocalMeta; } + } + /// <summary> /// Shoulds the fetch. /// </summary> @@ -62,16 +67,11 @@ namespace MediaBrowser.Controller.Providers.Music /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> protected override bool ShouldFetch(BaseItem item, BaseProviderInfo providerInfo) { - var artist = (MusicArtist)item; - if (item.Path == null || item.DontFetchMeta || string.IsNullOrEmpty(artist.GetProviderId(MetadataProviders.Musicbrainz))) return false; //nothing to do - var artExists = item.ResolveArgs.ContainsMetaFileByName(ART_FILE); - var logoExists = item.ResolveArgs.ContainsMetaFileByName(LOGO_FILE); - var discExists = item.ResolveArgs.ContainsMetaFileByName(DISC_FILE); + if (item.Path == null || item.DontFetchMeta || string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Musicbrainz))) return false; //nothing to do - return (!artExists && ConfigurationManager.Configuration.DownloadMusicArtistImages.Art) - || (!logoExists && ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo) - || (!discExists && ConfigurationManager.Configuration.DownloadMusicArtistImages.Disc) - || ((artist.AlbumCovers == null || artist.AlbumCovers.Count == 0) && ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary); + return (!item.ResolveArgs.ContainsMetaFileByName(ART_FILE) && ConfigurationManager.Configuration.DownloadMusicArtistImages.Art) + || (!item.ResolveArgs.ContainsMetaFileByName(LOGO_FILE) && ConfigurationManager.Configuration.DownloadMusicArtistImages.Logo) + || (!item.ResolveArgs.ContainsMetaFileByName(DISC_FILE) && ConfigurationManager.Configuration.DownloadMusicArtistImages.Disc); } /// <summary> @@ -85,7 +85,7 @@ namespace MediaBrowser.Controller.Providers.Music { cancellationToken.ThrowIfCancellationRequested(); - var artist = (MusicArtist)item; + //var artist = item; BaseProviderInfo providerData; @@ -94,9 +94,9 @@ namespace MediaBrowser.Controller.Providers.Music providerData = new BaseProviderInfo(); } - if (ShouldFetch(artist, providerData)) + if (ShouldFetch(item, providerData)) { - var url = string.Format(FanArtBaseUrl, APIKey, artist.GetProviderId(MetadataProviders.Musicbrainz)); + var url = string.Format(FanArtBaseUrl, APIKey, item.GetProviderId(MetadataProviders.Musicbrainz)); var doc = new XmlDocument(); try @@ -124,10 +124,10 @@ namespace MediaBrowser.Controller.Providers.Music path = node != null ? node.Value : null; if (!string.IsNullOrEmpty(path)) { - Logger.Debug("FanArtProvider getting ClearLogo for " + artist.Name); + Logger.Debug("FanArtProvider getting ClearLogo for " + item.Name); try { - artist.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(artist, path, LOGO_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + item.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(item, path, LOGO_FILE, SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -146,16 +146,16 @@ namespace MediaBrowser.Controller.Providers.Music if (nodes != null) { var numBackdrops = 0; - artist.BackdropImagePaths = new List<string>(); + item.BackdropImagePaths = new List<string>(); foreach (XmlNode node in nodes) { path = node.Value; if (!string.IsNullOrEmpty(path)) { - Logger.Debug("FanArtProvider getting Backdrop for " + artist.Name); + Logger.Debug("FanArtProvider getting Backdrop for " + item.Name); try { - artist.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(artist, path, ("Backdrop" + (numBackdrops > 0 ? numBackdrops.ToString() : "") + ".jpg"), FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + item.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(item, path, ("Backdrop" + (numBackdrops > 0 ? numBackdrops.ToString() : "") + ".jpg"), SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); numBackdrops++; if (numBackdrops >= ConfigurationManager.Configuration.MaxBackdrops) break; } @@ -175,32 +175,6 @@ namespace MediaBrowser.Controller.Providers.Music cancellationToken.ThrowIfCancellationRequested(); - if (ConfigurationManager.Configuration.DownloadMusicAlbumImages.Primary) - { - var nodes = doc.SelectNodes("//fanart/music/albums/*"); - if (nodes != null) - { - artist.AlbumCovers = new Dictionary<string, string>(); - foreach (XmlNode node in nodes) - { - - var key = node.Attributes["id"] != null ? node.Attributes["id"].Value : null; - var cover = node.SelectSingleNode("albumcover/@url"); - path = cover != null ? cover.Value : null; - - if (!string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(key)) - { - Logger.Debug("FanArtProvider getting Album Cover for " + artist.Name); - artist.AlbumCovers[key] = path; - } - } - - } - - } - - cancellationToken.ThrowIfCancellationRequested(); - if (ConfigurationManager.Configuration.DownloadMusicArtistImages.Art && !item.ResolveArgs.ContainsMetaFileByName(ART_FILE)) { var node = @@ -209,10 +183,10 @@ namespace MediaBrowser.Controller.Providers.Music path = node != null ? node.Value : null; if (!string.IsNullOrEmpty(path)) { - Logger.Debug("FanArtProvider getting ClearArt for " + artist.Name); + Logger.Debug("FanArtProvider getting ClearArt for " + item.Name); try { - artist.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(artist, path, ART_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + item.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(item, path, ART_FILE, SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -232,10 +206,10 @@ namespace MediaBrowser.Controller.Providers.Music path = node != null ? node.Value : null; if (!string.IsNullOrEmpty(path)) { - Logger.Debug("FanArtProvider getting Banner for " + artist.Name); + Logger.Debug("FanArtProvider getting Banner for " + item.Name); try { - artist.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(artist, path, BANNER_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + item.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(item, path, BANNER_FILE, SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -256,10 +230,10 @@ namespace MediaBrowser.Controller.Providers.Music path = node != null ? node.Value : null; if (!string.IsNullOrEmpty(path)) { - Logger.Debug("FanArtProvider getting Primary image for " + artist.Name); + Logger.Debug("FanArtProvider getting Primary image for " + item.Name); try { - artist.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(artist, path, PRIMARY_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, path, PRIMARY_FILE, SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -272,7 +246,7 @@ namespace MediaBrowser.Controller.Providers.Music } } } - SetLastRefreshed(artist, DateTime.UtcNow); + SetLastRefreshed(item, DateTime.UtcNow); return true; } } diff --git a/MediaBrowser.Controller/Providers/Music/LastfmAlbumProvider.cs b/MediaBrowser.Controller/Providers/Music/LastfmAlbumProvider.cs index 697a6604c..b48999176 100644 --- a/MediaBrowser.Controller/Providers/Music/LastfmAlbumProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/LastfmAlbumProvider.cs @@ -1,14 +1,15 @@ -using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; +using MoreLinq; +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Controller.Providers.Music { @@ -33,26 +34,7 @@ namespace MediaBrowser.Controller.Providers.Music protected override async Task FetchLastfmData(BaseItem item, string id, CancellationToken cancellationToken) { - // Get albu info using artist and album name - var url = RootUrl + string.Format("method=album.getInfo&artist={0}&album={1}&api_key={2}&format=json", UrlEncode(item.Parent.Name), UrlEncode(item.Name), ApiKey); - - LastfmGetAlbumResult result; - - try - { - using (var json = await HttpClient.Get(url, LastfmResourcePool, cancellationToken).ConfigureAwait(false)) - { - result = JsonSerializer.DeserializeFromStream<LastfmGetAlbumResult>(json); - } - } - catch (HttpException e) - { - if (e.StatusCode == HttpStatusCode.NotFound) - { - throw new LastfmProviderException(string.Format("Unable to retrieve album info for {0} with artist {1}", item.Name, item.Parent.Name)); - } - throw; - } + var result = await GetAlbumResult(item, cancellationToken).ConfigureAwait(false); if (result != null && result.album != null) { @@ -71,9 +53,60 @@ namespace MediaBrowser.Controller.Providers.Music } } + private async Task<LastfmGetAlbumResult> GetAlbumResult(BaseItem item, CancellationToken cancellationToken) + { + var result = await GetAlbumResult(item.Parent.Name, item.Name, cancellationToken); + + if (result != null && result.album != null) + { + return result; + } + + var folder = (Folder)item; + + // 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(); + + foreach (var song in songs.Where(song => !string.IsNullOrEmpty(song.Album) && !string.IsNullOrEmpty(song.AlbumArtist))) + { + result = await GetAlbumResult(song.AlbumArtist, song.Album, cancellationToken).ConfigureAwait(false); + + if (result != null && result.album != null) + { + return result; + } + } + + return null; + } + + private async Task<LastfmGetAlbumResult> GetAlbumResult(string artist, string album, CancellationToken cancellationToken) + { + // Get albu info using artist and album name + var url = RootUrl + string.Format("method=album.getInfo&artist={0}&album={1}&api_key={2}&format=json", UrlEncode(artist), UrlEncode(album), ApiKey); + + using (var json = await HttpClient.Get(url, LastfmResourcePool, cancellationToken).ConfigureAwait(false)) + { + return JsonSerializer.DeserializeFromStream<LastfmGetAlbumResult>(json); + } + } + + protected override Task FetchData(BaseItem item, CancellationToken cancellationToken) + { + return FetchLastfmData(item, string.Empty, cancellationToken); + } + public override bool Supports(BaseItem item) { return item is MusicAlbum; } + + protected override bool RefreshOnFileSystemStampChange + { + get + { + return true; + } + } } } diff --git a/MediaBrowser.Controller/Providers/Music/LastfmArtistByNameProvider.cs b/MediaBrowser.Controller/Providers/Music/LastfmArtistByNameProvider.cs new file mode 100644 index 000000000..f9ec2cc74 --- /dev/null +++ b/MediaBrowser.Controller/Providers/Music/LastfmArtistByNameProvider.cs @@ -0,0 +1,50 @@ +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; + +namespace MediaBrowser.Controller.Providers.Music +{ + /// <summary> + /// Class LastfmArtistByNameProvider + /// </summary> + public class LastfmArtistByNameProvider : LastfmArtistProvider + { + /// <summary> + /// Initializes a new instance of the <see cref="LastfmArtistByNameProvider"/> class. + /// </summary> + /// <param name="jsonSerializer">The json serializer.</param> + /// <param name="httpClient">The HTTP client.</param> + /// <param name="logManager">The log manager.</param> + /// <param name="configurationManager">The configuration manager.</param> + /// <param name="providerManager">The provider manager.</param> + public LastfmArtistByNameProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager) + : base(jsonSerializer, httpClient, logManager, configurationManager, providerManager) + { + } + + /// <summary> + /// Gets a value indicating whether [save local meta]. + /// </summary> + /// <value><c>true</c> if [save local meta]; otherwise, <c>false</c>.</value> + protected override bool SaveLocalMeta + { + get + { + return true; + } + } + + /// <summary> + /// Supportses the specified item. + /// </summary> + /// <param name="item">The item.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + public override bool Supports(BaseItem item) + { + return item is Artist; + } + } +} diff --git a/MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs b/MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs index 8a17bcbf6..165b996f6 100644 --- a/MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/LastfmArtistProvider.cs @@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Providers.Music //Execute the Artist search against our name and assume first one is the one we want var url = RootUrl + string.Format("method=artist.search&artist={0}&api_key={1}&format=json", UrlEncode(item.Name), ApiKey); - LastfmArtistSearchResults searchResult = null; + LastfmArtistSearchResults searchResult; try { @@ -60,29 +60,18 @@ namespace MediaBrowser.Controller.Providers.Music // Get artist info with provided id var url = RootUrl + string.Format("method=artist.getInfo&mbid={0}&api_key={1}&format=json", UrlEncode(id), ApiKey); - LastfmGetArtistResult result = null; + LastfmGetArtistResult result; - try + using (var json = await HttpClient.Get(url, LastfmResourcePool, cancellationToken).ConfigureAwait(false)) { - using (var json = await HttpClient.Get(url, LastfmResourcePool, cancellationToken).ConfigureAwait(false)) - { - result = JsonSerializer.DeserializeFromStream<LastfmGetArtistResult>(json); - } - } - catch (HttpException e) - { - if (e.StatusCode == HttpStatusCode.NotFound) - { - throw new LastfmProviderException(string.Format("Unable to retrieve artist info for {0} with id {1}", item.Name, id)); - } - throw; + result = JsonSerializer.DeserializeFromStream<LastfmGetArtistResult>(json); } if (result != null && result.artist != null) { LastfmHelper.ProcessArtistData(item, result.artist); //And save locally if indicated - if (ConfigurationManager.Configuration.SaveLocalMeta) + if (SaveLocalMeta) { var ms = new MemoryStream(); JsonSerializer.SerializeToStream(result.artist, ms); diff --git a/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs b/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs index f7e0eef48..dc586cb51 100644 --- a/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs +++ b/MediaBrowser.Controller/Providers/Music/LastfmBaseProvider.cs @@ -1,26 +1,17 @@ -using System.Collections.Generic; -using System.Net; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; using System; +using System.Collections.Generic; +using System.Net; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Providers.Music { - class LastfmProviderException : ApplicationException - { - public LastfmProviderException(string msg) - : base(msg) - { - } - - } /// <summary> /// Class MovieDbProvider /// </summary> @@ -84,6 +75,14 @@ namespace MediaBrowser.Controller.Providers.Music /// </summary> protected string LocalMetaFileName { get; set; } + protected virtual bool SaveLocalMeta + { + get + { + return ConfigurationManager.Configuration.SaveLocalMeta; + } + } + /// <summary> /// If we save locally, refresh if they delete something /// </summary> @@ -91,7 +90,7 @@ namespace MediaBrowser.Controller.Providers.Music { get { - return ConfigurationManager.Configuration.SaveLocalMeta; + return SaveLocalMeta; } } @@ -173,16 +172,15 @@ namespace MediaBrowser.Controller.Providers.Music { if (item.DontFetchMeta) return false; - if (ConfigurationManager.Configuration.SaveLocalMeta && HasFileSystemStampChanged(item, providerInfo)) + if (RefreshOnFileSystemStampChange && HasFileSystemStampChanged(item, providerInfo)) { //If they deleted something from file system, chances are, this item was mis-identified the first time item.SetProviderId(MetadataProviders.Musicbrainz, null); Logger.Debug("LastfmProvider reports file system stamp change..."); return true; - } - if (providerInfo.LastRefreshStatus == ProviderRefreshStatus.CompletedWithErrors) + if (providerInfo.LastRefreshStatus != ProviderRefreshStatus.Success) { Logger.Debug("LastfmProvider for {0} - last attempt had errors. Will try again.", item.Path); return true; @@ -194,22 +192,10 @@ namespace MediaBrowser.Controller.Providers.Music return true; } - var downloadDate = providerInfo.LastRefreshed; - - if (ConfigurationManager.Configuration.MetadataRefreshDays == -1 && downloadDate != DateTime.MinValue) - { - return false; - } - - if (DateTime.Today.Subtract(item.DateCreated).TotalDays > 180 && downloadDate != DateTime.MinValue) - return false; // don't trigger a refresh data for item that are more than 6 months old and have been refreshed before - - if (DateTime.Today.Subtract(downloadDate).TotalDays < ConfigurationManager.Configuration.MetadataRefreshDays) // only refresh every n days - return false; - + if (DateTime.UtcNow.Subtract(providerInfo.LastRefreshed).TotalDays > ConfigurationManager.Configuration.MetadataRefreshDays) // only refresh every n days + return true; - Logger.Debug("LastfmProvider - " + item.Name + " needs refresh. Download date: " + downloadDate + " item created date: " + item.DateCreated + " Check for Update age: " + ConfigurationManager.Configuration.MetadataRefreshDays); - return true; + return false; } /// <summary> @@ -221,36 +207,9 @@ namespace MediaBrowser.Controller.Providers.Music /// <returns>Task{System.Boolean}.</returns> public override async Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) { - if (item.DontFetchMeta) - { - Logger.Info("LastfmProvider - Not fetching because requested to ignore " + item.Name); - return false; - } - cancellationToken.ThrowIfCancellationRequested(); - BaseProviderInfo providerData; - - if (!item.ProviderData.TryGetValue(Id, out providerData)) - { - providerData = new BaseProviderInfo(); - } - - if (!ConfigurationManager.Configuration.SaveLocalMeta || !HasLocalMeta(item) || (force && !HasLocalMeta(item)) || (RefreshOnVersionChange && providerData.ProviderVersion != ProviderVersion)) - { - try - { - await FetchData(item, cancellationToken).ConfigureAwait(false); - SetLastRefreshed(item, DateTime.UtcNow); - } - catch (LastfmProviderException) - { - SetLastRefreshed(item, DateTime.UtcNow, ProviderRefreshStatus.CompletedWithErrors); - } - - return true; - } - Logger.Debug("LastfmProvider not fetching because local meta exists for " + item.Name); + await FetchData(item, cancellationToken).ConfigureAwait(false); SetLastRefreshed(item, DateTime.UtcNow); return true; } diff --git a/MediaBrowser.Controller/Providers/Music/LastfmHelper.cs b/MediaBrowser.Controller/Providers/Music/LastfmHelper.cs index 842d10e4d..442dd4b69 100644 --- a/MediaBrowser.Controller/Providers/Music/LastfmHelper.cs +++ b/MediaBrowser.Controller/Providers/Music/LastfmHelper.cs @@ -6,20 +6,23 @@ namespace MediaBrowser.Controller.Providers.Music { public static class LastfmHelper { - public static string LocalArtistMetaFileName = "MBArtist.json"; - public static string LocalAlbumMetaFileName = "MBAlbum.json"; + public static string LocalArtistMetaFileName = "mbartist.js"; + public static string LocalAlbumMetaFileName = "mbalbum.js"; public static void ProcessArtistData(BaseItem artist, LastfmArtist data) { - var overview = data.bio != null ? data.bio.content : null; - - artist.Overview = overview; - var yearFormed = 0; if (data.bio != null) { Int32.TryParse(data.bio.yearformed, out yearFormed); + + artist.Overview = data.bio.content; + + if (!string.IsNullOrEmpty(data.bio.placeformed)) + { + artist.AddProductionLocation(data.bio.placeformed); + } } artist.PremiereDate = yearFormed > 0 ? new DateTime(yearFormed, 1,1) : DateTime.MinValue; @@ -52,7 +55,10 @@ namespace MediaBrowser.Controller.Providers.Music { foreach (var tag in tags.tag) { - item.AddGenre(tag.name); + if (!string.IsNullOrEmpty(tag.name)) + { + item.AddGenre(tag.name); + } } } } diff --git a/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs b/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs index a7fc4586f..cdbfb0883 100644 --- a/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/FanArtTVProvider.cs @@ -100,7 +100,7 @@ namespace MediaBrowser.Controller.Providers.TV Logger.Debug("FanArtProvider getting ClearLogo for " + series.Name); try { - series.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(series, path, LOGO_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + series.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(series, path, LOGO_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -124,7 +124,7 @@ namespace MediaBrowser.Controller.Providers.TV Logger.Debug("FanArtProvider getting ClearArt for " + series.Name); try { - series.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(series, path, ART_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + series.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(series, path, ART_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -148,7 +148,7 @@ namespace MediaBrowser.Controller.Providers.TV Logger.Debug("FanArtProvider getting ThumbArt for " + series.Name); try { - series.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(series, path, THUMB_FILE, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); + series.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(series, path, THUMB_FILE, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { diff --git a/MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs b/MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs index b5c6df7ec..74cb1bfd4 100644 --- a/MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/RemoteEpisodeProvider.cs @@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Providers.TV try { - episode.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken); + episode.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken); } catch (HttpException) { diff --git a/MediaBrowser.Controller/Providers/TV/RemoteSeasonProvider.cs b/MediaBrowser.Controller/Providers/TV/RemoteSeasonProvider.cs index f39e91834..9d1a7c256 100644 --- a/MediaBrowser.Controller/Providers/TV/RemoteSeasonProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/RemoteSeasonProvider.cs @@ -177,7 +177,7 @@ namespace MediaBrowser.Controller.Providers.TV try { if (n != null) - season.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false); + season.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false); } catch (HttpException) { @@ -204,7 +204,7 @@ namespace MediaBrowser.Controller.Providers.TV TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), - RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken). + ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken). ConfigureAwait(false); season.SetImage(ImageType.Banner, bannerImagePath); @@ -231,7 +231,7 @@ namespace MediaBrowser.Controller.Providers.TV try { if (season.BackdropImagePaths == null) season.BackdropImagePaths = new List<string>(); - season.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "backdrop" + Path.GetExtension(n.InnerText), RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false)); + season.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "backdrop" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { @@ -265,7 +265,7 @@ namespace MediaBrowser.Controller.Providers.TV "backdrop" + Path.GetExtension( n.InnerText), - RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken) + ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken) .ConfigureAwait(false)); } catch (HttpException) diff --git a/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs b/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs index 89cb89289..5a68981bf 100644 --- a/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs +++ b/MediaBrowser.Controller/Providers/TV/RemoteSeriesProvider.cs @@ -228,7 +228,7 @@ namespace MediaBrowser.Controller.Providers.TV string n = doc.SafeGetString("//banner"); if (!string.IsNullOrWhiteSpace(n)) { - series.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n, "banner" + Path.GetExtension(n), TvDbResourcePool, cancellationToken).ConfigureAwait(false)); + series.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n, "banner" + Path.GetExtension(n), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false)); } string s = doc.SafeGetString("//Network"); @@ -369,7 +369,7 @@ namespace MediaBrowser.Controller.Providers.TV { try { - series.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), TvDbResourcePool, cancellationToken).ConfigureAwait(false); + series.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false); } catch (HttpException) { @@ -392,7 +392,7 @@ namespace MediaBrowser.Controller.Providers.TV { try { - var bannerImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), TvDbResourcePool, cancellationToken); + var bannerImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken); series.SetImage(ImageType.Banner, bannerImagePath); } @@ -421,7 +421,7 @@ namespace MediaBrowser.Controller.Providers.TV { try { - series.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), TvDbResourcePool, cancellationToken).ConfigureAwait(false)); + series.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, TvDbResourcePool, cancellationToken).ConfigureAwait(false)); } catch (HttpException) { diff --git a/MediaBrowser.Controller/packages.config b/MediaBrowser.Controller/packages.config new file mode 100644 index 000000000..44996f0e8 --- /dev/null +++ b/MediaBrowser.Controller/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<packages> + <package id="morelinq" version="1.0.15631-beta" targetFramework="net45" /> +</packages>
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 4d66d1422..692176efc 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Progress; using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -103,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.Library private readonly IUserManager _userManager; private readonly IUserDataRepository _userDataRepository; - + /// <summary> /// Gets or sets the configuration manager. /// </summary> @@ -244,7 +245,7 @@ namespace MediaBrowser.Server.Implementations.Library { // Any number of configuration settings could change the way the library is refreshed, so do that now _taskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>(); - + if (refreshPeopleAfterUpdate) { _taskManager.CancelIfRunningAndQueue<PeopleValidationTask>(); @@ -285,7 +286,7 @@ namespace MediaBrowser.Server.Implementations.Library items.AddRange(userFolders); - return new ConcurrentDictionary<Guid,BaseItem>(items.ToDictionary(i => i.Id)); + return new ConcurrentDictionary<Guid, BaseItem>(items.ToDictionary(i => i.Id)); } /// <summary> @@ -505,7 +506,7 @@ namespace MediaBrowser.Server.Implementations.Library { return _userRootFolders.GetOrAdd(userRootPath, key => RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder))) as UserRootFolder ?? (UserRootFolder)ResolvePath(userRootPath)); } - + /// <summary> /// Gets a Person /// </summary> @@ -560,7 +561,20 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Genre}.</returns> public Task<Artist> GetArtist(string name, bool allowSlowProviders = false) { - return GetImagesByNameItem<Artist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name, CancellationToken.None, allowSlowProviders); + return GetArtist(name, CancellationToken.None, allowSlowProviders); + } + + /// <summary> + /// Gets the artist. + /// </summary> + /// <param name="name">The name.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param> + /// <param name="forceCreation">if set to <c>true</c> [force creation].</param> + /// <returns>Task{Artist}.</returns> + private Task<Artist> GetArtist(string name, CancellationToken cancellationToken, bool allowSlowProviders = false, bool forceCreation = false) + { + return GetImagesByNameItem<Artist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name, cancellationToken, allowSlowProviders, forceCreation); } /// <summary> @@ -764,6 +778,76 @@ namespace MediaBrowser.Server.Implementations.Library _logger.Info("People validation complete"); } + public async Task ValidateArtists(CancellationToken cancellationToken, IProgress<double> progress) + { + const int maxTasks = 25; + + var tasks = new List<Task>(); + + var artists = RootFolder.RecursiveChildren + .OfType<Audio>() + .SelectMany(c => + { + var list = c.Artists.ToList(); + + if (!string.IsNullOrEmpty(c.AlbumArtist)) + { + list.Add(c.AlbumArtist); + } + + return list; + }) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + var numComplete = 0; + + foreach (var artist in artists) + { + if (tasks.Count > maxTasks) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + tasks.Clear(); + + // Safe cancellation point, when there are no pending tasks + cancellationToken.ThrowIfCancellationRequested(); + } + + // Avoid accessing the foreach variable within the closure + var currentArtist = artist; + + tasks.Add(Task.Run(async () => + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await GetArtist(currentArtist, cancellationToken, true, true).ConfigureAwait(false); + } + catch (IOException ex) + { + _logger.ErrorException("Error validating Artist {0}", ex, currentArtist); + } + + // Update progress + lock (progress) + { + numComplete++; + double percent = numComplete; + percent /= artists.Count; + + progress.Report(100 * percent); + } + })); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + progress.Report(100); + + _logger.Info("Artist validation complete"); + } + /// <summary> /// Reloads the root media folder /// </summary> @@ -796,8 +880,20 @@ namespace MediaBrowser.Server.Implementations.Library await ValidateCollectionFolders(folder, cancellationToken).ConfigureAwait(false); } + var innerProgress = new ActionableProgress<double>(); + + innerProgress.RegisterAction(pct => progress.Report(pct * .8)); + // Now validate the entire media library - await RootFolder.ValidateChildren(progress, cancellationToken, recursive: true).ConfigureAwait(false); + await RootFolder.ValidateChildren(innerProgress, cancellationToken, recursive: true).ConfigureAwait(false); + + innerProgress = new ActionableProgress<double>(); + + innerProgress.RegisterAction(pct => progress.Report(80 + pct * .2)); + + await ValidateArtists(cancellationToken, innerProgress); + + progress.Report(100); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 79aee7ad2..6d278bb18 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -145,6 +145,7 @@ <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Providers\ProviderManager.cs" /> <Compile Include="Reflection\TypeMapper.cs" /> + <Compile Include="ScheduledTasks\ArtistValidationTask.cs" /> <Compile Include="ScheduledTasks\PeopleValidationTask.cs" /> <Compile Include="ScheduledTasks\ChapterImagesTask.cs" /> <Compile Include="ScheduledTasks\ImageCleanupTask.cs" /> diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs index 8c74b8aed..8bdf597a1 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs +++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs @@ -757,8 +757,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder throw new ArgumentNullException("outputPath"); } - var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -filter:v select=\\'eq(pict_type\\,I)\\' -vf \"scale=iw*sar:ih\" -f image2 \"{1}\"", inputPath, outputPath) : - string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"scale=iw*sar:ih\" -f image2 \"{1}\"", inputPath, outputPath); + var args = useIFrame ? string.Format("-i {0} -threads 0 -v quiet -vframes 1 -filter:v select=\\'eq(pict_type\\,I)\\' -vf \"scale=iw*sar:ih, scale=600:-1\" -f image2 \"{1}\"", inputPath, outputPath) : + string.Format("-i {0} -threads 0 -v quiet -vframes 1 -vf \"scale=iw*sar:ih, scale=600:-1\" -f image2 \"{1}\"", inputPath, outputPath); var probeSize = GetProbeSizeArgument(type); diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index e4f57e8d9..4d5123cc5 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -330,11 +330,12 @@ namespace MediaBrowser.Server.Implementations.Providers /// <param name="item">The item.</param> /// <param name="source">The source.</param> /// <param name="targetName">Name of the target.</param> + /// <param name="saveLocally">if set to <c>true</c> [save locally].</param> /// <param name="resourcePool">The resource pool.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{System.String}.</returns> /// <exception cref="System.ArgumentNullException">item</exception> - public async Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + public async Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, bool saveLocally, SemaphoreSlim resourcePool, CancellationToken cancellationToken) { if (item == null) { @@ -354,13 +355,13 @@ namespace MediaBrowser.Server.Implementations.Providers } //download and save locally - var localPath = (ConfigurationManager.Configuration.SaveLocalMeta && item.MetaLocation != null) ? + var localPath = (saveLocally && item.MetaLocation != null) ? Path.Combine(item.MetaLocation, targetName) : _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Path.ToLower(), targetName); var img = await _httpClient.Get(source, resourcePool, cancellationToken).ConfigureAwait(false); - if (ConfigurationManager.Configuration.SaveLocalMeta) // queue to media directories + if (saveLocally) // queue to media directories { await SaveToLibraryFilesystem(item, localPath, img, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ArtistValidationTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ArtistValidationTask.cs new file mode 100644 index 000000000..a67db1b2d --- /dev/null +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/ArtistValidationTask.cs @@ -0,0 +1,81 @@ +using MediaBrowser.Common.ScheduledTasks; +using MediaBrowser.Controller.Library; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.ScheduledTasks +{ + public class ArtistValidationTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="PeopleValidationTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + public ArtistValidationTask(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Creates the triggers that define when the task will run + /// </summary> + /// <returns>IEnumerable{BaseTaskTrigger}.</returns> + public IEnumerable<ITaskTrigger> GetDefaultTriggers() + { + return new ITaskTrigger[] + { + new DailyTrigger { TimeOfDay = TimeSpan.FromHours(5) }, + + new IntervalTrigger{ Interval = TimeSpan.FromHours(12)} + }; + } + + /// <summary> + /// Returns the task to be executed + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + return _libraryManager.ValidateArtists(cancellationToken, progress); + } + + /// <summary> + /// Gets the name of the task + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return "Refresh music artists"; } + } + + /// <summary> + /// Gets the description. + /// </summary> + /// <value>The description.</value> + public string Description + { + get { return "Updates metadata for music artists in your media library."; } + } + + /// <summary> + /// Gets the category. + /// </summary> + /// <value>The category.</value> + public string Category + { + get + { + return "Library"; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs index 6b8113480..ce15f5606 100644 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs +++ b/MediaBrowser.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Server.Implementations.ScheduledTasks /// <value>The description.</value> public string Description { - get { return "Updates metadata for actors, artists and directors in your media library."; } + get { return "Updates metadata for actors and directors in your media library."; } } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs b/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs index 575cd81ba..897ece764 100644 --- a/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs +++ b/MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs @@ -150,6 +150,8 @@ namespace MediaBrowser.Server.Implementations.Sqlite GC.SuppressFinalize(this); } + private readonly object _disposeLock = new object(); + /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> @@ -160,7 +162,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite { try { - lock (this) + lock (_disposeLock) { if (connection != null) { diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 1b22557c8..9f49c83a8 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -449,7 +449,6 @@ namespace MediaBrowser.WebDashboard.Api "addpluginpage.js", "advancedconfigurationpage.js", "advancedmetadataconfigurationpage.js", - "boxset.js", "boxsets.js", "clientsettings.js", "dashboardpage.js", @@ -477,6 +476,8 @@ namespace MediaBrowser.WebDashboard.Api "moviesrecommended.js", "moviestudios.js", "movietrailers.js", + "musicalbums.js", + "musicartists.js", "musicgenres.js", "playlist.js", "plugincatalogpage.js", @@ -488,8 +489,6 @@ namespace MediaBrowser.WebDashboard.Api "supporterpage.js", "tvgenres.js", "tvpeople.js", - "tvseason.js", - "tvseries.js", "tvrecommended.js", "tvshows.js", "tvstudios.js", diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index fe676abaf..759fb0dc4 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -862,13 +862,19 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { /** * Gets a studio */ - self.getStudio = function (name) { + self.getStudio = function (name, userId) { if (!name) { throw new Error("null name"); } - var url = self.getUrl("Studios/" + encodeName(name)); + var options = {}; + + if (userId) { + options.userId = userId; + } + + var url = self.getUrl("Studios/" + encodeName(name), options); return self.ajax({ type: "GET", @@ -880,13 +886,43 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { /** * Gets a genre */ - self.getGenre = function (name) { + self.getGenre = function (name, userId) { if (!name) { throw new Error("null name"); } - var url = self.getUrl("Genres/" + encodeName(name)); + var options = {}; + + if (userId) { + options.userId = userId; + } + + var url = self.getUrl("Genres/" + encodeName(name), options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + /** + * Gets an artist + */ + self.getArtist = function (name, userId) { + + if (!name) { + throw new Error("null name"); + } + + var options = {}; + + if (userId) { + options.userId = userId; + } + + var url = self.getUrl("Artists/" + encodeName(name), options); return self.ajax({ type: "GET", @@ -898,13 +934,19 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { /** * Gets a year */ - self.getYear = function (year) { + self.getYear = function (yea, userId) { - if (!year) { - throw new Error("null year"); + if (!name) { + throw new Error("null name"); + } + + var options = {}; + + if (userId) { + options.userId = userId; } - var url = self.getUrl("Years/" + year); + var url = self.getUrl("Years/" + encodeName(name), options); return self.ajax({ type: "GET", @@ -916,13 +958,19 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { /** * Gets a Person */ - self.getPerson = function (name) { + self.getPerson = function (name, userId) { if (!name) { throw new Error("null name"); } - var url = self.getUrl("Persons/" + encodeName(name)); + var options = {}; + + if (userId) { + options.userId = userId; + } + + var url = self.getUrl("Persons/" + encodeName(name), options); return self.ajax({ type: "GET", @@ -1132,6 +1180,41 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { }; /** + * Constructs a url for a artist image + * @param {String} name + * @param {Object} options + * Options supports the following properties: + * width - download the image at a fixed width + * height - download the image at a fixed height + * maxWidth - download the image at a maxWidth + * maxHeight - download the image at a maxHeight + * quality - A scale of 0-100. This should almost always be omitted as the default will suffice. + * For best results do not specify both width and height together, as aspect ratio might be altered. + */ + self.getArtistImageUrl = function (name, options) { + + if (!name) { + throw new Error("null name"); + } + + options = options || { + + }; + + var url = "Artists/" + encodeName(name) + "/Images/" + options.type; + + if (options.index != null) { + url += "/" + options.index; + } + + // Don't put these on the query string + delete options.type; + delete options.index; + + return self.getUrl(url, options); + }; + + /** * Constructs a url for a studio image * @param {String} name * @param {Object} options @@ -1525,6 +1608,27 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { }; /** + Gets artists from an item + */ + self.getArtists = function (userId, options) { + + if (!userId) { + throw new Error("null userId"); + } + + options = options || {}; + options.userId = userId; + + var url = self.getUrl("Artists", options); + + return self.ajax({ + type: "GET", + url: url, + dataType: "json" + }); + }; + + /** Gets genres from an item */ self.getGenres = function (userId, options) { @@ -1533,12 +1637,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null userId"); } - var parentId = options.parentId || "root"; - - // Don't put these on the query string - delete options.parentId; + options = options || {}; + options.userId = userId; - var url = self.getUrl("Users/" + userId + "/Items/" + parentId + "/Genres", options); + var url = self.getUrl("Genres", options); return self.ajax({ type: "GET", @@ -1556,12 +1658,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null userId"); } - var parentId = options.parentId || "root"; - - // Don't put these on the query string - delete options.parentId; + options = options || {}; + options.userId = userId; - var url = self.getUrl("Users/" + userId + "/Items/" + parentId + "/Persons", options); + var url = self.getUrl("Persons", options); return self.ajax({ type: "GET", @@ -1579,12 +1679,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null userId"); } - var parentId = options.parentId || "root"; - - // Don't put these on the query string - delete options.parentId; + options = options || {}; + options.userId = userId; - var url = self.getUrl("Users/" + userId + "/Items/" + parentId + "/Studios", options); + var url = self.getUrl("Studios", options); return self.ajax({ type: "GET", @@ -1722,7 +1820,67 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { * @param {String} name * @param {Boolean} isFavorite */ - self.updateItemByNameFavoriteStatus = function (userId, name, isFavorite) { + self.updateFavoriteArtistStatus = function (userId, name, isFavorite) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Favorites/Artists/" + encodeName(name)); + + var method = isFavorite ? "POST" : "DELETE"; + + return self.ajax({ + type: method, + url: url + }); + }; + + self.updateFavoritePersonStatus = function (userId, name, isFavorite) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Favorites/Persons/" + encodeName(name)); + + var method = isFavorite ? "POST" : "DELETE"; + + return self.ajax({ + type: method, + url: url + }); + }; + + self.updateFavoriteStudioStatus = function (userId, name, isFavorite) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Favorites/Studios/" + encodeName(name)); + + var method = isFavorite ? "POST" : "DELETE"; + + return self.ajax({ + type: method, + url: url + }); + }; + + self.updateFavoriteGenreStatus = function (userId, name, isFavorite) { if (!userId) { throw new Error("null userId"); @@ -1732,7 +1890,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null name"); } - var url = self.getUrl("Users/" + userId + "/ItemsByName/Favorites/" + encodeName(name)); + var url = self.getUrl("Users/" + userId + "/Favorites/Genres/" + encodeName(name)); var method = isFavorite ? "POST" : "DELETE"; @@ -1748,7 +1906,27 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { * @param {String} name * @param {Boolean} likes */ - self.updateItemByNameRating = function (userId, name, likes) { + self.updateArtistRating = function (userId, name, likes) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Ratings/Artists/" + encodeName(name), { + likes: likes + }); + + return self.ajax({ + type: "POST", + url: url + }); + }; + + self.updatePersonRating = function (userId, name, likes) { if (!userId) { throw new Error("null userId"); @@ -1758,7 +1936,47 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null name"); } - var url = self.getUrl("Users/" + userId + "/ItemsByName/" + encodeName(name) + "/Rating", { + var url = self.getUrl("Users/" + userId + "/Ratings/Persons/" + encodeName(name), { + likes: likes + }); + + return self.ajax({ + type: "POST", + url: url + }); + }; + + self.updateStudioRating = function (userId, name, likes) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Ratings/Studios/" + encodeName(name), { + likes: likes + }); + + return self.ajax({ + type: "POST", + url: url + }); + }; + + self.updateGenreRating = function (userId, name, likes) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Ratings/Genres/" + encodeName(name), { likes: likes }); @@ -1773,7 +1991,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { * @param {String} userId * @param {String} name */ - self.clearItemByNameRating = function (userId, name) { + self.clearArtistRating = function (userId, name) { if (!userId) { throw new Error("null userId"); @@ -1783,7 +2001,61 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null name"); } - var url = self.getUrl("Users/" + userId + "/ItemsByName/" + encodeName(name) + "/Rating"); + var url = self.getUrl("Users/" + userId + "/Ratings/Artists/" + encodeName(name)); + + return self.ajax({ + type: "DELETE", + url: url + }); + }; + + self.clearPersonRating = function (userId, name) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Ratings/Persons/" + encodeName(name)); + + return self.ajax({ + type: "DELETE", + url: url + }); + }; + + self.clearStudioRating = function (userId, name) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Ratings/Studios/" + encodeName(name)); + + return self.ajax({ + type: "DELETE", + url: url + }); + }; + + self.clearGenreRating = function (userId, name) { + + if (!userId) { + throw new Error("null userId"); + } + + if (!name) { + throw new Error("null name"); + } + + var url = self.getUrl("Users/" + userId + "/Ratings/Genres/" + encodeName(name)); return self.ajax({ type: "DELETE", @@ -1792,11 +2064,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { }; /** - * Gets the full user data object for an item by name. - * @param {String} userId - * @param {String} name + Gets a variety of item counts that a person appears in */ - self.getItembyNameUserData = function (userId, name) { + self.getPersonItemCounts = function (userId, name) { if (!userId) { throw new Error("null userId"); @@ -1806,7 +2076,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null name"); } - var url = self.getUrl("Users/" + userId + "/ItemsByName/" + encodeName(name) + "/UserData"); + var url = self.getUrl("Persons/" + encodeName(name) + "/Counts", { + userId: userId + }); return self.ajax({ type: "GET", @@ -1816,9 +2088,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { }; /** - Gets a variety of item counts that a person appears in + Gets a variety of item counts that a genre appears in */ - self.getPersonItemCounts = function (userId, name) { + self.getGenreItemCounts = function (userId, name) { if (!userId) { throw new Error("null userId"); @@ -1828,7 +2100,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null name"); } - var url = self.getUrl("Users/" + userId + "/Persons/" + encodeName(name) + "/Counts"); + var url = self.getUrl("Genres/" + encodeName(name) + "/Counts", { + userId: userId + }); return self.ajax({ type: "GET", @@ -1838,9 +2112,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { }; /** - Gets a variety of item counts that a genre appears in + Gets a variety of item counts that an artist appears in */ - self.getGenreItemCounts = function (userId, name) { + self.getArtistItemCounts = function (userId, name) { if (!userId) { throw new Error("null userId"); @@ -1850,7 +2124,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null name"); } - var url = self.getUrl("Users/" + userId + "/Genres/" + encodeName(name) + "/Counts"); + var url = self.getUrl("Artists/" + encodeName(name) + "/Counts", { + userId: userId + }); return self.ajax({ type: "GET", @@ -1872,7 +2148,9 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) { throw new Error("null name"); } - var url = self.getUrl("Users/" + userId + "/Studios/" + encodeName(name) + "/Counts"); + var url = self.getUrl("Studios/" + encodeName(name) + "/Counts", { + userId: userId + }); return self.ajax({ type: "GET", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 68836a245..f7cd271ef 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -84,9 +84,6 @@ </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ApiClient.js" />
- <Content Include="dashboard-ui\boxset.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\boxsets.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -237,6 +234,12 @@ <Content Include="dashboard-ui\movietrailers.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\musicalbums.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\musicartists.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\musicgenres.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -246,9 +249,6 @@ <Content Include="dashboard-ui\playlist.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\boxset.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\boxsets.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -288,6 +288,12 @@ <Content Include="dashboard-ui\scripts\movietrailers.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\musicalbums.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\musicartists.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\musicgenres.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -335,12 +341,6 @@ <Content Include="dashboard-ui\scripts\tvrecommended.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\tvseason.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\scripts\tvseries.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\tvshows.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -383,12 +383,6 @@ <Content Include="dashboard-ui\tvrecommended.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\tvseason.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
- <Content Include="dashboard-ui\tvseries.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\tvshows.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index c84847e3e..56ea6b5c9 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.80" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.85" targetFramework="net45" /> <package id="ServiceStack.Common" version="3.9.43" targetFramework="net45" /> <package id="ServiceStack.Text" version="3.9.43" targetFramework="net45" /> </packages>
\ No newline at end of file |
