de-normalize item by name data. create counts during library scan for fast access.

This commit is contained in:
Luke Pulverenti 2013-09-10 14:56:00 -04:00
parent d078edfb96
commit 740a10a4e3
63 changed files with 1923 additions and 971 deletions

View File

@ -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>
@ -114,49 +92,6 @@ namespace MediaBrowser.Api.UserLibrary
return await DtoService.GetBaseItemDto(item, fields.ToList()).ConfigureAwait(false);
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetArtistsItemCounts request)
{
var 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>
@ -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>

View File

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

View File

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

View File

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

View File

@ -427,28 +427,9 @@ namespace MediaBrowser.Api.UserLibrary
items = items.Where(i =>
{
var audio = i as Audio;
var audio = i as IHasArtist;
if (audio != null)
{
return artists.Any(audio.HasArtist);
}
var album = i as MusicAlbum;
if (album != null)
{
return artists.Any(album.HasArtist);
}
var musicVideo = i as MusicVideo;
if (musicVideo != null)
{
return artists.Any(musicVideo.HasArtist);
}
return false;
return audio != null && artists.Any(audio.HasArtist);
});
}

View File

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

View File

@ -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;
@ -29,28 +26,6 @@ namespace MediaBrowser.Api.UserLibrary
public string PersonTypes { get; set; }
}
/// <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>
@ -136,43 +111,6 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(result);
}
/// <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>
@ -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)
);
}

View File

@ -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>
@ -109,41 +87,6 @@ namespace MediaBrowser.Api.UserLibrary
return await DtoService.GetBaseItemDto(item, fields.ToList()).ConfigureAwait(false);
}
/// <summary>
/// Gets the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
public object Get(GetStudioItemCounts request)
{
var 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>
@ -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>

View File

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

View File

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

View File

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

View File

@ -5,4 +5,9 @@ namespace MediaBrowser.Controller.Entities.Audio
{
string AlbumArtist { get; }
}
public interface IHasArtist
{
bool HasArtist(string name);
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities.Audio
{
public interface IHasMusicGenres
{
List<string> Genres { get; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -235,6 +235,38 @@ namespace MediaBrowser.Controller.Library
/// <returns>Task.</returns>
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>

View File

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

View File

@ -0,0 +1,10 @@

namespace MediaBrowser.Controller.Providers
{
/// <summary>
/// Marker interface for a provider that always runs
/// </summary>
public interface IDynamicInfoProvider
{
}
}

View File

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

View File

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

View File

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

View File

@ -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();
@ -128,74 +128,6 @@ namespace MediaBrowser.Providers
return item.ResolveArgs;
}
/// <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>

View File

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

View File

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

View File

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

View File

@ -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)
{
@ -116,6 +125,41 @@ namespace MediaBrowser.Server.Implementations.Dto
return 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>
@ -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;

View File

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

View File

@ -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;
return new ArtistsValidator(this, _userManager, _logger).Run(progress, cancellationToken);
}
var tasks = new List<Task>();
/// <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);
}
var artists = RootFolder.RecursiveChildren
.OfType<Audio>()
.SelectMany(c =>
{
var list = new List<string>();
/// <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);
}
if (!string.IsNullOrEmpty(c.AlbumArtist))
{
list.Add(c.AlbumArtist);
}
list.AddRange(c.Artists);
/// <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);
}
return list;
})
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var numComplete = 0;
foreach (var artist in artists)
{
if (tasks.Count > maxTasks)
{
await Task.WhenAll(tasks).ConfigureAwait(false);
tasks.Clear();
// Safe cancellation point, when there are no pending tasks
cancellationToken.ThrowIfCancellationRequested();
}
// Avoid accessing the foreach variable within the closure
var currentArtist = artist;
tasks.Add(Task.Run(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
try
{
await GetArtist(currentArtist, cancellationToken, true, true).ConfigureAwait(false);
}
catch (IOException ex)
{
_logger.ErrorException("Error validating Artist {0}", ex, currentArtist);
}
// Update progress
lock (progress)
{
numComplete++;
double percent = numComplete;
percent /= artists.Count;
progress.Report(100 * percent);
}
}));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
progress.Report(100);
_logger.Info("Artist validation complete");
/// <summary>
/// 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);
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -85,11 +85,6 @@ namespace MediaBrowser.Server.Implementations.Session
get { return _activeConnections.Values.OrderByDescending(c => c.LastActivityDate).ToList(); }
}
/// <summary>
/// The _true task result
/// </summary>
private readonly Task _trueTaskResult = Task.FromResult(true);
/// <summary>
/// Logs the user activity.
/// </summary>
@ -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);

View File

@ -336,11 +336,6 @@
<ItemGroup>
<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>
@ -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">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -3171,144 +3171,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

View File

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

View File

@ -237,4 +237,7 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal