new Artist entity

pull/702/head
Luke Pulverenti 11 years ago
parent 1a153cbd39
commit 374b7f2f03

@ -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>
@ -233,6 +246,18 @@ namespace MediaBrowser.Api.Images
return GetImage(request, item);
}
/// <summary>
/// Gets the specified request.
/// </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>

@ -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;
@ -36,66 +32,6 @@ namespace MediaBrowser.Api.Library
public bool HasInternetProvider { get; set; }
}
/// <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>
@ -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>

@ -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" />

@ -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);
}
}
}

@ -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>
@ -257,12 +289,24 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
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>

@ -9,13 +9,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>

@ -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);
}
}
}

@ -1,38 +1,21 @@
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;
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>
@ -128,35 +126,32 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
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);

@ -21,6 +21,13 @@ namespace MediaBrowser.Api.UserLibrary
[Api(Description = "Gets items based on a query.")]
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>
@ -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 =>

@ -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>
{
@ -51,6 +52,28 @@ namespace MediaBrowser.Api.UserLibrary
public string Name { get; set; }
}
/// <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>
@ -67,6 +90,42 @@ namespace MediaBrowser.Api.UserLibrary
{
}
/// <summary>
/// Gets the specified request.
/// </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>
@ -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(',');

@ -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>
{
@ -42,6 +43,28 @@ namespace MediaBrowser.Api.UserLibrary
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>
/// 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>
@ -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();

@ -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,13 +16,34 @@ 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>
@ -34,6 +59,42 @@ namespace MediaBrowser.Api.UserLibrary
{
}
/// <summary>
/// Gets the specified request.
/// </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>
@ -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();

@ -528,7 +528,7 @@ namespace MediaBrowser.Controller.Dto
recursiveItemCount++;
// Check is recently added
if (child.IsRecentlyAdded(user))
if (child.IsRecentlyAdded())
{
rcentlyAddedItemCount++;
}

@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <returns>System.String.</returns>
public override string GetUserDataKey()
{
return Name;
return "Artist-" + Name;
}
}
}

@ -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);
}
}
}

@ -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

@ -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();
}
}
}

@ -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;
}

@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
public override string GetUserDataKey()
{
return Name;
return "Genre-" + Name;
}
}
}

@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
public override string GetUserDataKey()
{
return Name;
return "Person-" + Name;
}
}

@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
public override string GetUserDataKey()
{
return Name;
return "Studio-" + Name;
}
}
}

@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
public override string GetUserDataKey()
{
return Name;
return "Year-" + Name;
}
}
}

@ -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);
}
}

@ -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 (

@ -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.

@ -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)
{

@ -251,7 +251,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";
@ -268,7 +268,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;
@ -281,9 +281,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;
@ -1034,7 +1031,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)
{
@ -1066,7 +1063,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)
{

@ -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;

@ -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;
}
}
}

@ -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;
}
}
}
}

@ -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;
}
}

@ -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;
}
}
}
}

@ -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;
}
}
}

@ -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);

@ -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;
}

@ -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);
}
}
}
}

@ -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)
{

@ -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)
{

@ -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)

@ -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)
{

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="morelinq" version="1.0.15631-beta" targetFramework="net45" />
</packages>

@ -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>

@ -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" />

@ -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);
}

@ -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";
}
}
}
}

@ -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>

@ -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)
{

@ -477,6 +477,8 @@ namespace MediaBrowser.WebDashboard.Api
"moviesrecommended.js",
"moviestudios.js",
"movietrailers.js",
"musicalbums.js",
"musicartists.js",
"musicgenres.js",
"playlist.js",
"plugincatalogpage.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",
@ -1131,6 +1179,41 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.getUrl(url, options);
};
/**
* 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
@ -1524,6 +1607,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
*/
@ -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",

@ -237,6 +237,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>
@ -288,6 +294,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>

@ -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>
Loading…
Cancel
Save