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