diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 9682ea8c21..6d619ce048 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -61,7 +61,10 @@ namespace MediaBrowser.Controller.Entities { //we don't directly validate our children //but we do need to clear out the index cache... - IndexCache = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); + if (IndexCache != null) + { + IndexCache.Clear(); + } return NullTaskResult; } @@ -102,7 +105,7 @@ namespace MediaBrowser.Controller.Entities /// Our children are actually just references to the ones in the physical root... /// /// The actual children. - protected override ConcurrentDictionary ActualChildren + protected override IEnumerable ActualChildren { get { @@ -115,16 +118,14 @@ namespace MediaBrowser.Controller.Entities catch (IOException ex) { Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path); - return new ConcurrentDictionary(); + return new BaseItem[] { }; } - var ourChildren = + return LibraryManager.RootFolder.Children .OfType() .Where(i => i.Path != null && locationsDicionary.ContainsKey(i.Path)) .SelectMany(c => c.Children); - - return new ConcurrentDictionary(ourChildren.ToDictionary(i => i.Id)); } } } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index d14ee22284..80c29f6245 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -99,14 +99,11 @@ namespace MediaBrowser.Controller.Entities item.DateModified = DateTime.UtcNow; } - if (!_children.TryAdd(item.Id, item)) - { - throw new InvalidOperationException("Unable to add " + item.Name); - } + _children.Add(item); await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false); - await ItemRepository.SaveChildren(Id, _children.Values.ToList().Select(i => i.Id), cancellationToken).ConfigureAwait(false); + await ItemRepository.SaveChildren(Id, _children.ToList().Select(i => i.Id), cancellationToken).ConfigureAwait(false); } /// @@ -135,18 +132,22 @@ namespace MediaBrowser.Controller.Entities /// Unable to remove + item.Name public Task RemoveChild(BaseItem item, CancellationToken cancellationToken) { - BaseItem removed; + List newChildren; - if (!_children.TryRemove(item.Id, out removed)) + lock (_childrenSyncLock) { - throw new InvalidOperationException("Unable to remove " + item.Name); + newChildren = _children.ToList(); + + newChildren.Remove(item); + + _children = new ConcurrentBag(newChildren); } item.Parent = null; LibraryManager.ReportItemRemoved(item); - return ItemRepository.SaveChildren(Id, _children.Values.ToList().Select(i => i.Id), cancellationToken); + return ItemRepository.SaveChildren(Id, newChildren.Select(i => i.Id), cancellationToken); } #region Indexing @@ -411,9 +412,13 @@ namespace MediaBrowser.Controller.Entities /// IEnumerable{BaseItem}. private IEnumerable GetIndexedChildren(User user, string indexBy) { - List result; + List result = null; var cacheKey = user.Name + indexBy; - IndexCache.TryGetValue(cacheKey, out result); + + if (IndexCache != null) + { + IndexCache.TryGetValue(cacheKey, out result); + } if (result == null) { @@ -438,7 +443,7 @@ namespace MediaBrowser.Controller.Entities /// /// The index cache /// - protected ConcurrentDictionary> IndexCache = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); + protected ConcurrentDictionary> IndexCache; /// /// Builds the index. @@ -449,6 +454,11 @@ namespace MediaBrowser.Controller.Entities /// List{BaseItem}. protected virtual List BuildIndex(string indexKey, Func> indexFunction, User user) { + if (IndexCache == null) + { + IndexCache = new ConcurrentDictionary>(); + } + return indexFunction != null ? IndexCache[user.Name + indexKey] = indexFunction(user).ToList() : null; @@ -459,7 +469,7 @@ namespace MediaBrowser.Controller.Entities /// /// The children /// - private ConcurrentDictionary _children; + private ConcurrentBag _children; /// /// The _children initialized /// @@ -472,22 +482,13 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the actual children. /// /// The actual children. - protected virtual ConcurrentDictionary ActualChildren + protected virtual IEnumerable ActualChildren { get { LazyInitializer.EnsureInitialized(ref _children, ref _childrenInitialized, ref _childrenSyncLock, LoadChildrenInternal); return _children; } - private set - { - _children = value; - - if (value == null) - { - _childrenInitialized = false; - } - } } /// @@ -497,10 +498,7 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public IEnumerable Children { - get - { - return ActualChildren.Values.ToArray(); - } + get { return ActualChildren; } } /// @@ -529,9 +527,9 @@ namespace MediaBrowser.Controller.Entities } } - private ConcurrentDictionary LoadChildrenInternal() + private ConcurrentBag LoadChildrenInternal() { - return new ConcurrentDictionary(LoadChildren().ToDictionary(i => i.Id)); + return new ConcurrentBag(LoadChildren()); } /// @@ -642,7 +640,7 @@ namespace MediaBrowser.Controller.Entities progress.Report(5); //build a dictionary of the current children we have now by Id so we can compare quickly and easily - var currentChildren = ActualChildren; + var currentChildren = ActualChildren.ToDictionary(i => i.Id); //create a list for our validated children var validChildren = new ConcurrentBag>(); @@ -694,22 +692,14 @@ namespace MediaBrowser.Controller.Entities //that's all the new and changed ones - now see if there are any that are missing var itemsRemoved = currentChildren.Values.Except(newChildren).ToList(); + var actualRemovals = new List(); + foreach (var item in itemsRemoved) { if (IsRootPathAvailable(item.Path)) { item.IsOffline = false; - - BaseItem removed; - - if (!_children.TryRemove(item.Id, out removed)) - { - Logger.Error("Failed to remove {0}", item.Name); - } - else - { - LibraryManager.ReportItemRemoved(item); - } + actualRemovals.Add(item); } else { @@ -719,24 +709,30 @@ namespace MediaBrowser.Controller.Entities } } + if (actualRemovals.Count > 0) + { + lock (_childrenSyncLock) + { + _children = new ConcurrentBag(_children.Except(actualRemovals)); + } + } + await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false); foreach (var item in newItems) { - if (!_children.TryAdd(item.Id, item)) - { - Logger.Error("Failed to add {0}", item.Name); - } - else - { - Logger.Debug("** " + item.Name + " Added to library."); - } + _children.Add(item); + + Logger.Debug("** " + item.Name + " Added to library."); } - await ItemRepository.SaveChildren(Id, _children.Values.ToList().Select(i => i.Id), cancellationToken).ConfigureAwait(false); + await ItemRepository.SaveChildren(Id, _children.ToList().Select(i => i.Id), cancellationToken).ConfigureAwait(false); //force the indexes to rebuild next time - IndexCache.Clear(); + if (IndexCache != null) + { + IndexCache.Clear(); + } } progress.Report(10); @@ -949,7 +945,7 @@ namespace MediaBrowser.Controller.Entities } // If indexed is false or the indexing function is null - return children.Where(c => c.IsVisible(user)); + return children.AsParallel().Where(c => c.IsVisible(user)).AsEnumerable(); } /// @@ -970,7 +966,7 @@ namespace MediaBrowser.Controller.Entities if (includeLinkedChildren) { - children = children.DistinctBy(i => i.Id); + children = children.Distinct(); } return children; @@ -1028,7 +1024,7 @@ namespace MediaBrowser.Controller.Entities { throw new ArgumentException("Encountered linked child with empty path."); } - + var item = LibraryManager.RootFolder.FindByPath(info.Path); if (item == null) diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 9b78f39805..27d6953d79 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -341,7 +341,7 @@ namespace MediaBrowser.Server.Implementations.Library items.Add(RootFolder); - // Need to use DistinctBy Id because there could be multiple instances with the same id + // Need to use Distinct because there could be multiple instances with the same id // due to sharing the default library var userRootFolders = _userManager.Users.Select(i => i.RootFolder) .Distinct() @@ -357,9 +357,14 @@ namespace MediaBrowser.Server.Implementations.Library items.AddRange(userFolders); - var disctinctItems = items.DistinctBy(i => i.Id).ToList(); + var dictionary = new ConcurrentDictionary(); - return new ConcurrentDictionary(disctinctItems.ToDictionary(i => i.Id)); + foreach (var item in items) + { + dictionary[item.Id] = item; + } + + return dictionary; } /// diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index e3faea0719..767df9c796 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -72,6 +72,12 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var numComplete = 0; + var userLibraries = _userManager.Users + .Select(i => new Tuple(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType().ToArray())) + .ToArray(); + + var numArtists = allArtists.Count; + foreach (var artist in allArtists) { cancellationToken.ThrowIfCancellationRequested(); @@ -106,14 +112,14 @@ namespace MediaBrowser.Server.Implementations.Library.Validators // Populate counts of items //SetItemCounts(artist, null, allItems.OfType()); - foreach (var user in _userManager.Users.ToArray()) + foreach (var lib in userLibraries) { - SetItemCounts(artist, user.Id, user.RootFolder.GetRecursiveChildren(user).OfType().ToArray()); + SetItemCounts(artist, lib.Item1, lib.Item2); } numComplete++; double percent = numComplete; - percent /= allArtists.Length; + percent /= numArtists; percent *= 20; progress.Report(80 + percent); @@ -180,7 +186,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// The cancellation token. /// The progress. /// Task{Artist[]}. - private async Task GetAllArtists(IEnumerable