From a9b041a7e62f408e07d15cb7cf253d76919b6fa1 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 29 Apr 2017 22:37:51 -0400 Subject: [PATCH 1/2] rework refresh queue --- .../Channels/ChannelManager.cs | 2 +- .../Collections/CollectionManager.cs | 6 +- .../Library/LibraryManager.cs | 11 +- .../LiveTv/LiveTvManager.cs | 7 +- .../Playlists/PlaylistManager.cs | 6 +- MediaBrowser.Api/ItemRefreshService.cs | 9 +- MediaBrowser.Api/Subtitles/SubtitleService.cs | 2 +- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- .../Providers/IProviderManager.cs | 11 +- .../Manager/GenericPriorityQueue.cs | 426 ++++++++++++++++++ .../Manager/GenericPriorityQueueNode.cs | 28 ++ .../Manager/IFixedSizePriorityQueue.cs | 28 ++ .../Manager/IPriorityQueue.cs | 59 +++ .../Manager/ProviderManager.cs | 22 +- .../Manager/SimplePriorityQueue.cs | 243 ++++++++++ .../MediaBrowser.Providers.csproj | 5 + .../Movies/FanartMovieImageProvider.cs | 1 - .../Movies/MovieDbProvider.cs | 7 - .../Music/AudioDbArtistProvider.cs | 2 - .../Music/FanArtArtistProvider.cs | 2 - .../Music/MusicBrainzAlbumProvider.cs | 6 - MediaBrowser.Providers/Omdb/OmdbProvider.cs | 2 - .../Subtitles/SubtitleManager.cs | 7 +- .../TV/FanArt/FanartSeriesProvider.cs | 1 - .../TV/TheMovieDb/MovieDbProviderBase.cs | 3 +- .../TV/TheTVDB/TvdbEpisodeImageProvider.cs | 3 +- .../TV/TheTVDB/TvdbEpisodeProvider.cs | 3 +- .../TV/TheTVDB/TvdbPrescanTask.cs | 2 - .../TV/TheTVDB/TvdbSeasonImageProvider.cs | 3 +- .../TV/TheTVDB/TvdbSeriesImageProvider.cs | 3 +- .../TV/TheTVDB/TvdbSeriesProvider.cs | 5 - 31 files changed, 838 insertions(+), 79 deletions(-) create mode 100644 MediaBrowser.Providers/Manager/GenericPriorityQueue.cs create mode 100644 MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs create mode 100644 MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs create mode 100644 MediaBrowser.Providers/Manager/IPriorityQueue.cs create mode 100644 MediaBrowser.Providers/Manager/SimplePriorityQueue.cs diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index 9ac6459a1..de76157ba 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -1416,7 +1416,7 @@ namespace Emby.Server.Implementations.Channels if (!_refreshedItems.ContainsKey(program.Id)) { _refreshedItems.TryAdd(program.Id, true); - _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem)); + _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.Low); } } diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index d0bd76c35..9c26655fc 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Collections } else { - _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem)); + _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.High); } EventHelper.FireEventIfNotNull(CollectionCreated, this, new CollectionCreatedEventArgs @@ -191,7 +191,7 @@ namespace Emby.Server.Implementations.Collections await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - _providerManager.QueueRefresh(collection.Id, refreshOptions); + _providerManager.QueueRefresh(collection.Id, refreshOptions, RefreshPriority.High); if (fireEvent) { @@ -244,7 +244,7 @@ namespace Emby.Server.Implementations.Collections collection.UpdateRatingToContent(); await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); - _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem)); + _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.High); EventHelper.FireEventIfNotNull(ItemsRemovedFromCollection, this, new CollectionModifiedEventArgs { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 16a73f392..684ad6284 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -2126,7 +2126,8 @@ namespace Emby.Server.Implementations.Library // Not sure why this is necessary but need to figure it out // View images are not getting utilized without this ForceSave = true - }); + + }, RefreshPriority.Normal); } return item; @@ -2188,7 +2189,8 @@ namespace Emby.Server.Implementations.Library { // Need to force save to increment DateLastSaved ForceSave = true - }); + + }, RefreshPriority.Normal); } return item; @@ -2252,7 +2254,8 @@ namespace Emby.Server.Implementations.Library { // Need to force save to increment DateLastSaved ForceSave = true - }); + + }, RefreshPriority.Normal); } return item; @@ -2328,7 +2331,7 @@ namespace Emby.Server.Implementations.Library { // Need to force save to increment DateLastSaved ForceSave = true - }); + }, RefreshPriority.Normal); } return item; diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index a898d3084..fa86ac36d 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -857,7 +857,8 @@ namespace Emby.Server.Implementations.LiveTv _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem) { MetadataRefreshMode = metadataRefreshMode - }); + + }, RefreshPriority.Normal); } return item.Id; @@ -1395,11 +1396,11 @@ namespace Emby.Server.Implementations.LiveTv foreach (var program in newPrograms) { - _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem)); + _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.Low); } foreach (var program in updatedPrograms) { - _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem)); + _providerManager.QueueRefresh(program.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.Low); } currentChannel.IsMovie = isMovie; diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 386da73c6..18042b587 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -201,7 +201,8 @@ namespace Emby.Server.Implementations.Playlists _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(_fileSystem) { ForceSave = true - }); + + }, RefreshPriority.High); } public async Task RemoveFromPlaylist(string playlistId, IEnumerable entryIds) @@ -228,7 +229,8 @@ namespace Emby.Server.Implementations.Playlists _providerManager.QueueRefresh(playlist.Id, new MetadataRefreshOptions(_fileSystem) { ForceSave = true - }); + + }, RefreshPriority.High); } public async Task MoveItem(string playlistId, string entryId, int newIndex) diff --git a/MediaBrowser.Api/ItemRefreshService.cs b/MediaBrowser.Api/ItemRefreshService.cs index 81547637e..bca292241 100644 --- a/MediaBrowser.Api/ItemRefreshService.cs +++ b/MediaBrowser.Api/ItemRefreshService.cs @@ -62,14 +62,7 @@ namespace MediaBrowser.Api var options = GetRefreshOptions(request); - if (item is Folder) - { - _providerManager.QueueRefresh(item.Id, options); - } - else - { - _providerManager.RefreshFullItem(item, options, CancellationToken.None); - } + _providerManager.QueueRefresh(item.Id, options, RefreshPriority.High); } private MetadataRefreshOptions GetRefreshOptions(BaseRefreshRequest request) diff --git a/MediaBrowser.Api/Subtitles/SubtitleService.cs b/MediaBrowser.Api/Subtitles/SubtitleService.cs index 47d442e79..798004a5e 100644 --- a/MediaBrowser.Api/Subtitles/SubtitleService.cs +++ b/MediaBrowser.Api/Subtitles/SubtitleService.cs @@ -288,7 +288,7 @@ namespace MediaBrowser.Api.Subtitles await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None) .ConfigureAwait(false); - _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(_fileSystem)); + _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(_fileSystem), RefreshPriority.High); } catch (Exception ex) { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 31b0783b2..04ddb2729 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1817,7 +1817,7 @@ namespace MediaBrowser.Controller.Entities /// Task. public virtual Task ChangedExternally() { - ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(FileSystem)); + ProviderManager.QueueRefresh(Id, new MetadataRefreshOptions(FileSystem), RefreshPriority.High); return Task.FromResult(true); } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index f4d45c7e0..c0bc90214 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -20,9 +20,7 @@ namespace MediaBrowser.Controller.Providers /// /// Queues the refresh. /// - /// The item identifier. - /// The options. - void QueueRefresh(Guid itemId, MetadataRefreshOptions options); + void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority); /// /// Refreshes the full item. @@ -161,4 +159,11 @@ namespace MediaBrowser.Controller.Providers /// Task{HttpResponseInfo}. Task GetSearchImage(string providerName, string url, CancellationToken cancellationToken); } + + public enum RefreshPriority + { + High = 0, + Normal = 1, + Low = 2 + } } \ No newline at end of file diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs new file mode 100644 index 000000000..18998fd59 --- /dev/null +++ b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + /// A copy of StablePriorityQueue which also has generic priority-type + /// + /// The values in the queue. Must extend the GenericPriorityQueue class + /// The priority-type. Must extend IComparable<TPriority> + public sealed class GenericPriorityQueue : IFixedSizePriorityQueue + where TItem : GenericPriorityQueueNode + where TPriority : IComparable + { + private int _numNodes; + private TItem[] _nodes; + private long _numNodesEverEnqueued; + + /// + /// Instantiate a new Priority Queue + /// + /// The max nodes ever allowed to be enqueued (going over this will cause undefined behavior) + public GenericPriorityQueue(int maxNodes) + { +#if DEBUG + if (maxNodes <= 0) + { + throw new InvalidOperationException("New queue size cannot be smaller than 1"); + } +#endif + + _numNodes = 0; + _nodes = new TItem[maxNodes + 1]; + _numNodesEverEnqueued = 0; + } + + /// + /// Returns the number of nodes in the queue. + /// O(1) + /// + public int Count + { + get + { + return _numNodes; + } + } + + /// + /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), + /// attempting to enqueue another item will cause undefined behavior. O(1) + /// + public int MaxSize + { + get + { + return _nodes.Length - 1; + } + } + + /// + /// Removes every node from the queue. + /// O(n) (So, don't do this often!) + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public void Clear() + { + Array.Clear(_nodes, 1, _numNodes); + _numNodes = 0; + } + + /// + /// Returns (in O(1)!) whether the given node is in the queue. O(1) + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public bool Contains(TItem node) + { +#if DEBUG + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (node.QueueIndex < 0 || node.QueueIndex >= _nodes.Length) + { + throw new InvalidOperationException("node.QueueIndex has been corrupted. Did you change it manually? Or add this node to another queue?"); + } +#endif + + return (_nodes[node.QueueIndex] == node); + } + + /// + /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. + /// If the queue is full, the result is undefined. + /// If the node is already enqueued, the result is undefined. + /// O(log n) + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public void Enqueue(TItem node, TPriority priority) + { +#if DEBUG + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (_numNodes >= _nodes.Length - 1) + { + throw new InvalidOperationException("Queue is full - node cannot be added: " + node); + } + if (Contains(node)) + { + throw new InvalidOperationException("Node is already enqueued: " + node); + } +#endif + + node.Priority = priority; + _numNodes++; + _nodes[_numNodes] = node; + node.QueueIndex = _numNodes; + node.InsertionIndex = _numNodesEverEnqueued++; + CascadeUp(_nodes[_numNodes]); + } + +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void Swap(TItem node1, TItem node2) + { + //Swap the nodes + _nodes[node1.QueueIndex] = node2; + _nodes[node2.QueueIndex] = node1; + + //Swap their indicies + int temp = node1.QueueIndex; + node1.QueueIndex = node2.QueueIndex; + node2.QueueIndex = temp; + } + + //Performance appears to be slightly better when this is NOT inlined o_O + private void CascadeUp(TItem node) + { + //aka Heapify-up + int parent = node.QueueIndex / 2; + while (parent >= 1) + { + TItem parentNode = _nodes[parent]; + if (HasHigherPriority(parentNode, node)) + break; + + //Node has lower priority value, so move it up the heap + Swap(node, parentNode); //For some reason, this is faster with Swap() rather than (less..?) individual operations, like in CascadeDown() + + parent = node.QueueIndex / 2; + } + } + +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void CascadeDown(TItem node) + { + //aka Heapify-down + TItem newParent; + int finalQueueIndex = node.QueueIndex; + while (true) + { + newParent = node; + int childLeftIndex = 2 * finalQueueIndex; + + //Check if the left-child is higher-priority than the current node + if (childLeftIndex > _numNodes) + { + //This could be placed outside the loop, but then we'd have to check newParent != node twice + node.QueueIndex = finalQueueIndex; + _nodes[finalQueueIndex] = node; + break; + } + + TItem childLeft = _nodes[childLeftIndex]; + if (HasHigherPriority(childLeft, newParent)) + { + newParent = childLeft; + } + + //Check if the right-child is higher-priority than either the current node or the left child + int childRightIndex = childLeftIndex + 1; + if (childRightIndex <= _numNodes) + { + TItem childRight = _nodes[childRightIndex]; + if (HasHigherPriority(childRight, newParent)) + { + newParent = childRight; + } + } + + //If either of the children has higher (smaller) priority, swap and continue cascading + if (newParent != node) + { + //Move new parent to its new index. node will be moved once, at the end + //Doing it this way is one less assignment operation than calling Swap() + _nodes[finalQueueIndex] = newParent; + + int temp = newParent.QueueIndex; + newParent.QueueIndex = finalQueueIndex; + finalQueueIndex = temp; + } + else + { + //See note above + node.QueueIndex = finalQueueIndex; + _nodes[finalQueueIndex] = node; + break; + } + } + } + + /// + /// Returns true if 'higher' has higher priority than 'lower', false otherwise. + /// Note that calling HasHigherPriority(node, node) (ie. both arguments the same node) will return false + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private bool HasHigherPriority(TItem higher, TItem lower) + { + var cmp = higher.Priority.CompareTo(lower.Priority); + return (cmp < 0 || (cmp == 0 && higher.InsertionIndex < lower.InsertionIndex)); + } + + /// + /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. + /// If queue is empty, result is undefined + /// O(log n) + /// + public TItem Dequeue() + { +#if DEBUG + if (_numNodes <= 0) + { + throw new InvalidOperationException("Cannot call Dequeue() on an empty queue"); + } + + if (!IsValidQueue()) + { + throw new InvalidOperationException("Queue has been corrupted (Did you update a node priority manually instead of calling UpdatePriority()?" + + "Or add the same node to two different queues?)"); + } +#endif + + TItem returnMe = _nodes[1]; + Remove(returnMe); + return returnMe; + } + + /// + /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. + /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior + /// O(n) + /// + public void Resize(int maxNodes) + { +#if DEBUG + if (maxNodes <= 0) + { + throw new InvalidOperationException("Queue size cannot be smaller than 1"); + } + + if (maxNodes < _numNodes) + { + throw new InvalidOperationException("Called Resize(" + maxNodes + "), but current queue contains " + _numNodes + " nodes"); + } +#endif + + TItem[] newArray = new TItem[maxNodes + 1]; + int highestIndexToCopy = Math.Min(maxNodes, _numNodes); + for (int i = 1; i <= highestIndexToCopy; i++) + { + newArray[i] = _nodes[i]; + } + _nodes = newArray; + } + + /// + /// Returns the head of the queue, without removing it (use Dequeue() for that). + /// If the queue is empty, behavior is undefined. + /// O(1) + /// + public TItem First + { + get + { +#if DEBUG + if (_numNodes <= 0) + { + throw new InvalidOperationException("Cannot call .First on an empty queue"); + } +#endif + + return _nodes[1]; + } + } + + /// + /// This method must be called on a node every time its priority changes while it is in the queue. + /// Forgetting to call this method will result in a corrupted queue! + /// Calling this method on a node not in the queue results in undefined behavior + /// O(log n) + /// +#if NET_VERSION_4_5 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public void UpdatePriority(TItem node, TPriority priority) + { +#if DEBUG + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (!Contains(node)) + { + throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + node); + } +#endif + + node.Priority = priority; + OnNodeUpdated(node); + } + + private void OnNodeUpdated(TItem node) + { + //Bubble the updated node up or down as appropriate + int parentIndex = node.QueueIndex / 2; + TItem parentNode = _nodes[parentIndex]; + + if (parentIndex > 0 && HasHigherPriority(node, parentNode)) + { + CascadeUp(node); + } + else + { + //Note that CascadeDown will be called if parentNode == node (that is, node is the root) + CascadeDown(node); + } + } + + /// + /// Removes a node from the queue. The node does not need to be the head of the queue. + /// If the node is not in the queue, the result is undefined. If unsure, check Contains() first + /// O(log n) + /// + public void Remove(TItem node) + { +#if DEBUG + if (node == null) + { + throw new ArgumentNullException("node"); + } + if (!Contains(node)) + { + throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + node); + } +#endif + + //If the node is already the last node, we can remove it immediately + if (node.QueueIndex == _numNodes) + { + _nodes[_numNodes] = null; + _numNodes--; + return; + } + + //Swap the node with the last node + TItem formerLastNode = _nodes[_numNodes]; + Swap(node, formerLastNode); + _nodes[_numNodes] = null; + _numNodes--; + + //Now bubble formerLastNode (which is no longer the last node) up or down as appropriate + OnNodeUpdated(formerLastNode); + } + + public IEnumerator GetEnumerator() + { + for (int i = 1; i <= _numNodes; i++) + yield return _nodes[i]; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Should not be called in production code. + /// Checks to make sure the queue is still in a valid state. Used for testing/debugging the queue. + /// + public bool IsValidQueue() + { + for (int i = 1; i < _nodes.Length; i++) + { + if (_nodes[i] != null) + { + int childLeftIndex = 2 * i; + if (childLeftIndex < _nodes.Length && _nodes[childLeftIndex] != null && HasHigherPriority(_nodes[childLeftIndex], _nodes[i])) + return false; + + int childRightIndex = childLeftIndex + 1; + if (childRightIndex < _nodes.Length && _nodes[childRightIndex] != null && HasHigherPriority(_nodes[childRightIndex], _nodes[i])) + return false; + } + } + return true; + } + } +} diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs new file mode 100644 index 000000000..e6e93e443 --- /dev/null +++ b/MediaBrowser.Providers/Manager/GenericPriorityQueueNode.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + public class GenericPriorityQueueNode + { + /// + /// The Priority to insert this node at. Must be set BEFORE adding a node to the queue (ideally just once, in the node's constructor). + /// Should not be manually edited once the node has been enqueued - use queue.UpdatePriority() instead + /// + public TPriority Priority { get; protected internal set; } + + /// + /// Represents the current position in the queue + /// + public int QueueIndex { get; internal set; } + + /// + /// Represents the order the node was inserted in + /// + public long InsertionIndex { get; internal set; } + } +} diff --git a/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs b/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs new file mode 100644 index 000000000..8da88e1c6 --- /dev/null +++ b/MediaBrowser.Providers/Manager/IFixedSizePriorityQueue.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + /// A helper-interface only needed to make writing unit tests a bit easier (hence the 'internal' access modifier) + /// + internal interface IFixedSizePriorityQueue : IPriorityQueue + where TPriority : IComparable + { + /// + /// Resize the queue so it can accept more nodes. All currently enqueued nodes are remain. + /// Attempting to decrease the queue size to a size too small to hold the existing nodes results in undefined behavior + /// + void Resize(int maxNodes); + + /// + /// Returns the maximum number of items that can be enqueued at once in this queue. Once you hit this number (ie. once Count == MaxSize), + /// attempting to enqueue another item will cause undefined behavior. + /// + int MaxSize { get; } + } +} \ No newline at end of file diff --git a/MediaBrowser.Providers/Manager/IPriorityQueue.cs b/MediaBrowser.Providers/Manager/IPriorityQueue.cs new file mode 100644 index 000000000..11f2c6214 --- /dev/null +++ b/MediaBrowser.Providers/Manager/IPriorityQueue.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + /// The IPriorityQueue interface. This is mainly here for purists, and in case I decide to add more implementations later. + /// For speed purposes, it is actually recommended that you *don't* access the priority queue through this interface, since the JIT can + /// (theoretically?) optimize method calls from concrete-types slightly better. + /// + public interface IPriorityQueue : IEnumerable + where TPriority : IComparable + { + /// + /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. + /// See implementation for how duplicates are handled. + /// + void Enqueue(TItem node, TPriority priority); + + /// + /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. + /// + TItem Dequeue(); + + /// + /// Removes every node from the queue. + /// + void Clear(); + + /// + /// Returns whether the given node is in the queue. + /// + bool Contains(TItem node); + + /// + /// Removes a node from the queue. The node does not need to be the head of the queue. + /// + void Remove(TItem node); + + /// + /// Call this method to change the priority of a node. + /// + void UpdatePriority(TItem node, TPriority priority); + + /// + /// Returns the head of the queue, without removing it (use Dequeue() for that). + /// + TItem First { get; } + + /// + /// Returns the number of nodes in the queue. + /// + int Count { get; } + } +} diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 0b8dca2eb..f08a7d3c3 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -24,6 +24,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; +using Priority_Queue; namespace MediaBrowser.Providers.Manager { @@ -577,7 +578,6 @@ namespace MediaBrowser.Providers.Manager return SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase))); } - private readonly SemaphoreSlim _saveLock = new SemaphoreSlim(1, 1); /// /// Saves the metadata. /// @@ -607,8 +607,6 @@ namespace MediaBrowser.Providers.Manager continue; } - await _saveLock.WaitAsync().ConfigureAwait(false); - try { _libraryMonitor.ReportFileSystemChangeBeginning(path); @@ -620,7 +618,6 @@ namespace MediaBrowser.Providers.Manager } finally { - _saveLock.Release(); _libraryMonitor.ReportFileSystemChangeComplete(path, false); } } @@ -851,20 +848,20 @@ namespace MediaBrowser.Providers.Manager }); } - private readonly ConcurrentQueue> _refreshQueue = - new ConcurrentQueue>(); + private readonly SimplePriorityQueue> _refreshQueue = + new SimplePriorityQueue>(); private readonly object _refreshQueueLock = new object(); private bool _isProcessingRefreshQueue; - public void QueueRefresh(Guid id, MetadataRefreshOptions options) + public void QueueRefresh(Guid id, MetadataRefreshOptions options, RefreshPriority priority) { if (_disposed) { return; } - _refreshQueue.Enqueue(new Tuple(id, options)); + _refreshQueue.Enqueue(new Tuple(id, options), (int)priority); lock (_refreshQueueLock) { @@ -876,12 +873,19 @@ namespace MediaBrowser.Providers.Manager } } + private bool TryDequeue(out Tuple item) + { + item = _refreshQueue.Dequeue(); + + return item != null; + } + private async Task StartProcessingRefreshQueue() { Tuple refreshItem; var libraryManager = _libraryManagerFactory(); - while (_refreshQueue.TryDequeue(out refreshItem)) + while (TryDequeue(out refreshItem)) { if (_disposed) { diff --git a/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs b/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs new file mode 100644 index 000000000..6435aa06b --- /dev/null +++ b/MediaBrowser.Providers/Manager/SimplePriorityQueue.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Priority_Queue +{ + /// + /// Credit: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp + /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than + /// FastPriorityQueue + /// + /// The type to enqueue + /// The priority-type to use for nodes. Must extend IComparable<TPriority> + public class SimplePriorityQueue : IPriorityQueue + where TPriority : IComparable + { + private class SimpleNode : GenericPriorityQueueNode + { + public TItem Data { get; private set; } + + public SimpleNode(TItem data) + { + Data = data; + } + } + + private const int INITIAL_QUEUE_SIZE = 10; + private readonly GenericPriorityQueue _queue; + + public SimplePriorityQueue() + { + _queue = new GenericPriorityQueue(INITIAL_QUEUE_SIZE); + } + + /// + /// Given an item of type T, returns the exist SimpleNode in the queue + /// + private SimpleNode GetExistingNode(TItem item) + { + var comparer = EqualityComparer.Default; + foreach (var node in _queue) + { + if (comparer.Equals(node.Data, item)) + { + return node; + } + } + throw new InvalidOperationException("Item cannot be found in queue: " + item); + } + + /// + /// Returns the number of nodes in the queue. + /// O(1) + /// + public int Count + { + get + { + lock (_queue) + { + return _queue.Count; + } + } + } + + + /// + /// Returns the head of the queue, without removing it (use Dequeue() for that). + /// Throws an exception when the queue is empty. + /// O(1) + /// + public TItem First + { + get + { + lock (_queue) + { + if (_queue.Count <= 0) + { + throw new InvalidOperationException("Cannot call .First on an empty queue"); + } + + SimpleNode first = _queue.First; + return (first != null ? first.Data : default(TItem)); + } + } + } + + /// + /// Removes every node from the queue. + /// O(n) + /// + public void Clear() + { + lock (_queue) + { + _queue.Clear(); + } + } + + /// + /// Returns whether the given item is in the queue. + /// O(n) + /// + public bool Contains(TItem item) + { + lock (_queue) + { + var comparer = EqualityComparer.Default; + foreach (var node in _queue) + { + if (comparer.Equals(node.Data, item)) + { + return true; + } + } + return false; + } + } + + /// + /// Removes the head of the queue (node with minimum priority; ties are broken by order of insertion), and returns it. + /// If queue is empty, throws an exception + /// O(log n) + /// + public TItem Dequeue() + { + lock (_queue) + { + if (_queue.Count <= 0) + { + throw new InvalidOperationException("Cannot call Dequeue() on an empty queue"); + } + + SimpleNode node = _queue.Dequeue(); + return node.Data; + } + } + + /// + /// Enqueue a node to the priority queue. Lower values are placed in front. Ties are broken by first-in-first-out. + /// This queue automatically resizes itself, so there's no concern of the queue becoming 'full'. + /// Duplicates are allowed. + /// O(log n) + /// + public void Enqueue(TItem item, TPriority priority) + { + lock (_queue) + { + SimpleNode node = new SimpleNode(item); + if (_queue.Count == _queue.MaxSize) + { + _queue.Resize(_queue.MaxSize * 2 + 1); + } + _queue.Enqueue(node, priority); + } + } + + /// + /// Removes an item from the queue. The item does not need to be the head of the queue. + /// If the item is not in the queue, an exception is thrown. If unsure, check Contains() first. + /// If multiple copies of the item are enqueued, only the first one is removed. + /// O(n) + /// + public void Remove(TItem item) + { + lock (_queue) + { + try + { + _queue.Remove(GetExistingNode(item)); + } + catch (InvalidOperationException ex) + { + throw new InvalidOperationException("Cannot call Remove() on a node which is not enqueued: " + item, ex); + } + } + } + + /// + /// Call this method to change the priority of an item. + /// Calling this method on a item not in the queue will throw an exception. + /// If the item is enqueued multiple times, only the first one will be updated. + /// (If your requirements are complex enough that you need to enqueue the same item multiple times and be able + /// to update all of them, please wrap your items in a wrapper class so they can be distinguished). + /// O(n) + /// + public void UpdatePriority(TItem item, TPriority priority) + { + lock (_queue) + { + try + { + SimpleNode updateMe = GetExistingNode(item); + _queue.UpdatePriority(updateMe, priority); + } + catch (InvalidOperationException ex) + { + throw new InvalidOperationException("Cannot call UpdatePriority() on a node which is not enqueued: " + item, ex); + } + } + } + + public IEnumerator GetEnumerator() + { + List queueData = new List(); + lock (_queue) + { + //Copy to a separate list because we don't want to 'yield return' inside a lock + foreach (var node in _queue) + { + queueData.Add(node.Data); + } + } + + return queueData.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public bool IsValidQueue() + { + lock (_queue) + { + return _queue.IsValidQueue(); + } + } + } + + /// + /// A simplified priority queue implementation. Is stable, auto-resizes, and thread-safe, at the cost of being slightly slower than + /// FastPriorityQueue + /// This class is kept here for backwards compatibility. It's recommended you use Simple + /// + /// The type to enqueue + public class SimplePriorityQueue : SimplePriorityQueue { } +} \ No newline at end of file diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index fe554545f..9d20ec423 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -65,10 +65,15 @@ + + + + + diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index dd2cad1f9..946d6e68d 100644 --- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -284,7 +284,6 @@ namespace MediaBrowser.Providers.Movies using (var response = await _httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = FanartArtistProvider.Current.FanArtResourcePool, CancellationToken = cancellationToken, BufferContent = true diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 8e4b86519..b3cf0541e 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -30,8 +30,6 @@ namespace MediaBrowser.Providers.Movies /// public class MovieDbProvider : IRemoteMetadataProvider, IDisposable, IHasOrder { - internal readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1); - internal static MovieDbProvider Current { get; private set; } private readonly IJsonSerializer _jsonSerializer; @@ -137,10 +135,6 @@ namespace MediaBrowser.Providers.Movies /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { - if (dispose) - { - MovieDbResourcePool.Dispose(); - } } /// @@ -431,7 +425,6 @@ namespace MediaBrowser.Providers.Movies await Task.Delay(Convert.ToInt32(delayMs)).ConfigureAwait(false); } - options.ResourcePool = MovieDbResourcePool; _lastRequestTicks = DateTime.UtcNow.Ticks; options.BufferContent = true; diff --git a/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs index 0f0c31e6e..bad7cf2ba 100644 --- a/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs +++ b/MediaBrowser.Providers/Music/AudioDbArtistProvider.cs @@ -27,7 +27,6 @@ namespace MediaBrowser.Providers.Music public static AudioDbArtistProvider Current; - public SemaphoreSlim AudioDbResourcePool = new SemaphoreSlim(2, 2); private const string ApiKey = "49jhsf8248yfahka89724011"; public const string BaseUrl = "http://www.theaudiodb.com/api/v1/json/" + ApiKey; @@ -151,7 +150,6 @@ namespace MediaBrowser.Providers.Music using (var response = await _httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = AudioDbResourcePool, CancellationToken = cancellationToken, BufferContent = true diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 6fd0d82bd..37e38e048 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -27,7 +27,6 @@ namespace MediaBrowser.Providers.Music { public class FanartArtistProvider : IRemoteImageProvider, IHasOrder { - internal readonly SemaphoreSlim FanArtResourcePool = new SemaphoreSlim(3, 3); internal const string ApiKey = "5c6b04c68e904cfed1e6cbc9a9e683d4"; private const string FanArtBaseUrl = "https://webservice.fanart.tv/v3.1/music/{1}?api_key={0}"; @@ -255,7 +254,6 @@ namespace MediaBrowser.Providers.Music using (var response = await _httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = FanArtResourcePool, CancellationToken = cancellationToken, BufferContent = true diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 4aefb62c8..b77fcf1b2 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -548,11 +548,6 @@ namespace MediaBrowser.Providers.Music return null; } - /// - /// The _music brainz resource pool - /// - private readonly SemaphoreSlim _musicBrainzResourcePool = new SemaphoreSlim(1, 1); - private long _lastMbzUrlQueryTicks = 0; private List _mbzUrls = null; private MbzUrl _chosenUrl; @@ -656,7 +651,6 @@ namespace MediaBrowser.Providers.Music Url = url, CancellationToken = cancellationToken, UserAgent = _appHost.Name + "/" + _appHost.ApplicationVersion, - ResourcePool = _musicBrainzResourcePool, BufferContent = throttleMs > 0 }; diff --git a/MediaBrowser.Providers/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Omdb/OmdbProvider.cs index 2c368c97b..474fdf14c 100644 --- a/MediaBrowser.Providers/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Omdb/OmdbProvider.cs @@ -19,7 +19,6 @@ namespace MediaBrowser.Providers.Omdb { public class OmdbProvider { - internal static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(1, 1); private readonly IJsonSerializer _jsonSerializer; private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; @@ -330,7 +329,6 @@ namespace MediaBrowser.Providers.Omdb return httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = ResourcePool, CancellationToken = cancellationToken, BufferContent = true, EnableDefaultUserAgent = true diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index cd741bed5..15f4f654f 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -256,12 +256,7 @@ namespace MediaBrowser.Providers.Subtitles _monitor.ReportFileSystemChangeComplete(path, false); } - return _libraryManager.GetItemById(itemId).RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - ImageRefreshMode = ImageRefreshMode.ValidationOnly, - MetadataRefreshMode = MetadataRefreshMode.ValidationOnly - - }, CancellationToken.None); + return _libraryManager.GetItemById(itemId).ChangedExternally(); } public Task GetRemoteSubtitles(string id, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs index 8db3eaa79..d50e12a27 100644 --- a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs @@ -319,7 +319,6 @@ namespace MediaBrowser.Providers.TV using (var response = await _httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = FanartArtistProvider.Current.FanArtResourcePool, CancellationToken = cancellationToken, BufferContent = true diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs index 38831feb6..764b1e019 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs @@ -142,8 +142,7 @@ namespace MediaBrowser.Providers.TV return _httpClient.GetResponse(new HttpRequestOptions { CancellationToken = cancellationToken, - Url = url, - ResourcePool = MovieDbProvider.Current.MovieDbResourcePool + Url = url }); } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs index 989748846..030150e4d 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs @@ -175,8 +175,7 @@ namespace MediaBrowser.Providers.TV return _httpClient.GetResponse(new HttpRequestOptions { CancellationToken = cancellationToken, - Url = url, - ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool + Url = url }); } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs index 4a52b972f..3039968b9 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs @@ -919,8 +919,7 @@ namespace MediaBrowser.Providers.TV return _httpClient.GetResponse(new HttpRequestOptions { CancellationToken = cancellationToken, - Url = url, - ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool + Url = url }); } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs index 72bd62d9f..e8ed05225 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -143,7 +143,6 @@ namespace MediaBrowser.Providers.TV Url = ServerTimeUrl, CancellationToken = cancellationToken, EnableHttpCompression = true, - ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool, BufferContent = false }).ConfigureAwait(false)) @@ -240,7 +239,6 @@ namespace MediaBrowser.Providers.TV Url = string.Format(UpdatesUrl, lastUpdateTime), CancellationToken = cancellationToken, EnableHttpCompression = true, - ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool, BufferContent = false }).ConfigureAwait(false)) diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs index e68b7ad1d..daa6e78f5 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs @@ -358,8 +358,7 @@ namespace MediaBrowser.Providers.TV return _httpClient.GetResponse(new HttpRequestOptions { CancellationToken = cancellationToken, - Url = url, - ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool + Url = url }); } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs index cdb9ac51e..50bc6bc74 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs @@ -347,8 +347,7 @@ namespace MediaBrowser.Providers.TV return _httpClient.GetResponse(new HttpRequestOptions { CancellationToken = cancellationToken, - Url = url, - ResourcePool = TvdbSeriesProvider.Current.TvDbResourcePool + Url = url }); } } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index af37c7632..4c5e57a94 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -27,7 +27,6 @@ namespace MediaBrowser.Providers.TV { public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { - internal readonly SemaphoreSlim TvDbResourcePool = new SemaphoreSlim(2, 2); internal static TvdbSeriesProvider Current { get; private set; } private readonly IZipClient _zipClient; private readonly IHttpClient _httpClient; @@ -220,7 +219,6 @@ namespace MediaBrowser.Providers.TV using (var zipStream = await _httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = TvDbResourcePool, CancellationToken = cancellationToken, BufferContent = false @@ -265,7 +263,6 @@ namespace MediaBrowser.Providers.TV using (var result = await _httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = TvDbResourcePool, CancellationToken = cancellationToken, BufferContent = false @@ -520,7 +517,6 @@ namespace MediaBrowser.Providers.TV using (var stream = await _httpClient.Get(new HttpRequestOptions { Url = url, - ResourcePool = TvDbResourcePool, CancellationToken = cancellationToken, BufferContent = false @@ -1651,7 +1647,6 @@ namespace MediaBrowser.Providers.TV { CancellationToken = cancellationToken, Url = url, - ResourcePool = TvDbResourcePool, BufferContent = false }); } From 7ef16a449ae044306227da00a83f5abe062fb893 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 29 Apr 2017 22:44:52 -0400 Subject: [PATCH 2/2] 3.2.13.8 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 2a6e14293..6ebb8664d 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,3 +1,3 @@ using System.Reflection; -[assembly: AssemblyVersion("3.2.13.7")] +[assembly: AssemblyVersion("3.2.13.8")]