diff options
Diffstat (limited to 'MediaBrowser.Providers/Manager/ProviderManager.cs')
| -rw-r--r-- | MediaBrowser.Providers/Manager/ProviderManager.cs | 114 |
1 files changed, 77 insertions, 37 deletions
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 81a299015..f34034964 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -9,6 +9,7 @@ using System.Net.Http; using System.Net.Mime; using System.Threading; using System.Threading.Tasks; +using AsyncKeyedLock; using Jellyfin.Data.Enums; using Jellyfin.Data.Events; using Jellyfin.Extensions; @@ -30,6 +31,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Book = MediaBrowser.Controller.Entities.Book; using Episode = MediaBrowser.Controller.Entities.TV.Episode; @@ -59,6 +61,13 @@ namespace MediaBrowser.Providers.Manager private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new(); private readonly CancellationTokenSource _disposeCancellationTokenSource = new(); private readonly PriorityQueue<(Guid ItemId, MetadataRefreshOptions RefreshOptions), RefreshPriority> _refreshQueue = new(); + private readonly IMemoryCache _memoryCache; + + private readonly AsyncKeyedLocker<string> _imageSaveLock = new(o => + { + o.PoolSize = 20; + o.PoolInitialFill = 1; + }); private IImageProvider[] _imageProviders = Array.Empty<IImageProvider>(); private IMetadataService[] _metadataServices = Array.Empty<IMetadataService>(); @@ -81,6 +90,7 @@ namespace MediaBrowser.Providers.Manager /// <param name="libraryManager">The library manager.</param> /// <param name="baseItemManager">The BaseItem manager.</param> /// <param name="lyricManager">The lyric manager.</param> + /// <param name="memoryCache">The memory cache.</param> public ProviderManager( IHttpClientFactory httpClientFactory, ISubtitleManager subtitleManager, @@ -91,7 +101,8 @@ namespace MediaBrowser.Providers.Manager IServerApplicationPaths appPaths, ILibraryManager libraryManager, IBaseItemManager baseItemManager, - ILyricManager lyricManager) + ILyricManager lyricManager, + IMemoryCache memoryCache) { _logger = logger; _httpClientFactory = httpClientFactory; @@ -103,6 +114,7 @@ namespace MediaBrowser.Providers.Manager _subtitleManager = subtitleManager; _baseItemManager = baseItemManager; _lyricManager = lyricManager; + _memoryCache = memoryCache; } /// <inheritdoc/> @@ -150,52 +162,79 @@ namespace MediaBrowser.Providers.Manager /// <inheritdoc/> public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken) { - var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); - using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); - - if (response.StatusCode != HttpStatusCode.OK) + using (await _imageSaveLock.LockAsync(url, cancellationToken).ConfigureAwait(false)) { - throw new HttpRequestException("Invalid image received.", null, response.StatusCode); - } + if (_memoryCache.TryGetValue(url, out (string ContentType, byte[] ImageContents)? cachedValue) + && cachedValue is not null) + { + var imageContents = cachedValue.Value.ImageContents; + var cacheStream = new MemoryStream(imageContents, 0, imageContents.Length, false); + await using (cacheStream.ConfigureAwait(false)) + { + await SaveImage( + item, + cacheStream, + cachedValue.Value.ContentType, + type, + imageIndex, + cancellationToken).ConfigureAwait(false); + return; + } + } - var contentType = response.Content.Headers.ContentType?.MediaType; + var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); + using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false); - // Workaround for tvheadend channel icons - // TODO: Isolate this hack into the tvh plugin - if (string.IsNullOrEmpty(contentType)) - { - if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase)) + response.EnsureSuccessStatusCode(); + + var contentType = response.Content.Headers.ContentType?.MediaType; + + // Workaround for tvheadend channel icons + // TODO: Isolate this hack into the tvh plugin + if (string.IsNullOrEmpty(contentType)) { - contentType = "image/png"; + if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase)) + { + contentType = MediaTypeNames.Image.Png; + } + else + { + throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode); + } } - else + + // TVDb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons... + if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase)) { - throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode); + throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound); } - } - // TVDb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons... - if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase)) - { - throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound); - } + // some iptv/epg providers don't correctly report media type, extract from url if no extension found + if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType))) + { + // Strip query parameters from url to get actual path. + contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path)); + } - // some iptv/epg providers don't correctly report media type, extract from url if no extension found - if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType))) - { - contentType = MimeTypes.GetMimeType(url); - } + if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + { + throw new HttpRequestException($"Request returned {contentType} instead of an image type", null, HttpStatusCode.NotFound); + } - var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - await using (stream.ConfigureAwait(false)) - { - await SaveImage( - item, - stream, - contentType, - type, - imageIndex, - cancellationToken).ConfigureAwait(false); + var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false); + var stream = new MemoryStream(responseBytes, 0, responseBytes.Length, false); + await using (stream.ConfigureAwait(false)) + { + _memoryCache.Set(url, (contentType, responseBytes), TimeSpan.FromSeconds(10)); + + await SaveImage( + item, + stream, + contentType, + type, + imageIndex, + cancellationToken).ConfigureAwait(false); + } } } @@ -1115,6 +1154,7 @@ namespace MediaBrowser.Providers.Manager } _disposeCancellationTokenSource.Dispose(); + _imageSaveLock.Dispose(); } _disposed = true; |
