diff options
| author | Luke Pulverenti <luke.pulverenti@gmail.com> | 2013-09-10 14:56:00 -0400 |
|---|---|---|
| committer | Luke Pulverenti <luke.pulverenti@gmail.com> | 2013-09-10 14:56:00 -0400 |
| commit | 740a10a4e3f85ffcfd26ec18263d4c78d4b14ecc (patch) | |
| tree | 61462d05ce44c1bb17f48e557b02e14bb480816d /MediaBrowser.Server.Implementations/Library | |
| parent | d078edfb96fe2dcfebdc34e9189f85b0487ac242 (diff) | |
de-normalize item by name data. create counts during library scan for fast access.
Diffstat (limited to 'MediaBrowser.Server.Implementations/Library')
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); + } + } + } +} |
