aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations/Library
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Server.Implementations/Library')
-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
14 files changed, 1457 insertions, 92 deletions
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);
+ }
+ }
+ }
+}