diff options
63 files changed, 1923 insertions, 971 deletions
diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 6bc8a2bea..d9db48a30 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -22,28 +22,6 @@ namespace MediaBrowser.Api.UserLibrary { } - /// <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> @@ -119,49 +97,6 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public object Get(GetArtistsItemCounts request) - { - var name = DeSlugArtistName(request.Name, LibraryManager); - - var items = GetItems(request.UserId).Where(i => - { - var song = i as Audio; - - if (song != null) - { - return song.HasArtist(name); - } - - var musicVideo = i as MusicVideo; - - if (musicVideo != null) - { - return musicVideo.HasArtist(name); - } - - return false; - - }).ToList(); - - var counts = new ItemByNameCounts - { - TotalCount = items.Count, - - SongCount = items.OfType<Audio>().Count(), - - AlbumCount = items.Select(i => i.Parent).OfType<MusicAlbum>().Distinct().Count(), - - MusicVideoCount = items.OfType<MusicVideo>().Count(i => i.HasArtist(name)) - }; - - 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; @@ -193,7 +128,7 @@ namespace MediaBrowser.Api.UserLibrary return list; }) .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(name => new IbnStub<Artist>(name, () => itemsList.Where(i => i.HasArtist(name)), GetEntity)); + .Select(name => new IbnStub<Artist>(name, GetEntity)); } /// <summary> diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index d4881c167..fd7925551 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <typeparam name="TItemType">The type of the T item type.</typeparam> public abstract class BaseItemsByNameService<TItemType> : BaseApiService - where TItemType : BaseItem + where TItemType : BaseItem, IItemByName { /// <summary> /// The _user manager @@ -38,6 +38,8 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="userManager">The user manager.</param> /// <param name="libraryManager">The library manager.</param> /// <param name="userDataRepository">The user data repository.</param> + /// <param name="itemRepository">The item repository.</param> + /// <param name="dtoService">The dto service.</param> protected BaseItemsByNameService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepository, IDtoService dtoService) { UserManager = userManager; @@ -292,7 +294,7 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>Task{DtoBaseItem}.</returns> private async Task<BaseItemDto> GetDto(IbnStub<TItemType> stub, User user, List<ItemFields> fields) { - BaseItem item; + TItemType item; try { @@ -307,14 +309,6 @@ namespace MediaBrowser.Api.UserLibrary var dto = user == null ? await DtoService.GetBaseItemDto(item, fields).ConfigureAwait(false) : await DtoService.GetBaseItemDto(item, fields, user).ConfigureAwait(false); - if (fields.Contains(ItemFields.ItemCounts)) - { - var items = stub.Items; - - dto.ChildCount = items.Count; - dto.RecentlyAddedItemCount = items.Count(i => i.IsRecentlyAdded()); - } - return dto; } @@ -367,9 +361,6 @@ namespace MediaBrowser.Api.UserLibrary public class IbnStub<T> where T : BaseItem { - private readonly Func<IEnumerable<BaseItem>> _childItemsFunction; - private List<BaseItem> _childItems; - private readonly Func<string,Task<T>> _itemFunction; private Task<T> _itemTask; @@ -377,11 +368,6 @@ namespace MediaBrowser.Api.UserLibrary private UserItemData _userData; - public List<BaseItem> Items - { - get { return _childItems ?? (_childItems = _childItemsFunction().ToList()); } - } - public Task<T> GetItem() { return _itemTask ?? (_itemTask = _itemFunction(Name)); @@ -394,10 +380,9 @@ namespace MediaBrowser.Api.UserLibrary return _userData ?? (_userData = repo.GetUserData(userId, item.GetUserDataKey())); } - public IbnStub(string name, Func<IEnumerable<BaseItem>> childItems, Func<string,Task<T>> item) + public IbnStub(string name, Func<string,Task<T>> item) { Name = name; - _childItemsFunction = childItems; _itemFunction = item; } } diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs index 50ee09826..ff26fe423 100644 --- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs @@ -23,25 +23,6 @@ namespace MediaBrowser.Api.UserLibrary } } - [Route("/GameGenres/{Name}/Counts", "GET")] - [Api(Description = "Gets item counts of library items that a genre appears in")] - public class GetGameGenreItemCounts : IReturn<ItemByNameCounts> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [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. - /// </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; } - } - [Route("/GameGenres/{Name}", "GET")] [Api(Description = "Gets a Game genre, by name")] public class GetGameGenre : IReturn<BaseItemDto> @@ -127,7 +108,7 @@ namespace MediaBrowser.Api.UserLibrary return itemsList .SelectMany(i => i.Genres) .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(name => new IbnStub<GameGenre>(name, () => itemsList.Where(i => i.Genres.Contains(name, StringComparer.OrdinalIgnoreCase)), GetEntity)); + .Select(name => new IbnStub<GameGenre>(name, GetEntity)); } /// <summary> @@ -139,26 +120,5 @@ namespace MediaBrowser.Api.UserLibrary { return LibraryManager.GetGameGenre(name); } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetGameGenreItemCounts request) - { - var name = DeSlugGameGenreName(request.Name, LibraryManager); - - var items = GetItems(request.UserId).Where(i => i.Genres != null && i.Genres.Contains(name, StringComparer.OrdinalIgnoreCase)).ToList(); - - var counts = new ItemByNameCounts - { - TotalCount = items.Count, - - GameCount = items.OfType<Game>().Count() - }; - - return ToOptimizedResult(counts); - } } } diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 83405bf52..afec8f799 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -1,7 +1,5 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; @@ -23,25 +21,6 @@ namespace MediaBrowser.Api.UserLibrary { } - [Route("/Genres/{Name}/Counts", "GET")] - [Api(Description = "Gets item counts of library items that a genre appears in")] - public class GetGenreItemCounts : IReturn<ItemByNameCounts> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [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. - /// </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 GetGenre /// </summary> @@ -133,7 +112,7 @@ namespace MediaBrowser.Api.UserLibrary return itemsList .SelectMany(i => i.Genres) .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(name => new IbnStub<Genre>(name, () => itemsList.Where(i => i.Genres.Contains(name, StringComparer.OrdinalIgnoreCase)), GetEntity)); + .Select(name => new IbnStub<Genre>(name, GetEntity)); } /// <summary> @@ -145,34 +124,5 @@ 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 name = DeSlugGenreName(request.Name, LibraryManager); - - var items = GetItems(request.UserId).Where(i => i.Genres.Contains(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<Game>().Count(), - - AdultVideoCount = items.OfType<AdultVideo>().Count() - }; - - return ToOptimizedResult(counts); - } } } diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index c9218c6b5..5f08b6131 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -427,28 +427,9 @@ namespace MediaBrowser.Api.UserLibrary items = items.Where(i => { - var audio = i as Audio; - - if (audio != null) - { - return artists.Any(audio.HasArtist); - } - - var album = i as MusicAlbum; - - if (album != null) - { - return artists.Any(album.HasArtist); - } + var audio = i as IHasArtist; - var musicVideo = i as MusicVideo; - - if (musicVideo != null) - { - return artists.Any(musicVideo.HasArtist); - } - - return false; + return audio != null && artists.Any(audio.HasArtist); }); } diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs index d9f0016fb..4c3a8d958 100644 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs @@ -23,25 +23,6 @@ namespace MediaBrowser.Api.UserLibrary } } - [Route("/MusicGenres/{Name}/Counts", "GET")] - [Api(Description = "Gets item counts of library items that a genre appears in")] - public class GetMusicGenreItemCounts : IReturn<ItemByNameCounts> - { - /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - [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. - /// </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; } - } - [Route("/MusicGenres/{Name}", "GET")] [Api(Description = "Gets a music genre, by name")] public class GetMusicGenre : IReturn<BaseItemDto> @@ -127,7 +108,7 @@ namespace MediaBrowser.Api.UserLibrary return itemsList .SelectMany(i => i.Genres) .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(name => new IbnStub<MusicGenre>(name, () => itemsList.Where(i => i.Genres.Contains(name, StringComparer.OrdinalIgnoreCase)), GetEntity)); + .Select(name => new IbnStub<MusicGenre>(name, GetEntity)); } /// <summary> @@ -139,30 +120,5 @@ namespace MediaBrowser.Api.UserLibrary { return LibraryManager.GetMusicGenre(name); } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetMusicGenreItemCounts request) - { - var name = DeSlugGenreName(request.Name, LibraryManager); - - var items = GetItems(request.UserId).Where(i => i.Genres != null && i.Genres.Contains(name, StringComparer.OrdinalIgnoreCase)).ToList(); - - var counts = new ItemByNameCounts - { - TotalCount = items.Count, - - SongCount = items.OfType<Audio>().Count(), - - AlbumCount = items.OfType<MusicAlbum>().Count(), - - MusicVideoCount = items.OfType<MusicVideo>().Count() - }; - - return ToOptimizedResult(counts); - } } } diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index a21c53976..e8a6ddcc5 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -1,8 +1,5 @@ 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; @@ -30,28 +27,6 @@ namespace MediaBrowser.Api.UserLibrary } /// <summary> - /// Class GetPersonItemCounts - /// </summary> - [Route("/Persons/{Name}/Counts", "GET")] - [Api(Description = "Gets item counts of library items that a person appears in")] - public class GetPersonItemCounts : 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 person name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - - /// <summary> /// Class GetPerson /// </summary> [Route("/Persons/{Name}", "GET")] @@ -137,43 +112,6 @@ namespace MediaBrowser.Api.UserLibrary } /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetPersonItemCounts request) - { - var name = DeSlugPersonName(request.Name, LibraryManager); - - var items = GetItems(request.UserId).Where(i => i.People != null && i.People.Any(p => string.Equals(p.Name, name, StringComparison.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<Game>().Count(), - - SongCount = items.OfType<Audio>().Count(), - - AlbumCount = items.OfType<MusicAlbum>().Count(), - - EpisodeCount = items.OfType<Episode>().Count(), - - MusicVideoCount = items.OfType<MusicVideo>().Count(), - - AdultVideoCount = items.OfType<AdultVideo>().Count() - }; - - return ToOptimizedResult(counts); - } - - /// <summary> /// Gets all items. /// </summary> /// <param name="request">The request.</param> @@ -193,15 +131,7 @@ namespace MediaBrowser.Api.UserLibrary .Select(i => i.Name) .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(name => new IbnStub<Person>(name, () => - { - if (personTypes.Length == 0) - { - return itemsList.Where(i => i.People.Any(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))); - } - - return itemsList.Where(i => i.People.Any(p => p.Name.Equals(name, StringComparison.OrdinalIgnoreCase) && (personTypes.Contains(p.Type ?? string.Empty, StringComparer.OrdinalIgnoreCase) || personTypes.Contains(p.Role ?? string.Empty, StringComparer.OrdinalIgnoreCase)))); - }, GetEntity) + .Select(name => new IbnStub<Person>(name, GetEntity) ); } diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index f58adb1fa..5f42eab80 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -1,8 +1,5 @@ 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; @@ -24,25 +21,6 @@ namespace MediaBrowser.Api.UserLibrary { } - [Route("/Studios/{Name}/Counts", "GET")] - [Api(Description = "Gets item counts of library items that a studio appears in")] - public class GetStudioItemCounts : 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 studio name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - } - /// <summary> /// Class GetStudio /// </summary> @@ -114,41 +92,6 @@ namespace MediaBrowser.Api.UserLibrary /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> - public object Get(GetStudioItemCounts request) - { - var name = DeSlugStudioName(request.Name, LibraryManager); - - var items = GetItems(request.UserId).Where(i => i.Studios != null && i.Studios.Contains(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<Game>().Count(), - - SongCount = items.OfType<Audio>().Count(), - - AlbumCount = items.OfType<MusicAlbum>().Count(), - - MusicVideoCount = items.OfType<MusicVideo>().Count(), - - AdultVideoCount = items.OfType<AdultVideo>().Count() - }; - - return ToOptimizedResult(counts); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> public object Get(GetStudios request) { var result = GetResult(request).Result; @@ -169,7 +112,7 @@ namespace MediaBrowser.Api.UserLibrary return itemsList .SelectMany(i => i.Studios) .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(name => new IbnStub<Studio>(name, () => itemsList.Where(i => i.Studios.Contains(name, StringComparer.OrdinalIgnoreCase)), GetEntity)); + .Select(name => new IbnStub<Studio>(name, GetEntity)); } /// <summary> diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index b153b6e10..642129564 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -118,7 +118,7 @@ namespace MediaBrowser.Api.UserLibrary return itemsList .Select(i => i.ProductionYear.Value) .Distinct() - .Select(year => new IbnStub<Year>(year.ToString(UsCulture), () => itemsList.Where(i => i.ProductionYear.HasValue && i.ProductionYear.Value == year), GetEntity)); + .Select(year => new IbnStub<Year>(year.ToString(UsCulture), GetEntity)); } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Audio/Artist.cs b/MediaBrowser.Controller/Entities/Audio/Artist.cs index 274356b30..3d2d41fb7 100644 --- a/MediaBrowser.Controller/Entities/Audio/Artist.cs +++ b/MediaBrowser.Controller/Entities/Audio/Artist.cs @@ -1,11 +1,20 @@ - +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities.Audio { /// <summary> /// Class Artist /// </summary> - public class Artist : BaseItem, IItemByName + public class Artist : BaseItem, IItemByName, IHasMusicGenres { + public Artist() + { + ItemCounts = new ItemByNameCounts(); + UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + } + public string LastFmImageUrl { get; set; } /// <summary> @@ -17,5 +26,8 @@ namespace MediaBrowser.Controller.Entities.Audio return "Artist-" + Name; } + public ItemByNameCounts ItemCounts { get; set; } + + public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index ff632c981..beb25f1f2 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class Audio /// </summary> - public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist + public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres { public Audio() { diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs index d9dac077f..77bd3fd66 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs @@ -5,4 +5,9 @@ namespace MediaBrowser.Controller.Entities.Audio { string AlbumArtist { get; } } + + public interface IHasArtist + { + bool HasArtist(string name); + } } diff --git a/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs new file mode 100644 index 000000000..fdf939e35 --- /dev/null +++ b/MediaBrowser.Controller/Entities/Audio/IHasMusicGenres.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Entities.Audio +{ + public interface IHasMusicGenres + { + List<string> Genres { get; } + } +} diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 834fc6943..46b4bc73c 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Runtime.Serialization; namespace MediaBrowser.Controller.Entities.Audio @@ -6,10 +7,15 @@ namespace MediaBrowser.Controller.Entities.Audio /// <summary> /// Class MusicAlbum /// </summary> - public class MusicAlbum : Folder, IHasAlbumArtist + public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres { + public MusicAlbum() + { + Artists = new string[] { }; + } + public string LastFmImageUrl { get; set; } - + /// <summary> /// Songs will group into us so don't also include us in the index /// </summary> @@ -60,23 +66,17 @@ namespace MediaBrowser.Controller.Entities.Audio /// <returns><c>true</c> if the specified artist has artist; otherwise, <c>false</c>.</returns> public bool HasArtist(string artist) { - return RecursiveChildren.OfType<Audio>().Any(i => i.HasArtist(artist)); + return string.Equals(AlbumArtist, artist, StringComparison.OrdinalIgnoreCase) + || Artists.Contains(artist, StringComparer.OrdinalIgnoreCase); } - public string AlbumArtist - { - get - { - return RecursiveChildren - .OfType<Audio>() - .Select(i => i.AlbumArtist) - .FirstOrDefault(i => !string.IsNullOrEmpty(i)); - } - } + public string AlbumArtist { get; set; } + + public string[] Artists { get; set; } } public class MusicAlbumDisc : Folder { - + } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 452aaec58..3839f2a0b 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -1,4 +1,7 @@ - +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities.Audio { /// <summary> @@ -6,6 +9,12 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> public class MusicGenre : BaseItem, IItemByName { + public MusicGenre() + { + ItemCounts = new ItemByNameCounts(); + UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + } + /// <summary> /// Gets the user data key. /// </summary> @@ -14,5 +23,9 @@ namespace MediaBrowser.Controller.Entities.Audio { return "MusicGenre-" + Name; } + + public ItemByNameCounts ItemCounts { get; set; } + + public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index fb2260769..4887af2cf 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1537,5 +1537,58 @@ namespace MediaBrowser.Controller.Entities // Refresh metadata return RefreshMetadata(CancellationToken.None, forceSave: true); } + + /// <summary> + /// Validates that images within the item are still on the file system + /// </summary> + public void ValidateImages() + { + // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below + var deletedKeys = Images + .ToList() + .Where(image => !File.Exists(image.Value)) + .Select(i => i.Key) + .ToList(); + + // Now remove them from the dictionary + foreach (var key in deletedKeys) + { + Images.Remove(key); + } + } + + /// <summary> + /// Validates that backdrops within the item are still on the file system + /// </summary> + public void ValidateBackdrops() + { + // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below + var deletedImages = BackdropImagePaths + .Where(path => !File.Exists(path)) + .ToList(); + + // Now remove them from the dictionary + foreach (var path in deletedImages) + { + BackdropImagePaths.Remove(path); + } + } + + /// <summary> + /// Validates the screenshots. + /// </summary> + public void ValidateScreenshots() + { + // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below + var deletedImages = ScreenshotImagePaths + .Where(path => !File.Exists(path)) + .ToList(); + + // Now remove them from the dictionary + foreach (var path in deletedImages) + { + ScreenshotImagePaths.Remove(path); + } + } } } diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index f2462aac2..7ae4f8f73 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -1,8 +1,17 @@ - +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities { public class GameGenre : BaseItem, IItemByName { + public GameGenre() + { + ItemCounts = new ItemByNameCounts(); + UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + } + /// <summary> /// Gets the user data key. /// </summary> @@ -11,5 +20,9 @@ namespace MediaBrowser.Controller.Entities { return "GameGenre-" + Name; } + + public ItemByNameCounts ItemCounts { get; set; } + + public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index e02a427e0..83fa57dfb 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -1,4 +1,7 @@ - +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities { /// <summary> @@ -6,6 +9,12 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Genre : BaseItem, IItemByName { + public Genre() + { + ItemCounts = new ItemByNameCounts(); + UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + } + /// <summary> /// Gets the user data key. /// </summary> @@ -14,5 +23,9 @@ namespace MediaBrowser.Controller.Entities { return "Genre-" + Name; } + + public ItemByNameCounts ItemCounts { get; set; } + + public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs index 6d66acb70..380846d15 100644 --- a/MediaBrowser.Controller/Entities/IItemByName.cs +++ b/MediaBrowser.Controller/Entities/IItemByName.cs @@ -1,4 +1,7 @@ - +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities { /// <summary> @@ -6,5 +9,8 @@ namespace MediaBrowser.Controller.Entities /// </summary> public interface IItemByName { + ItemByNameCounts ItemCounts { get; set; } + + Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 6b2601687..207f76efd 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,9 +1,10 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Model.Entities; using System; namespace MediaBrowser.Controller.Entities { - public class MusicVideo : Video + public class MusicVideo : Video, IHasArtist, IHasMusicGenres { /// <summary> /// Gets or sets the artist. diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index 273a10acc..3e0798435 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -1,4 +1,7 @@ - +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities { /// <summary> @@ -6,6 +9,16 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Person : BaseItem, IItemByName { + public Person() + { + ItemCounts = new ItemByNameCounts(); + UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + } + + public ItemByNameCounts ItemCounts { get; set; } + + public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + /// <summary> /// Gets the user data key. /// </summary> diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 57e560348..8bbf59173 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -1,4 +1,7 @@ - +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities { /// <summary> @@ -6,6 +9,12 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Studio : BaseItem, IItemByName { + public Studio() + { + ItemCounts = new ItemByNameCounts(); + UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + } + /// <summary> /// Gets the user data key. /// </summary> @@ -14,5 +23,9 @@ namespace MediaBrowser.Controller.Entities { return "Studio-" + Name; } + + public ItemByNameCounts ItemCounts { get; set; } + + public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } } } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 7ce008c5e..ed07e3175 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -136,6 +136,11 @@ namespace MediaBrowser.Controller.Entities get { return Video3DFormat.HasValue; } } + public bool IsHd + { + get { return MediaStreams != null && MediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1280); } + } + /// <summary> /// Gets the type of the media. /// </summary> diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index c5235a07a..daaebdc85 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -1,4 +1,7 @@ - +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities { /// <summary> @@ -6,6 +9,16 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Year : BaseItem, IItemByName { + public Year() + { + ItemCounts = new ItemByNameCounts(); + UserItemCounts = new Dictionary<Guid, ItemByNameCounts>(); + } + + public ItemByNameCounts ItemCounts { get; set; } + + public Dictionary<Guid, ItemByNameCounts> UserItemCounts { get; set; } + /// <summary> /// Gets the user data key. /// </summary> diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index f4165a630..b6b80586a 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -236,6 +236,38 @@ namespace MediaBrowser.Controller.Library Task ValidateArtists(CancellationToken cancellationToken, IProgress<double> progress); /// <summary> + /// Validates the music genres. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + Task ValidateMusicGenres(CancellationToken cancellationToken, IProgress<double> progress); + + /// <summary> + /// Validates the game genres. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + Task ValidateGameGenres(CancellationToken cancellationToken, IProgress<double> progress); + + /// <summary> + /// Validates the genres. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + Task ValidateGenres(CancellationToken cancellationToken, IProgress<double> progress); + + /// <summary> + /// Validates the studios. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + Task ValidateStudios(CancellationToken cancellationToken, IProgress<double> progress); + + /// <summary> /// Occurs when [item added]. /// </summary> event EventHandler<ItemChangeEventArgs> ItemAdded; diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 3546b8b04..990c5f670 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -74,6 +74,7 @@ <Compile Include="Dto\IDtoService.cs" /> <Compile Include="Entities\AdultVideo.cs" /> <Compile Include="Entities\Audio\IHasAlbumArtist.cs" /> + <Compile Include="Entities\Audio\IHasMusicGenres.cs" /> <Compile Include="Entities\Book.cs" /> <Compile Include="Configuration\IServerConfigurationManager.cs" /> <Compile Include="Entities\Audio\MusicGenre.cs" /> @@ -90,6 +91,7 @@ <Compile Include="Localization\ILocalizationManager.cs" /> <Compile Include="Notifications\INotificationsRepository.cs" /> <Compile Include="Notifications\NotificationUpdateEventArgs.cs" /> + <Compile Include="Providers\IDynamicInfoProvider.cs" /> <Compile Include="Session\ISessionManager.cs" /> <Compile Include="Drawing\ImageExtensions.cs" /> <Compile Include="Drawing\ImageHeader.cs" /> diff --git a/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs b/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs new file mode 100644 index 000000000..0f5dea5f6 --- /dev/null +++ b/MediaBrowser.Controller/Providers/IDynamicInfoProvider.cs @@ -0,0 +1,10 @@ + +namespace MediaBrowser.Controller.Providers +{ + /// <summary> + /// Marker interface for a provider that always runs + /// </summary> + public interface IDynamicInfoProvider + { + } +} diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index 9bb96da5f..5a0159370 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -28,6 +28,8 @@ namespace MediaBrowser.Model.ApiClient /// </summary> event EventHandler<HttpResponseEventArgs> HttpResponseReceived; + Task<T> GetAsync<T>(string url, CancellationToken cancellationToken); + /// <summary> /// Gets the critic reviews. /// </summary> diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 6c69197f6..63b819d69 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -36,8 +36,6 @@ namespace MediaBrowser.Model.Dto /// <value>The name of the sort.</value> public string SortName { get; set; } - public string MainFeaturePlaylistName { get; set; } - /// <summary> /// Gets or sets the video3 D format. /// </summary> @@ -521,6 +519,48 @@ namespace MediaBrowser.Model.Dto /// <value>The locked fields.</value> public List<MetadataFields> LockedFields { get; set; } + public int? AdultVideoCount { get; set; } + /// <summary> + /// Gets or sets the movie count. + /// </summary> + /// <value>The movie count.</value> + public int? MovieCount { get; set; } + /// <summary> + /// Gets or sets the series count. + /// </summary> + /// <value>The series count.</value> + public int? SeriesCount { get; set; } + /// <summary> + /// Gets or sets the episode count. + /// </summary> + /// <value>The episode count.</value> + public int? EpisodeCount { get; set; } + /// <summary> + /// Gets or sets the game count. + /// </summary> + /// <value>The game count.</value> + public int? GameCount { get; set; } + /// <summary> + /// Gets or sets the trailer count. + /// </summary> + /// <value>The trailer count.</value> + public int? TrailerCount { get; set; } + /// <summary> + /// Gets or sets the song count. + /// </summary> + /// <value>The song count.</value> + public int? SongCount { get; set; } + /// <summary> + /// Gets or sets the album count. + /// </summary> + /// <value>The album count.</value> + public int? AlbumCount { get; set; } + /// <summary> + /// Gets or sets the music video count. + /// </summary> + /// <value>The music video count.</value> + public int? MusicVideoCount { get; set; } + /// <summary> /// Gets or sets a value indicating whether [enable internet providers]. /// </summary> diff --git a/MediaBrowser.Model/Dto/ItemByNameCounts.cs b/MediaBrowser.Model/Dto/ItemByNameCounts.cs index eff5ab5c8..ae801e196 100644 --- a/MediaBrowser.Model/Dto/ItemByNameCounts.cs +++ b/MediaBrowser.Model/Dto/ItemByNameCounts.cs @@ -11,6 +11,10 @@ namespace MediaBrowser.Model.Dto /// </summary> /// <value>The total count.</value> public int TotalCount { get; set; } + /// <summary> + /// Gets or sets the adult video count. + /// </summary> + /// <value>The adult video count.</value> public int AdultVideoCount { get; set; } /// <summary> /// Gets or sets the movie count. diff --git a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs index fe7fd8409..02025f4bb 100644 --- a/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs +++ b/MediaBrowser.Providers/ImageFromMediaLocationProvider.cs @@ -98,13 +98,13 @@ namespace MediaBrowser.Providers var args = GetResolveArgsContainingImages(item); // Make sure current image paths still exist - ValidateImages(item); + item.ValidateImages(); cancellationToken.ThrowIfCancellationRequested(); // Make sure current backdrop paths still exist - ValidateBackdrops(item); - ValidateScreenshots(item, args); + item.ValidateBackdrops(); + item.ValidateScreenshots(); cancellationToken.ThrowIfCancellationRequested(); @@ -129,74 +129,6 @@ namespace MediaBrowser.Providers } /// <summary> - /// Validates that images within the item are still on the file system - /// </summary> - /// <param name="item">The item.</param> - internal static void ValidateImages(BaseItem item) - { - // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below - var deletedKeys = item.Images - .ToList() - .Where(image => !File.Exists(image.Value)) - .Select(i => i.Key) - .ToList(); - - // Now remove them from the dictionary - foreach (var key in deletedKeys) - { - item.Images.Remove(key); - } - } - - /// <summary> - /// Validates that backdrops within the item are still on the file system - /// </summary> - /// <param name="item">The item.</param> - internal static void ValidateBackdrops(BaseItem item) - { - // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below - var deletedImages = item.BackdropImagePaths - .Where(path => !File.Exists(path)) - .ToList(); - - // Now remove them from the dictionary - foreach (var path in deletedImages) - { - item.BackdropImagePaths.Remove(path); - } - } - - /// <summary> - /// Validates the screenshots. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="args">The args.</param> - private void ValidateScreenshots(BaseItem item, ItemResolveArgs args) - { - // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below - var deletedImages = item.ScreenshotImagePaths - .Where(path => !File.Exists(path)) - .ToList(); - - // Now remove them from the dictionary - foreach (var path in deletedImages) - { - item.ScreenshotImagePaths.Remove(path); - } - } - - /// <summary> - /// Determines whether [is in same directory] [the specified item]. - /// </summary> - /// <param name="item">The item.</param> - /// <param name="path">The path.</param> - /// <returns><c>true</c> if [is in same directory] [the specified item]; otherwise, <c>false</c>.</returns> - private bool IsInMetaLocation(BaseItem item, string path) - { - return string.Equals(Path.GetDirectoryName(path), item.MetaLocation, StringComparison.OrdinalIgnoreCase); - } - - /// <summary> /// Gets the image. /// </summary> /// <param name="item">The item.</param> diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index ef8656670..d62c2f27f 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -70,7 +70,6 @@ <Compile Include="Music\AlbumInfoFromSongProvider.cs" /> <Compile Include="Music\ArtistInfoFromSongProvider.cs" /> <Compile Include="Music\ArtistProviderFromXml.cs" /> - <Compile Include="Music\ArtistsPostScanTask.cs" /> <Compile Include="Music\FanArtAlbumProvider.cs" /> <Compile Include="Music\FanArtArtistByNameProvider.cs" /> <Compile Include="Music\FanArtArtistProvider.cs" /> @@ -81,6 +80,7 @@ <Compile Include="Music\LastfmArtistProvider.cs" /> <Compile Include="Music\LastfmBaseProvider.cs" /> <Compile Include="Music\LastfmHelper.cs" /> + <Compile Include="Music\MusicAlbumDynamicInfoProvider.cs" /> <Compile Include="Music\MusicVideoXmlParser.cs" /> <Compile Include="Music\SoundtrackPostScanTask.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> @@ -106,9 +106,9 @@ <Compile Include="TV\RemoteSeasonProvider.cs" /> <Compile Include="TV\RemoteSeriesProvider.cs" /> <Compile Include="TV\SeasonProviderFromXml.cs" /> + <Compile Include="TV\SeriesPostScanTask.cs" /> <Compile Include="TV\SeriesProviderFromXml.cs" /> <Compile Include="TV\SeriesXmlParser.cs" /> - <Compile Include="TV\SeriesPostScanTask.cs" /> <Compile Include="TV\TvdbPersonImageProvider.cs" /> <Compile Include="TV\TvdbPrescanTask.cs" /> <Compile Include="TV\TvdbSeriesImageProvider.cs" /> diff --git a/MediaBrowser.Providers/Music/ArtistsPostScanTask.cs b/MediaBrowser.Providers/Music/ArtistsPostScanTask.cs deleted file mode 100644 index dd04f364b..000000000 --- a/MediaBrowser.Providers/Music/ArtistsPostScanTask.cs +++ /dev/null @@ -1,161 +0,0 @@ -using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Entities; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Providers.Music -{ - /// <summary> - /// Class ArtistsPostScanTask - /// </summary> - public class ArtistsPostScanTask : ILibraryPostScanTask - { - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - - /// <summary> - /// Initializes a new instance of the <see cref="ArtistsPostScanTask"/> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - public ArtistsPostScanTask(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - /// <summary> - /// Runs the specified progress. - /// </summary> - /// <param name="progress">The progress.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) - { - var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList(); - - var allArtists = await GetAllArtists(allItems).ConfigureAwait(false); - - progress.Report(10); - - var allMusicArtists = allItems.OfType<MusicArtist>().ToList(); - var allSongs = allItems.OfType<Audio>().ToList(); - - var numComplete = 0; - - foreach (var artist in allArtists) - { - var musicArtist = FindMusicArtist(artist, allMusicArtists); - - if (musicArtist != null) - { - MergeImages(musicArtist.Images, artist.Images); - - // Merge backdrops - var backdrops = musicArtist.BackdropImagePaths.ToList(); - backdrops.InsertRange(0, artist.BackdropImagePaths); - artist.BackdropImagePaths = backdrops.Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - ImageFromMediaLocationProvider.ValidateImages(artist); - ImageFromMediaLocationProvider.ValidateBackdrops(artist); - } - - if (!artist.LockedFields.Contains(MetadataFields.Genres)) - { - // Avoid implicitly captured closure - var artist1 = artist; - - artist.Genres = allSongs.Where(i => i.HasArtist(artist1.Name)) - .SelectMany(i => i.Genres) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - } - - numComplete++; - double percent = numComplete; - percent /= allArtists.Length; - percent *= 5; - - progress.Report(10 + percent); - } - - var innerProgress = new ActionableProgress<double>(); - - innerProgress.RegisterAction(pct => progress.Report(15 + pct * .85)); - - await _libraryManager.ValidateArtists(cancellationToken, innerProgress).ConfigureAwait(false); - } - - private void MergeImages(Dictionary<ImageType, string> source, Dictionary<ImageType, string> target) - { - foreach (var key in source.Keys - .ToList() - .Where(k => !target.ContainsKey(k))) - { - string path; - - if (source.TryGetValue(key, out path)) - { - target[key] = path; - } - } - } - - /// <summary> - /// Gets all artists. - /// </summary> - /// <param name="allItems">All items.</param> - /// <returns>Task{Artist[]}.</returns> - private Task<Artist[]> GetAllArtists(IEnumerable<BaseItem> allItems) - { - var itemsList = allItems.OfType<Audio>().ToList(); - - var tasks = itemsList - .SelectMany(i => - { - var list = new List<string>(); - - if (!string.IsNullOrEmpty(i.AlbumArtist)) - { - list.Add(i.AlbumArtist); - } - list.AddRange(i.Artists); - - return list; - }) - .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(i => _libraryManager.GetArtist(i)); - - return Task.WhenAll(tasks); - } - - /// <summary> - /// Finds the music artist. - /// </summary> - /// <param name="artist">The artist.</param> - /// <param name="allMusicArtists">All music artists.</param> - /// <returns>MusicArtist.</returns> - private static MusicArtist FindMusicArtist(Artist artist, IEnumerable<MusicArtist> allMusicArtists) - { - var musicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz); - - return allMusicArtists.FirstOrDefault(i => - { - if (!string.IsNullOrWhiteSpace(musicBrainzId) && string.Equals(musicBrainzId, i.GetProviderId(MetadataProviders.Musicbrainz), StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - return string.Compare(i.Name, artist.Name, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0; - }); - } - } -} diff --git a/MediaBrowser.Providers/Music/MusicAlbumDynamicInfoProvider.cs b/MediaBrowser.Providers/Music/MusicAlbumDynamicInfoProvider.cs new file mode 100644 index 000000000..3d4c401e7 --- /dev/null +++ b/MediaBrowser.Providers/Music/MusicAlbumDynamicInfoProvider.cs @@ -0,0 +1,85 @@ +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Providers.Music +{ + /// <summary> + /// Class MusicAlbumDynamicInfoProvider + /// </summary> + public class MusicAlbumDynamicInfoProvider : BaseMetadataProvider, IDynamicInfoProvider + { + /// <summary> + /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class. + /// </summary> + /// <param name="logManager">The log manager.</param> + /// <param name="configurationManager">The configuration manager.</param> + public MusicAlbumDynamicInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager) + : base(logManager, configurationManager) + { + } + + /// <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 MusicAlbum; + } + + /// <summary> + /// Needses the refresh internal. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="providerInfo">The provider info.</param> + /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns> + protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo) + { + return true; + } + + /// <summary> + /// Fetches metadata and returns true or false indicating if any work that requires persistence was done + /// </summary> + /// <param name="item">The item.</param> + /// <param name="force">if set to <c>true</c> [force].</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{System.Boolean}.</returns> + public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken) + { + var album = (MusicAlbum)item; + + var songs = album.RecursiveChildren + .OfType<Audio>() + .ToList(); + + album.AlbumArtist = songs + .Select(i => i.AlbumArtist) + .FirstOrDefault(i => !string.IsNullOrEmpty(i)); + + album.Artists = songs.SelectMany(i => i.Artists) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + // Don't save to the db + return FalseTaskResult; + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override MetadataProviderPriority Priority + { + get { return MetadataProviderPriority.Last; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 46dfd0ba3..5f383f1a0 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -107,6 +107,15 @@ namespace MediaBrowser.Server.Implementations.Dto .ToArray(); } + if (fields.Contains(ItemFields.ItemCounts)) + { + var itemByName = item as IItemByName; + if (itemByName != null) + { + AttachItemByNameCounts(dto, itemByName, user); + } + } + // Make sure all the tasks we kicked off have completed. if (tasks.Count > 0) { @@ -117,6 +126,41 @@ namespace MediaBrowser.Server.Implementations.Dto } /// <summary> + /// Attaches the item by name counts. + /// </summary> + /// <param name="dto">The dto.</param> + /// <param name="item">The item.</param> + /// <param name="user">The user.</param> + private void AttachItemByNameCounts(BaseItemDto dto, IItemByName item, User user) + { + ItemByNameCounts counts; + + if (user == null) + { + counts = item.ItemCounts; + } + else + { + if (!item.UserItemCounts.TryGetValue(user.Id, out counts)) + { + counts = new ItemByNameCounts(); + } + } + + dto.ChildCount = counts.TotalCount; + + dto.AdultVideoCount = counts.AdultVideoCount; + dto.AlbumCount = counts.AlbumCount; + dto.EpisodeCount = counts.EpisodeCount; + dto.GameCount = counts.GameCount; + dto.MovieCount = counts.MovieCount; + dto.MusicVideoCount = counts.MusicVideoCount; + dto.SeriesCount = counts.SeriesCount; + dto.SongCount = counts.SongCount; + dto.TrailerCount = counts.TrailerCount; + } + + /// <summary> /// Attaches the user specific info. /// </summary> /// <param name="dto">The dto.</param> @@ -380,7 +424,9 @@ namespace MediaBrowser.Server.Implementations.Dto _logger.ErrorException("Error getting {0} image info for {1}", ex, type, path); return null; } - } /// <summary> + } + + /// <summary> /// Attaches People DTO's to a DTOBaseItem /// </summary> /// <param name="dto">The dto.</param> @@ -913,12 +959,7 @@ namespace MediaBrowser.Server.Implementations.Dto if (album != null) { - var songs = album.RecursiveChildren.OfType<Audio>().ToList(); - - dto.Artists = - songs.SelectMany(i => i.Artists) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray(); + dto.Artists = album.Artists; } var hasAlbumArtist = item as IHasAlbumArtist; @@ -935,7 +976,6 @@ namespace MediaBrowser.Server.Implementations.Dto dto.VideoType = video.VideoType; dto.Video3DFormat = video.Video3DFormat; dto.IsoType = video.IsoType; - dto.MainFeaturePlaylistName = video.MainFeaturePlaylistName; dto.PartCount = video.AdditionalPartIds.Count + 1; diff --git a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs index c1e8d2877..6d6aa6325 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/NativeWebSocket.cs @@ -81,6 +81,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer { bytes = await ReceiveBytesAsync(CancellationToken.None).ConfigureAwait(false); } + catch (OperationCanceledException) + { + break; + } catch (WebSocketException ex) { _logger.ErrorException("Error receiving web socket message", ex); diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index d9ab75397..d017a5f7e 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -13,6 +13,7 @@ using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; +using MediaBrowser.Server.Implementations.Library.Validators; using MediaBrowser.Server.Implementations.ScheduledTasks; using MoreLinq; using System; @@ -597,11 +598,11 @@ namespace MediaBrowser.Server.Implementations.Library /// <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> + /// <param name="refreshMetadata">if set to <c>true</c> [force creation].</param> /// <returns>Task{Person}.</returns> - private Task<Person> GetPerson(string name, CancellationToken cancellationToken, bool allowSlowProviders = false, bool forceCreation = false) + private Task<Person> GetPerson(string name, CancellationToken cancellationToken, bool allowSlowProviders = false, bool refreshMetadata = false) { - return GetItemByName<Person>(ConfigurationManager.ApplicationPaths.PeoplePath, name, cancellationToken, allowSlowProviders, forceCreation); + return GetItemByName<Person>(ConfigurationManager.ApplicationPaths.PeoplePath, name, cancellationToken, allowSlowProviders, refreshMetadata); } /// <summary> @@ -612,7 +613,20 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Studio}.</returns> public Task<Studio> GetStudio(string name, bool allowSlowProviders = false) { - return GetItemByName<Studio>(ConfigurationManager.ApplicationPaths.StudioPath, name, CancellationToken.None, allowSlowProviders); + return GetStudio(name, CancellationToken.None, allowSlowProviders); + } + + /// <summary> + /// Gets the studio. + /// </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="refreshMetadata">if set to <c>true</c> [refresh metadata].</param> + /// <returns>Task{Studio}.</returns> + internal Task<Studio> GetStudio(string name, CancellationToken cancellationToken, bool allowSlowProviders = false, bool refreshMetadata = false) + { + return GetItemByName<Studio>(ConfigurationManager.ApplicationPaths.StudioPath, name, cancellationToken, allowSlowProviders, refreshMetadata); } /// <summary> @@ -623,7 +637,20 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{Genre}.</returns> public Task<Genre> GetGenre(string name, bool allowSlowProviders = false) { - return GetItemByName<Genre>(ConfigurationManager.ApplicationPaths.GenrePath, name, CancellationToken.None, allowSlowProviders); + return GetGenre(name, CancellationToken.None, allowSlowProviders); + } + + /// <summary> + /// Gets the genre. + /// </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="refreshMetadata">if set to <c>true</c> [refresh metadata].</param> + /// <returns>Task{Genre}.</returns> + internal Task<Genre> GetGenre(string name, CancellationToken cancellationToken, bool allowSlowProviders = false, bool refreshMetadata = false) + { + return GetItemByName<Genre>(ConfigurationManager.ApplicationPaths.GenrePath, name, cancellationToken, allowSlowProviders, refreshMetadata); } /// <summary> @@ -634,7 +661,20 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{MusicGenre}.</returns> public Task<MusicGenre> GetMusicGenre(string name, bool allowSlowProviders = false) { - return GetItemByName<MusicGenre>(ConfigurationManager.ApplicationPaths.MusicGenrePath, name, CancellationToken.None, allowSlowProviders); + return GetMusicGenre(name, CancellationToken.None, allowSlowProviders); + } + + /// <summary> + /// Gets the music genre. + /// </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="refreshMetadata">if set to <c>true</c> [refresh metadata].</param> + /// <returns>Task{MusicGenre}.</returns> + internal Task<MusicGenre> GetMusicGenre(string name, CancellationToken cancellationToken, bool allowSlowProviders = false, bool refreshMetadata = false) + { + return GetItemByName<MusicGenre>(ConfigurationManager.ApplicationPaths.MusicGenrePath, name, cancellationToken, allowSlowProviders, refreshMetadata); } /// <summary> @@ -645,7 +685,20 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task{GameGenre}.</returns> public Task<GameGenre> GetGameGenre(string name, bool allowSlowProviders = false) { - return GetItemByName<GameGenre>(ConfigurationManager.ApplicationPaths.GameGenrePath, name, CancellationToken.None, allowSlowProviders); + return GetGameGenre(name, CancellationToken.None, allowSlowProviders); + } + + /// <summary> + /// Gets the game genre. + /// </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="refreshMetadata">if set to <c>true</c> [refresh metadata].</param> + /// <returns>Task{GameGenre}.</returns> + internal Task<GameGenre> GetGameGenre(string name, CancellationToken cancellationToken, bool allowSlowProviders = false, bool refreshMetadata = false) + { + return GetItemByName<GameGenre>(ConfigurationManager.ApplicationPaths.GameGenrePath, name, cancellationToken, allowSlowProviders, refreshMetadata); } /// <summary> @@ -665,11 +718,11 @@ namespace MediaBrowser.Server.Implementations.Library /// <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> + /// <param name="refreshMetadata">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) + internal Task<Artist> GetArtist(string name, CancellationToken cancellationToken, bool allowSlowProviders = false, bool refreshMetadata = false) { - return GetItemByName<Artist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name, cancellationToken, allowSlowProviders, forceCreation); + return GetItemByName<Artist>(ConfigurationManager.ApplicationPaths.ArtistsPath, name, cancellationToken, allowSlowProviders, refreshMetadata); } /// <summary> @@ -707,11 +760,11 @@ namespace MediaBrowser.Server.Implementations.Library /// <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> + /// <param name="refreshMetadata">if set to <c>true</c> [force creation].</param> /// <returns>Task{``0}.</returns> /// <exception cref="System.ArgumentNullException"> /// </exception> - private async Task<T> GetItemByName<T>(string path, string name, CancellationToken cancellationToken, bool allowSlowProviders = true, bool forceCreation = false) + private async Task<T> GetItemByName<T>(string path, string name, CancellationToken cancellationToken, bool allowSlowProviders = true, bool refreshMetadata = false) where T : BaseItem, new() { if (string.IsNullOrEmpty(path)) @@ -730,11 +783,25 @@ namespace MediaBrowser.Server.Implementations.Library if (!_itemsByName.TryGetValue(key, out obj)) { - obj = await CreateItemByName<T>(path, name, cancellationToken, allowSlowProviders).ConfigureAwait(false); + var tuple = CreateItemByName<T>(path, name, cancellationToken); + + obj = tuple.Item2; _itemsByName.AddOrUpdate(key, obj, (keyName, oldValue) => obj); + + try + { + await obj.RefreshMetadata(cancellationToken, tuple.Item1, allowSlowProviders: allowSlowProviders).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + BaseItem removed; + _itemsByName.TryRemove(key, out removed); + + throw; + } } - else if (forceCreation) + else if (refreshMetadata) { await obj.RefreshMetadata(cancellationToken, false, allowSlowProviders: allowSlowProviders).ConfigureAwait(false); } @@ -749,10 +816,9 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="path">The path.</param> /// <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> /// <returns>Task{``0}.</returns> /// <exception cref="System.IO.IOException">Path not created: + path</exception> - private async Task<T> CreateItemByName<T>(string path, string name, CancellationToken cancellationToken, bool allowSlowProviders = true) + private Tuple<bool, T> CreateItemByName<T>(string path, string name, CancellationToken cancellationToken) where T : BaseItem, new() { cancellationToken.ThrowIfCancellationRequested(); @@ -783,6 +849,7 @@ namespace MediaBrowser.Server.Implementations.Library var id = path.GetMBId(type); var item = RetrieveItem(id) as T; + if (item == null) { item = new T @@ -796,16 +863,10 @@ namespace MediaBrowser.Server.Implementations.Library isNew = true; } - cancellationToken.ThrowIfCancellationRequested(); - // Set this now so we don't cause additional file system access during provider executions item.ResetResolveArgs(fileInfo); - await item.RefreshMetadata(cancellationToken, isNew, allowSlowProviders: allowSlowProviders).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - return item; + return new Tuple<bool,T>(isNew, item); } /// <summary> @@ -884,75 +945,53 @@ namespace MediaBrowser.Server.Implementations.Library /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> /// <returns>Task.</returns> - public async Task ValidateArtists(CancellationToken cancellationToken, IProgress<double> progress) + public 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 = new List<string>(); - - if (!string.IsNullOrEmpty(c.AlbumArtist)) - { - list.Add(c.AlbumArtist); - } - list.AddRange(c.Artists); - - 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; + return new ArtistsValidator(this, _userManager, _logger).Run(progress, cancellationToken); + } - progress.Report(100 * percent); - } - })); - } + /// <summary> + /// Validates the music genres. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task ValidateMusicGenres(CancellationToken cancellationToken, IProgress<double> progress) + { + return new MusicGenresValidator(this, _userManager, _logger).Run(progress, cancellationToken); + } - await Task.WhenAll(tasks).ConfigureAwait(false); + /// <summary> + /// Validates the game genres. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task ValidateGameGenres(CancellationToken cancellationToken, IProgress<double> progress) + { + return new GameGenresValidator(this, _userManager, _logger).Run(progress, cancellationToken); + } - progress.Report(100); + /// <summary> + /// Validates the studios. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task ValidateStudios(CancellationToken cancellationToken, IProgress<double> progress) + { + return new StudiosValidator(this, _userManager, _logger).Run(progress, cancellationToken); + } - _logger.Info("Artist validation complete"); + /// <summary> + /// Validates the genres. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task ValidateGenres(CancellationToken cancellationToken, IProgress<double> progress) + { + return new GenresValidator(this, _userManager, _logger).Run(progress, cancellationToken); } /// <summary> @@ -1000,12 +1039,12 @@ namespace MediaBrowser.Server.Implementations.Library var innerProgress = new ActionableProgress<double>(); - innerProgress.RegisterAction(pct => progress.Report(15 + pct * .65)); + innerProgress.RegisterAction(pct => progress.Report(15 + pct * .6)); // Now validate the entire media library await RootFolder.ValidateChildren(innerProgress, cancellationToken, recursive: true).ConfigureAwait(false); - progress.Report(80); + progress.Report(75); // Run post-scan tasks await RunPostScanTasks(progress, cancellationToken).ConfigureAwait(false); @@ -1078,7 +1117,7 @@ namespace MediaBrowser.Server.Implementations.Library double percent = progressDictionary.Values.Sum(); percent /= postscanTasks.Count; - progress.Report(80 + percent * .2); + progress.Report(75 + percent * .25); } }); diff --git a/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs b/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs index d8b8a18e9..6b37ee62f 100644 --- a/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/LuceneSearchEngine.cs @@ -155,7 +155,7 @@ namespace MediaBrowser.Server.Implementations.Library } // Find genres, from non-audio items - var genres = items.Where(i => !(i is Audio) && !(i is MusicAlbum) && !(i is MusicArtist) && !(i is MusicVideo) && !(i is Game)) + var genres = items.Where(i => !(i is IHasMusicGenres) && !(i is Game)) .SelectMany(i => i.Genres) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) @@ -181,7 +181,7 @@ namespace MediaBrowser.Server.Implementations.Library } // Find music genres - var musicGenres = items.Where(i => (i is Audio) || (i is MusicAlbum) || (i is MusicArtist) || (i is MusicVideo)) + var musicGenres = items.Where(i => i is IHasMusicGenres) .SelectMany(i => i.Genres) .Where(i => !string.IsNullOrEmpty(i)) .Distinct(StringComparer.OrdinalIgnoreCase) diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs new file mode 100644 index 000000000..575ffec14 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsPostScanTask.cs @@ -0,0 +1,38 @@ +using MediaBrowser.Controller.Library; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + /// <summary> + /// Class ArtistsPostScanTask + /// </summary> + public class ArtistsPostScanTask : ILibraryPostScanTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + public ArtistsPostScanTask(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return _libraryManager.ValidateArtists(cancellationToken, progress); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs new file mode 100644 index 000000000..8109208e7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -0,0 +1,285 @@ +using MediaBrowser.Common.Progress; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + /// <summary> + /// Class ArtistsValidator + /// </summary> + public class ArtistsValidator + { + /// <summary> + /// The _library manager + /// </summary> + private readonly LibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="userManager">The user manager.</param> + /// <param name="logger">The logger.</param> + public ArtistsValidator(LibraryManager libraryManager, IUserManager userManager, ILogger logger) + { + _libraryManager = libraryManager; + _userManager = userManager; + _logger = logger; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList(); + + var allMusicArtists = allItems.OfType<MusicArtist>().ToList(); + var allSongs = allItems.OfType<Audio>().ToList(); + + var innerProgress = new ActionableProgress<double>(); + + innerProgress.RegisterAction(pct => progress.Report(pct * .8)); + + var allArtists = await GetAllArtists(allSongs, cancellationToken, innerProgress).ConfigureAwait(false); + + progress.Report(80); + + var numComplete = 0; + + var userLibraries = _userManager.Users + .Select(i => new Tuple<Guid, List<IHasArtist>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<IHasArtist>().ToList())) + .ToList(); + + foreach (var artist in allArtists) + { + cancellationToken.ThrowIfCancellationRequested(); + + artist.ValidateImages(); + artist.ValidateBackdrops(); + + var musicArtist = FindMusicArtist(artist, allMusicArtists); + + if (musicArtist != null) + { + MergeImages(musicArtist.Images, artist.Images); + + // Merge backdrops + var backdrops = musicArtist.BackdropImagePaths.ToList(); + backdrops.InsertRange(0, artist.BackdropImagePaths); + artist.BackdropImagePaths = backdrops.Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + if (!artist.LockedFields.Contains(MetadataFields.Genres)) + { + // Avoid implicitly captured closure + var artist1 = artist; + + artist.Genres = allSongs.Where(i => i.HasArtist(artist1.Name)) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + } + + // Populate counts of items + SetItemCounts(artist, null, allItems.OfType<IHasArtist>()); + + foreach (var lib in userLibraries) + { + SetItemCounts(artist, lib.Item1, lib.Item2); + } + + numComplete++; + double percent = numComplete; + percent /= allArtists.Count; + percent *= 20; + + progress.Report(80 + percent); + } + + progress.Report(100); + } + + /// <summary> + /// Sets the item counts. + /// </summary> + /// <param name="artist">The artist.</param> + /// <param name="userId">The user id.</param> + /// <param name="allItems">All items.</param> + private void SetItemCounts(Artist artist, Guid? userId, IEnumerable<IHasArtist> allItems) + { + var name = artist.Name; + + var items = allItems + .Where(i => i.HasArtist(name)) + .ToList(); + + var counts = new ItemByNameCounts + { + TotalCount = items.Count, + + SongCount = items.OfType<Audio>().Count(), + + AlbumCount = items.OfType<MusicAlbum>().Count(), + + MusicVideoCount = items.OfType<MusicVideo>().Count() + }; + + if (userId.HasValue) + { + artist.UserItemCounts[userId.Value] = counts; + } + else + { + artist.ItemCounts = counts; + } + } + + /// <summary> + /// Merges the images. + /// </summary> + /// <param name="source">The source.</param> + /// <param name="target">The target.</param> + private void MergeImages(Dictionary<ImageType, string> source, Dictionary<ImageType, string> target) + { + foreach (var key in source.Keys + .ToList() + .Where(k => !target.ContainsKey(k))) + { + string path; + + if (source.TryGetValue(key, out path)) + { + target[key] = path; + } + } + } + + /// <summary> + /// Gets all artists. + /// </summary> + /// <param name="allSongs">All songs.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task{Artist[]}.</returns> + private async Task<List<Artist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress) + { + var allArtists = allSongs + .SelectMany(i => + { + var list = new List<string>(); + + if (!string.IsNullOrEmpty(i.AlbumArtist)) + { + list.Add(i.AlbumArtist); + } + list.AddRange(i.Artists); + + return list; + }) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + const int maxTasks = 5; + + var tasks = new List<Task>(); + + var returnArtists = new ConcurrentBag<Artist>(); + + var numComplete = 0; + + foreach (var artist in allArtists) + { + 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 + { + var artistItem = await _libraryManager.GetArtist(currentArtist, cancellationToken, true, true) + .ConfigureAwait(false); + + returnArtists.Add(artistItem); + } + catch (IOException ex) + { + _logger.ErrorException("Error validating Artist {0}", ex, currentArtist); + } + + // Update progress + lock (progress) + { + numComplete++; + double percent = numComplete; + percent /= allArtists.Count; + + progress.Report(100 * percent); + } + })); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + return returnArtists.ToList(); + } + + /// <summary> + /// Finds the music artist. + /// </summary> + /// <param name="artist">The artist.</param> + /// <param name="allMusicArtists">All music artists.</param> + /// <returns>MusicArtist.</returns> + private static MusicArtist FindMusicArtist(Artist artist, IEnumerable<MusicArtist> allMusicArtists) + { + var musicBrainzId = artist.GetProviderId(MetadataProviders.Musicbrainz); + + return allMusicArtists.FirstOrDefault(i => + { + if (!string.IsNullOrWhiteSpace(musicBrainzId) && string.Equals(musicBrainzId, i.GetProviderId(MetadataProviders.Musicbrainz), StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return string.Compare(i.Name, artist.Name, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0; + }); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/CountHelpers.cs b/MediaBrowser.Server.Implementations/Library/Validators/CountHelpers.cs new file mode 100644 index 000000000..ea4d887ea --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/CountHelpers.cs @@ -0,0 +1,155 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Model.Dto; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + /// <summary> + /// Class CountHelpers + /// </summary> + internal static class CountHelpers + { + /// <summary> + /// Adds to dictionary. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="counts">The counts.</param> + internal static void AddToDictionary(BaseItem item, Dictionary<string, int> counts) + { + if (item is Movie) + { + IncrementCount(counts, "Movie"); + } + else if (item is Trailer) + { + IncrementCount(counts, "Trailer"); + } + else if (item is Series) + { + IncrementCount(counts, "Series"); + } + else if (item is Game) + { + IncrementCount(counts, "Game"); + } + else if (item is Audio) + { + IncrementCount(counts, "Audio"); + } + else if (item is MusicAlbum) + { + IncrementCount(counts, "MusicAlbum"); + } + else if (item is Episode) + { + IncrementCount(counts, "Episode"); + } + else if (item is MusicVideo) + { + IncrementCount(counts, "MusicVideo"); + } + else if (item is AdultVideo) + { + IncrementCount(counts, "AdultVideo"); + } + + IncrementCount(counts, "Total"); + } + + /// <summary> + /// Increments the count. + /// </summary> + /// <param name="counts">The counts.</param> + /// <param name="key">The key.</param> + internal static void IncrementCount(Dictionary<string, int> counts, string key) + { + int count; + + if (counts.TryGetValue(key, out count)) + { + count++; + counts[key] = count; + } + else + { + counts.Add(key, 1); + } + } + + /// <summary> + /// Gets the counts. + /// </summary> + /// <param name="counts">The counts.</param> + /// <returns>ItemByNameCounts.</returns> + internal static ItemByNameCounts GetCounts(Dictionary<string, int> counts) + { + return new ItemByNameCounts + { + AdultVideoCount = GetCount(counts, "AdultVideo"), + AlbumCount = GetCount(counts, "MusicAlbum"), + EpisodeCount = GetCount(counts, "Episode"), + GameCount = GetCount(counts, "Game"), + MovieCount = GetCount(counts, "Movie"), + MusicVideoCount = GetCount(counts, "MusicVideo"), + SeriesCount = GetCount(counts, "Series"), + SongCount = GetCount(counts, "Audio"), + TrailerCount = GetCount(counts, "Trailer"), + TotalCount = GetCount(counts, "Total") + }; + } + + /// <summary> + /// Gets the count. + /// </summary> + /// <param name="counts">The counts.</param> + /// <param name="key">The key.</param> + /// <returns>System.Int32.</returns> + internal static int GetCount(Dictionary<string, int> counts, string key) + { + int count; + + if (counts.TryGetValue(key, out count)) + { + return count; + } + + return 0; + } + + /// <summary> + /// Sets the item counts. + /// </summary> + /// <param name="userId">The user id.</param> + /// <param name="media">The media.</param> + /// <param name="names">The names.</param> + /// <param name="masterDictionary">The master dictionary.</param> + internal static void SetItemCounts(Guid? userId, BaseItem media, List<string> names, Dictionary<string, Dictionary<Guid, Dictionary<string, int>>> masterDictionary) + { + foreach (var name in names) + { + Dictionary<Guid, Dictionary<string, int>> libraryCounts; + + if (!masterDictionary.TryGetValue(name, out libraryCounts)) + { + libraryCounts = new Dictionary<Guid, Dictionary<string, int>>(); + masterDictionary.Add(name, libraryCounts); + } + + var userLibId = userId ?? Guid.Empty; + Dictionary<string, int> userDictionary; + + if (!libraryCounts.TryGetValue(userLibId, out userDictionary)) + { + userDictionary = new Dictionary<string, int>(); + libraryCounts.Add(userLibId, userDictionary); + } + + AddToDictionary(media, userDictionary); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs new file mode 100644 index 000000000..5bd394363 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs @@ -0,0 +1,45 @@ +using MediaBrowser.Controller.Library; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + /// <summary> + /// Class GameGenresPostScanTask + /// </summary> + public class GameGenresPostScanTask : ILibraryPostScanTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="GameGenresPostScanTask"/> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="userManager">The user manager.</param> + public GameGenresPostScanTask(ILibraryManager libraryManager, IUserManager userManager) + { + _libraryManager = libraryManager; + _userManager = userManager; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return _libraryManager.ValidateGameGenres(cancellationToken, progress); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs new file mode 100644 index 000000000..f8aeadda1 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -0,0 +1,132 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + class GameGenresValidator + { + /// <summary> + /// The _library manager + /// </summary> + private readonly LibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + public GameGenresValidator(LibraryManager libraryManager, IUserManager userManager, ILogger logger) + { + _libraryManager = libraryManager; + _userManager = userManager; + _logger = logger; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var allItems = _libraryManager.RootFolder.RecursiveChildren.OfType<Game>().ToList(); + + var userLibraries = _userManager.Users + .Select(i => new Tuple<Guid, List<Game>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<Game>().ToList())) + .ToList(); + + var allLibraryItems = allItems; + + var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<string, int>>>(StringComparer.OrdinalIgnoreCase); + + // Populate counts of items + SetItemCounts(null, allLibraryItems, masterDictionary); + + progress.Report(2); + + var numComplete = 0; + + foreach (var lib in userLibraries) + { + SetItemCounts(lib.Item1, lib.Item2, masterDictionary); + + numComplete++; + double percent = numComplete; + percent /= userLibraries.Count; + percent *= 8; + + progress.Report(percent); + } + + progress.Report(10); + + var names = masterDictionary.Keys.ToList(); + numComplete = 0; + + foreach (var name in names) + { + try + { + await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error updating counts for {0}", ex, name); + } + + numComplete++; + double percent = numComplete; + percent /= names.Count; + percent *= 90; + + progress.Report(percent + 10); + } + + progress.Report(100); + } + + private async Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<string, int>> counts) + { + var itemByName = await _libraryManager.GetGameGenre(name, cancellationToken, true, true).ConfigureAwait(false); + + foreach (var libraryId in counts.Keys.ToList()) + { + var itemCounts = CountHelpers.GetCounts(counts[libraryId]); + + if (libraryId == Guid.Empty) + { + itemByName.ItemCounts = itemCounts; + } + else + { + itemByName.UserItemCounts[libraryId] = itemCounts; + } + } + } + + private void SetItemCounts(Guid? userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<string, int>>> masterDictionary) + { + foreach (var media in allItems) + { + var names = media + .Genres + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + CountHelpers.SetItemCounts(userId, media, names, masterDictionary); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs new file mode 100644 index 000000000..5da090b29 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresPostScanTask.cs @@ -0,0 +1,42 @@ +using MediaBrowser.Controller.Library; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + public class GenresPostScanTask : ILibraryPostScanTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="userManager">The user manager.</param> + public GenresPostScanTask(ILibraryManager libraryManager, IUserManager userManager) + { + _libraryManager = libraryManager; + _userManager = userManager; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return _libraryManager.ValidateGenres(cancellationToken, progress); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs new file mode 100644 index 000000000..852984a5c --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs @@ -0,0 +1,135 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + class GenresValidator + { + /// <summary> + /// The _library manager + /// </summary> + private readonly LibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + public GenresValidator(LibraryManager libraryManager, IUserManager userManager, ILogger logger) + { + _libraryManager = libraryManager; + _userManager = userManager; + _logger = logger; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var allItems = _libraryManager.RootFolder.RecursiveChildren + .Where(i => !(i is IHasMusicGenres) && !(i is Game)) + .ToList(); + + var userLibraries = _userManager.Users + .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => !(m is IHasMusicGenres) && !(m is Game)).ToList())) + .ToList(); + + var allLibraryItems = allItems; + + var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<string, int>>>(StringComparer.OrdinalIgnoreCase); + + // Populate counts of items + SetItemCounts(null, allLibraryItems, masterDictionary); + + progress.Report(2); + + var numComplete = 0; + + foreach (var lib in userLibraries) + { + SetItemCounts(lib.Item1, lib.Item2, masterDictionary); + + numComplete++; + double percent = numComplete; + percent /= userLibraries.Count; + percent *= 8; + + progress.Report(percent); + } + + progress.Report(10); + + var names = masterDictionary.Keys.ToList(); + numComplete = 0; + + foreach (var name in names) + { + try + { + await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error updating counts for {0}", ex, name); + } + + numComplete++; + double percent = numComplete; + percent /= names.Count; + percent *= 90; + + progress.Report(percent + 10); + } + + progress.Report(100); + } + + private async Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<string, int>> counts) + { + var itemByName = await _libraryManager.GetGenre(name, cancellationToken, true, true).ConfigureAwait(false); + + foreach (var libraryId in counts.Keys.ToList()) + { + var itemCounts = CountHelpers.GetCounts(counts[libraryId]); + + if (libraryId == Guid.Empty) + { + itemByName.ItemCounts = itemCounts; + } + else + { + itemByName.UserItemCounts[libraryId] = itemCounts; + } + } + } + + private void SetItemCounts(Guid? userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<string, int>>> masterDictionary) + { + foreach (var media in allItems) + { + var names = media + .Genres + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + CountHelpers.SetItemCounts(userId, media, names, masterDictionary); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs new file mode 100644 index 000000000..e64a4baa4 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresPostScanTask.cs @@ -0,0 +1,45 @@ +using MediaBrowser.Controller.Library; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + /// <summary> + /// Class MusicGenresPostScanTask + /// </summary> + public class MusicGenresPostScanTask : ILibraryPostScanTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="userManager">The user manager.</param> + public MusicGenresPostScanTask(ILibraryManager libraryManager, IUserManager userManager) + { + _libraryManager = libraryManager; + _userManager = userManager; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return _libraryManager.ValidateMusicGenres(cancellationToken, progress); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs new file mode 100644 index 000000000..53e443527 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -0,0 +1,135 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + class MusicGenresValidator + { + /// <summary> + /// The _library manager + /// </summary> + private readonly LibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + public MusicGenresValidator(LibraryManager libraryManager, IUserManager userManager, ILogger logger) + { + _libraryManager = libraryManager; + _userManager = userManager; + _logger = logger; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var allItems = _libraryManager.RootFolder.RecursiveChildren + .Where(i => i is IHasMusicGenres) + .ToList(); + + var userLibraries = _userManager.Users + .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).Where(m => m is IHasMusicGenres).ToList())) + .ToList(); + + var allLibraryItems = allItems; + + var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<string, int>>>(StringComparer.OrdinalIgnoreCase); + + // Populate counts of items + SetItemCounts(null, allLibraryItems, masterDictionary); + + progress.Report(2); + + var numComplete = 0; + + foreach (var lib in userLibraries) + { + SetItemCounts(lib.Item1, lib.Item2, masterDictionary); + + numComplete++; + double percent = numComplete; + percent /= userLibraries.Count; + percent *= 8; + + progress.Report(percent); + } + + progress.Report(10); + + var names = masterDictionary.Keys.ToList(); + numComplete = 0; + + foreach (var name in names) + { + try + { + await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error updating counts for {0}", ex, name); + } + + numComplete++; + double percent = numComplete; + percent /= names.Count; + percent *= 90; + + progress.Report(percent + 10); + } + + progress.Report(100); + } + + private async Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<string, int>> counts) + { + var itemByName = await _libraryManager.GetMusicGenre(name, cancellationToken, true, true).ConfigureAwait(false); + + foreach (var libraryId in counts.Keys.ToList()) + { + var itemCounts = CountHelpers.GetCounts(counts[libraryId]); + + if (libraryId == Guid.Empty) + { + itemByName.ItemCounts = itemCounts; + } + else + { + itemByName.UserItemCounts[libraryId] = itemCounts; + } + } + } + + private void SetItemCounts(Guid? userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<string, int>>> masterDictionary) + { + foreach (var media in allItems) + { + var names = media + .Genres + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + CountHelpers.SetItemCounts(userId, media, names, masterDictionary); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs new file mode 100644 index 000000000..7d7728030 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs @@ -0,0 +1,137 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + class PeoplePostScanTask : ILibraryPostScanTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + public PeoplePostScanTask(ILibraryManager libraryManager, IUserManager userManager, ILogger logger) + { + _libraryManager = libraryManager; + _userManager = userManager; + _logger = logger; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList(); + + var userLibraries = _userManager.Users + .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).ToList())) + .ToList(); + + var allLibraryItems = allItems; + + var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<string, int>>>(StringComparer.OrdinalIgnoreCase); + + // Populate counts of items + SetItemCounts(null, allLibraryItems, masterDictionary); + + progress.Report(2); + + var numComplete = 0; + + foreach (var lib in userLibraries) + { + cancellationToken.ThrowIfCancellationRequested(); + + SetItemCounts(lib.Item1, lib.Item2, masterDictionary); + + numComplete++; + double percent = numComplete; + percent /= userLibraries.Count; + percent *= 8; + + progress.Report(percent); + } + + progress.Report(10); + + var names = masterDictionary.Keys.ToList(); + numComplete = 0; + + foreach (var name in names) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await UpdateItemByNameCounts(name, masterDictionary[name]).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error updating counts for {0}", ex, name); + } + + numComplete++; + double percent = numComplete; + percent /= names.Count; + percent *= 90; + + progress.Report(percent + 10); + } + + progress.Report(100); + } + + private async Task UpdateItemByNameCounts(string name, Dictionary<Guid, Dictionary<string, int>> counts) + { + var itemByName = await _libraryManager.GetPerson(name).ConfigureAwait(false); + + foreach (var libraryId in counts.Keys.ToList()) + { + var itemCounts = CountHelpers.GetCounts(counts[libraryId]); + + if (libraryId == Guid.Empty) + { + itemByName.ItemCounts = itemCounts; + } + else + { + itemByName.UserItemCounts[libraryId] = itemCounts; + } + } + } + + private void SetItemCounts(Guid? userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<string, int>>> masterDictionary) + { + foreach (var media in allItems) + { + var names = media + .People.Select(i => i.Name) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + CountHelpers.SetItemCounts(userId, media, names, masterDictionary); + } + } + + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs new file mode 100644 index 000000000..4aefd3d49 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosPostScanTask.cs @@ -0,0 +1,45 @@ +using MediaBrowser.Controller.Library; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + /// <summary> + /// Class MusicGenresPostScanTask + /// </summary> + public class StudiosPostScanTask : ILibraryPostScanTask + { + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class. + /// </summary> + /// <param name="libraryManager">The library manager.</param> + /// <param name="userManager">The user manager.</param> + public StudiosPostScanTask(ILibraryManager libraryManager, IUserManager userManager) + { + _libraryManager = libraryManager; + _userManager = userManager; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + return _libraryManager.ValidateStudios(cancellationToken, progress); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs new file mode 100644 index 000000000..1814e7c4f --- /dev/null +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -0,0 +1,132 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Library.Validators +{ + class StudiosValidator + { + /// <summary> + /// The _library manager + /// </summary> + private readonly LibraryManager _libraryManager; + + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _logger + /// </summary> + private readonly ILogger _logger; + + public StudiosValidator(LibraryManager libraryManager, IUserManager userManager, ILogger logger) + { + _libraryManager = libraryManager; + _userManager = userManager; + _logger = logger; + } + + /// <summary> + /// Runs the specified progress. + /// </summary> + /// <param name="progress">The progress.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) + { + var allItems = _libraryManager.RootFolder.RecursiveChildren.ToList(); + + var userLibraries = _userManager.Users + .Select(i => new Tuple<Guid, List<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i).ToList())) + .ToList(); + + var allLibraryItems = allItems; + + var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<string, int>>>(StringComparer.OrdinalIgnoreCase); + + // Populate counts of items + SetItemCounts(null, allLibraryItems, masterDictionary); + + progress.Report(2); + + var numComplete = 0; + + foreach (var lib in userLibraries) + { + SetItemCounts(lib.Item1, lib.Item2, masterDictionary); + + numComplete++; + double percent = numComplete; + percent /= userLibraries.Count; + percent *= 8; + + progress.Report(percent); + } + + progress.Report(10); + + var names = masterDictionary.Keys.ToList(); + numComplete = 0; + + foreach (var name in names) + { + try + { + await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error updating counts for {0}", ex, name); + } + + numComplete++; + double percent = numComplete; + percent /= names.Count; + percent *= 90; + + progress.Report(percent + 10); + } + + progress.Report(100); + } + + private async Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<string, int>> counts) + { + var itemByName = await _libraryManager.GetStudio(name, cancellationToken, true, true).ConfigureAwait(false); + + foreach (var libraryId in counts.Keys.ToList()) + { + var itemCounts = CountHelpers.GetCounts(counts[libraryId]); + + if (libraryId == Guid.Empty) + { + itemByName.ItemCounts = itemCounts; + } + else + { + itemByName.UserItemCounts[libraryId] = itemCounts; + } + } + } + + private void SetItemCounts(Guid? userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<string, int>>> masterDictionary) + { + foreach (var media in allItems) + { + var names = media + .Studios + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + CountHelpers.SetItemCounts(userId, media, names, masterDictionary); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 0f26dcdd1..6cd7fe868 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -146,6 +146,18 @@ <Compile Include="Library\Resolvers\TV\SeriesResolver.cs" /> <Compile Include="Library\Resolvers\VideoResolver.cs" /> <Compile Include="Library\UserManager.cs" /> + <Compile Include="Library\Validators\ArtistsPostScanTask.cs" /> + <Compile Include="Library\Validators\ArtistsValidator.cs" /> + <Compile Include="Library\Validators\CountHelpers.cs" /> + <Compile Include="Library\Validators\GameGenresPostScanTask.cs" /> + <Compile Include="Library\Validators\GameGenresValidator.cs" /> + <Compile Include="Library\Validators\GenresPostScanTask.cs" /> + <Compile Include="Library\Validators\GenresValidator.cs" /> + <Compile Include="Library\Validators\MusicGenresPostScanTask.cs" /> + <Compile Include="Library\Validators\MusicGenresValidator.cs" /> + <Compile Include="Library\Validators\PeoplePostScanTask.cs" /> + <Compile Include="Library\Validators\StudiosPostScanTask.cs" /> + <Compile Include="Library\Validators\StudiosValidator.cs" /> <Compile Include="Localization\LocalizationManager.cs" /> <Compile Include="MediaEncoder\MediaEncoder.cs" /> <Compile Include="Persistence\SqliteChapterRepository.cs" /> @@ -155,7 +167,6 @@ <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Providers\ImageSaver.cs" /> <Compile Include="Providers\ProviderManager.cs" /> - <Compile Include="ScheduledTasks\ArtistValidationTask.cs" /> <Compile Include="ScheduledTasks\PeopleValidationTask.cs" /> <Compile Include="ScheduledTasks\ChapterImagesTask.cs" /> <Compile Include="ScheduledTasks\PluginUpdateTask.cs" /> diff --git a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs index 089b8a505..41e6725cd 100644 --- a/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs +++ b/MediaBrowser.Server.Implementations/Providers/ProviderManager.cs @@ -189,7 +189,11 @@ namespace MediaBrowser.Server.Implementations.Providers cancellationToken.ThrowIfCancellationRequested(); - _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--"); + // Don't clog up the log with these providers + if (!(provider is IDynamicInfoProvider)) + { + _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--"); + } // This provides the ability to cancel just this one provider var innerCancellationTokenSource = new CancellationTokenSource(); diff --git a/MediaBrowser.Server.Implementations/ScheduledTasks/ArtistValidationTask.cs b/MediaBrowser.Server.Implementations/ScheduledTasks/ArtistValidationTask.cs deleted file mode 100644 index a67db1b2d..000000000 --- a/MediaBrowser.Server.Implementations/ScheduledTasks/ArtistValidationTask.cs +++ /dev/null @@ -1,81 +0,0 @@ -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller.Library; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Server.Implementations.ScheduledTasks -{ - public class ArtistValidationTask - { - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - - /// <summary> - /// Initializes a new instance of the <see cref="PeopleValidationTask" /> class. - /// </summary> - /// <param name="libraryManager">The library manager.</param> - public ArtistValidationTask(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - /// <summary> - /// Creates the triggers that define when the task will run - /// </summary> - /// <returns>IEnumerable{BaseTaskTrigger}.</returns> - public IEnumerable<ITaskTrigger> GetDefaultTriggers() - { - return new ITaskTrigger[] - { - new DailyTrigger { TimeOfDay = TimeSpan.FromHours(5) }, - - new IntervalTrigger{ Interval = TimeSpan.FromHours(12)} - }; - } - - /// <summary> - /// Returns the task to be executed - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) - { - return _libraryManager.ValidateArtists(cancellationToken, progress); - } - - /// <summary> - /// Gets the name of the task - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return "Refresh music artists"; } - } - - /// <summary> - /// Gets the description. - /// </summary> - /// <value>The description.</value> - public string Description - { - get { return "Updates metadata for music artists in your media library."; } - } - - /// <summary> - /// Gets the category. - /// </summary> - /// <value>The category.</value> - public string Category - { - get - { - return "Library"; - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index 4ba0a22ee..bf5ae498b 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -86,11 +86,6 @@ namespace MediaBrowser.Server.Implementations.Session } /// <summary> - /// The _true task result - /// </summary> - private readonly Task _trueTaskResult = Task.FromResult(true); - - /// <summary> /// Logs the user activity. /// </summary> /// <param name="clientType">Type of the client.</param> @@ -339,6 +334,7 @@ namespace MediaBrowser.Server.Implementations.Session // If the client isn't able to report this, then we'll just have to make an assumption data.PlayCount++; data.Played = true; + data.PlaybackPositionTicks = 0; } await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index 11e609ad5..c3adbfdf7 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -337,11 +337,6 @@ <Resource Include="Resources\Images\audio.png" /> </ItemGroup> <ItemGroup> - <Resource Include="Resources\Images\starEmpty.png" /> - <Resource Include="Resources\Images\starFull.png" /> - <Resource Include="Resources\Images\starHalf.png" /> - </ItemGroup> - <ItemGroup> <Resource Include="Resources\Images\artist.png" /> </ItemGroup> <ItemGroup> @@ -358,8 +353,6 @@ </ItemGroup> <ItemGroup> <Resource Include="Resources\Images\folder.jpg" /> - <Resource Include="Resources\Images\mblogoblackfull.png" /> - <Resource Include="Resources\Images\mblogowhitefull.png" /> </ItemGroup> <ItemGroup> <BootstrapperPackage Include=".NETFramework,Version=v4.5"> diff --git a/MediaBrowser.ServerApplication/Resources/Images/mblogoblackfull.png b/MediaBrowser.ServerApplication/Resources/Images/mblogoblackfull.png Binary files differdeleted file mode 100644 index 340f12d9d..000000000 --- a/MediaBrowser.ServerApplication/Resources/Images/mblogoblackfull.png +++ /dev/null diff --git a/MediaBrowser.ServerApplication/Resources/Images/mblogowhitefull.png b/MediaBrowser.ServerApplication/Resources/Images/mblogowhitefull.png Binary files differdeleted file mode 100644 index 48c8f72d3..000000000 --- a/MediaBrowser.ServerApplication/Resources/Images/mblogowhitefull.png +++ /dev/null diff --git a/MediaBrowser.ServerApplication/Resources/Images/starEmpty.png b/MediaBrowser.ServerApplication/Resources/Images/starEmpty.png Binary files differdeleted file mode 100644 index b8355c7b1..000000000 --- a/MediaBrowser.ServerApplication/Resources/Images/starEmpty.png +++ /dev/null diff --git a/MediaBrowser.ServerApplication/Resources/Images/starFull.png b/MediaBrowser.ServerApplication/Resources/Images/starFull.png Binary files differdeleted file mode 100644 index d5df24102..000000000 --- a/MediaBrowser.ServerApplication/Resources/Images/starFull.png +++ /dev/null diff --git a/MediaBrowser.ServerApplication/Resources/Images/starHalf.png b/MediaBrowser.ServerApplication/Resources/Images/starHalf.png Binary files differdeleted file mode 100644 index e3251f0dd..000000000 --- a/MediaBrowser.ServerApplication/Resources/Images/starHalf.png +++ /dev/null diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 7db97e660..d139adfc3 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -3172,144 +3172,6 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }; /** - Gets a variety of item counts that a person appears in - */ - self.getPersonItemCounts = function (userId, name) { - - if (!userId) { - throw new Error("null userId"); - } - - if (!name) { - throw new Error("null name"); - } - - var url = self.getUrl("Persons/" + self.encodeName(name) + "/Counts", { - userId: userId - }); - - return self.ajax({ - type: "GET", - url: url, - dataType: "json" - }); - }; - - /** - Gets a variety of item counts that a genre appears in - */ - self.getGenreItemCounts = function (userId, name) { - - if (!userId) { - throw new Error("null userId"); - } - - if (!name) { - throw new Error("null name"); - } - - var url = self.getUrl("Genres/" + self.encodeName(name) + "/Counts", { - userId: userId - }); - - return self.ajax({ - type: "GET", - url: url, - dataType: "json" - }); - }; - - self.getMusicGenreItemCounts = function (userId, name) { - - if (!userId) { - throw new Error("null userId"); - } - - if (!name) { - throw new Error("null name"); - } - - var url = self.getUrl("MusicGenres/" + self.encodeName(name) + "/Counts", { - userId: userId - }); - - return self.ajax({ - type: "GET", - url: url, - dataType: "json" - }); - }; - - self.getGameGenreItemCounts = function (userId, name) { - - if (!userId) { - throw new Error("null userId"); - } - - if (!name) { - throw new Error("null name"); - } - - var url = self.getUrl("GameGenres/" + self.encodeName(name) + "/Counts", { - userId: userId - }); - - return self.ajax({ - type: "GET", - url: url, - dataType: "json" - }); - }; - - /** - Gets a variety of item counts that an artist appears in - */ - self.getArtistItemCounts = function (userId, name) { - - if (!userId) { - throw new Error("null userId"); - } - - if (!name) { - throw new Error("null name"); - } - - var url = self.getUrl("Artists/" + self.encodeName(name) + "/Counts", { - userId: userId - }); - - return self.ajax({ - type: "GET", - url: url, - dataType: "json" - }); - }; - - /** - Gets a variety of item counts that a studio appears in - */ - self.getStudioItemCounts = function (userId, name) { - - if (!userId) { - throw new Error("null userId"); - } - - if (!name) { - throw new Error("null name"); - } - - var url = self.getUrl("Studios/" + self.encodeName(name) + "/Counts", { - userId: userId - }); - - return self.ajax({ - type: "GET", - url: url, - dataType: "json" - }); - }; - - /** * Clears a user's personal rating for an item * @param {String} userId * @param {String} itemId diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index b3add5674..0963ca767 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.174" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.175" targetFramework="net45" /> <package id="ServiceStack.Common" version="3.9.58" targetFramework="net45" /> <package id="ServiceStack.Text" version="3.9.58" targetFramework="net45" /> </packages>
\ No newline at end of file diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 744debbcd..0c5360b49 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -237,4 +237,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal |
