< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.ItemImageProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/ItemImageProvider.cs
Line coverage
86%
Covered lines: 109
Uncovered lines: 17
Coverable lines: 126
Total lines: 715
Line coverage: 86.5%
Branch coverage
80%
Covered branches: 68
Total branches: 84
Branch coverage: 80.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
RemoveImages(...)87.5%88100%
ValidateImages(...)83.33%66100%
ContainsImages(...)100%1212100%
PruneImages(...)66.66%411241.17%
UpdateReplaceImages(...)75%5466.66%
MergeImages(...)86.36%222295.83%
GetFirstLocalImageInfoByType(...)100%44100%
UpdateMultiImages(...)100%22100%
EnableImageStub(...)50%251255.55%
SaveImageStub(...)100%22100%
SaveImageStub(...)100%11100%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Manager/ItemImageProvider.cs

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.IO;
 6using System.Linq;
 7using System.Net;
 8using System.Net.Http;
 9using System.Net.Mime;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using MediaBrowser.Controller.Entities;
 13using MediaBrowser.Controller.Entities.Audio;
 14using MediaBrowser.Controller.Entities.TV;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.LiveTv;
 17using MediaBrowser.Controller.Providers;
 18using MediaBrowser.Model.Configuration;
 19using MediaBrowser.Model.Drawing;
 20using MediaBrowser.Model.Entities;
 21using MediaBrowser.Model.IO;
 22using MediaBrowser.Model.MediaInfo;
 23using MediaBrowser.Model.Net;
 24using MediaBrowser.Model.Providers;
 25using Microsoft.Extensions.Logging;
 26
 27namespace MediaBrowser.Providers.Manager
 28{
 29    /// <summary>
 30    /// Utilities for managing images attached to items.
 31    /// </summary>
 32    public class ItemImageProvider
 33    {
 34        private readonly ILogger _logger;
 35        private readonly IProviderManager _providerManager;
 36        private readonly IFileSystem _fileSystem;
 237        private static readonly ImageType[] AllImageTypes = Enum.GetValues<ImageType>();
 38
 39        /// <summary>
 40        /// Image types that are only one per item.
 41        /// </summary>
 242        private static readonly ImageType[] _singularImages =
 243        {
 244            ImageType.Primary,
 245            ImageType.Art,
 246            ImageType.Banner,
 247            ImageType.Box,
 248            ImageType.BoxRear,
 249            ImageType.Disc,
 250            ImageType.Logo,
 251            ImageType.Menu,
 252            ImageType.Thumb
 253        };
 54
 55        /// <summary>
 56        /// Initializes a new instance of the <see cref="ItemImageProvider"/> class.
 57        /// </summary>
 58        /// <param name="logger">The logger.</param>
 59        /// <param name="providerManager">The provider manager for interacting with provider image references.</param>
 60        /// <param name="fileSystem">The filesystem.</param>
 61        public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem)
 62        {
 59163            _logger = logger;
 59164            _providerManager = providerManager;
 59165            _fileSystem = fileSystem;
 59166        }
 67
 68        /// <summary>
 69        /// Removes all existing images from the provided item.
 70        /// </summary>
 71        /// <param name="item">The <see cref="BaseItem"/> to remove images from.</param>
 72        /// <param name="canDeleteLocal">Whether removing images outside metadata folder is allowed.</param>
 73        /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
 74        public bool RemoveImages(BaseItem item, bool canDeleteLocal = false)
 75        {
 376            var singular = new List<ItemImageInfo>();
 377            var itemMetadataPath = item.GetInternalMetadataPath();
 6078            for (var i = 0; i < _singularImages.Length; i++)
 79            {
 2780                var currentImage = item.GetImageInfo(_singularImages[i], 0);
 2781                if (currentImage is not null)
 82                {
 183                    var imageInMetadataFolder = currentImage.Path.StartsWith(itemMetadataPath, StringComparison.OrdinalI
 184                    if (imageInMetadataFolder || canDeleteLocal || item.IsSaveLocalMetadataEnabled())
 85                    {
 186                        singular.Add(currentImage);
 87                    }
 88                }
 89            }
 90
 391            singular.AddRange(item.GetImages(ImageType.Backdrop));
 392            PruneImages(item, singular);
 93
 394            return singular.Count > 0;
 95        }
 96
 97        /// <summary>
 98        /// Verifies existing images have valid paths and adds any new local images provided.
 99        /// </summary>
 100        /// <param name="item">The <see cref="BaseItem"/> to validate images for.</param>
 101        /// <param name="providers">The providers to use, must include <see cref="ILocalImageProvider"/>(s) for local sc
 102        /// <param name="refreshOptions">The refresh options.</param>
 103        /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
 104        public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, ImageRefreshOptions refreshOpti
 105        {
 61106            var hasChanges = false;
 61107            var directoryService = refreshOptions?.DirectoryService;
 108
 61109            if (item is not Photo)
 110            {
 60111                var images = providers.OfType<ILocalImageProvider>()
 60112                    .SelectMany(i => i.GetImages(item, directoryService))
 60113                    .ToList();
 114
 60115                if (MergeImages(item, images, refreshOptions))
 116                {
 4117                    hasChanges = true;
 118                }
 119            }
 120
 61121            return hasChanges;
 122        }
 123
 124        /// <summary>
 125        /// Refreshes from the providers according to the given options.
 126        /// </summary>
 127        /// <param name="item">The <see cref="BaseItem"/> to gather images for.</param>
 128        /// <param name="libraryOptions">The library options.</param>
 129        /// <param name="providers">The providers to query for images.</param>
 130        /// <param name="refreshOptions">The refresh options.</param>
 131        /// <param name="cancellationToken">The cancellation token.</param>
 132        /// <returns>The refresh result.</returns>
 133        public async Task<RefreshResult> RefreshImages(
 134            BaseItem item,
 135            LibraryOptions libraryOptions,
 136            IEnumerable<IImageProvider> providers,
 137            ImageRefreshOptions refreshOptions,
 138            CancellationToken cancellationToken)
 139        {
 140            var oldBackdropImages = Array.Empty<ItemImageInfo>();
 141            if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
 142            {
 143                oldBackdropImages = item.GetImages(ImageType.Backdrop).ToArray();
 144            }
 145
 146            var result = new RefreshResult { UpdateType = ItemUpdateType.None };
 147
 148            var typeName = item.GetType().Name;
 149            var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName };
 150
 151            // track library limits, adding buffer to allow lazy replacing of current images
 152            var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Length;
 153            var downloadedImages = new List<ImageType>();
 154
 155            foreach (var provider in providers)
 156            {
 157                if (provider is IRemoteImageProvider remoteProvider)
 158                {
 159                    await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, backdropLimit, download
 160                    continue;
 161                }
 162
 163                if (provider is IDynamicImageProvider dynamicImageProvider)
 164                {
 165                    await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, typeOptions, downloadedImages,
 166                }
 167            }
 168
 169            // Only delete existing multi-images if new ones were added
 170            if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count())
 171            {
 172                PruneImages(item, oldBackdropImages);
 173            }
 174
 175            return result;
 176        }
 177
 178        /// <summary>
 179        /// Refreshes from a dynamic provider.
 180        /// </summary>
 181        private async Task RefreshFromProvider(
 182            BaseItem item,
 183            IDynamicImageProvider provider,
 184            ImageRefreshOptions refreshOptions,
 185            TypeOptions savedOptions,
 186            List<ImageType> downloadedImages,
 187            RefreshResult result,
 188            CancellationToken cancellationToken)
 189        {
 190            try
 191            {
 192                var images = provider.GetSupportedImages(item);
 193
 194                foreach (var imageType in images)
 195                {
 196                    if (!savedOptions.IsEnabled(imageType))
 197                    {
 198                        continue;
 199                    }
 200
 201                    if (!item.HasImage(imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Co
 202                    {
 203                        _logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, item.Path ?? item.Nam
 204
 205                        var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false)
 206
 207                        if (response.HasImage)
 208                        {
 209                            if (string.IsNullOrEmpty(response.Path))
 210                            {
 211                                var mimeType = response.Format.GetMimeType();
 212
 213                                await _providerManager.SaveImage(item, response.Stream, mimeType, imageType, null, cance
 214                            }
 215                            else
 216                            {
 217                                if (response.Protocol == MediaProtocol.Http)
 218                                {
 219                                    _logger.LogDebug("Setting image url into item {Item}", item.Id);
 220                                    var index = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count()
 221                                    item.SetImage(
 222                                        new ItemImageInfo
 223                                        {
 224                                            Path = response.Path,
 225                                            Type = imageType
 226                                        },
 227                                        index);
 228                                }
 229                                else
 230                                {
 231                                    var mimeType = MimeTypes.GetMimeType(response.Path);
 232
 233                                    await _providerManager.SaveImage(item, response.Path, mimeType, imageType, null, nul
 234                                }
 235                            }
 236
 237                            downloadedImages.Add(imageType);
 238                            result.UpdateType |= ItemUpdateType.ImageUpdate;
 239                        }
 240                    }
 241                }
 242            }
 243            catch (OperationCanceledException)
 244            {
 245                throw;
 246            }
 247            catch (Exception ex)
 248            {
 249                result.ErrorMessage = ex.Message;
 250                _logger.LogError(ex, "Error in {Provider}", provider.Name);
 251            }
 252        }
 253
 254        /// <summary>
 255        /// Refreshes from a remote provider.
 256        /// </summary>
 257        /// <param name="item">The item.</param>
 258        /// <param name="provider">The provider.</param>
 259        /// <param name="refreshOptions">The refresh options.</param>
 260        /// <param name="savedOptions">The saved options.</param>
 261        /// <param name="backdropLimit">The backdrop limit.</param>
 262        /// <param name="downloadedImages">The downloaded images.</param>
 263        /// <param name="result">The result.</param>
 264        /// <param name="cancellationToken">The cancellation token.</param>
 265        /// <returns>Task.</returns>
 266        private async Task RefreshFromProvider(
 267            BaseItem item,
 268            IRemoteImageProvider provider,
 269            ImageRefreshOptions refreshOptions,
 270            TypeOptions savedOptions,
 271            int backdropLimit,
 272            List<ImageType> downloadedImages,
 273            RefreshResult result,
 274            CancellationToken cancellationToken)
 275        {
 276            try
 277            {
 278                if (!item.SupportsRemoteImageDownloading)
 279                {
 280                    return;
 281                }
 282
 283                if (!refreshOptions.ReplaceAllImages &&
 284                    refreshOptions.ReplaceImages.Count == 0 &&
 285                    ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit))
 286                {
 287                    return;
 288                }
 289
 290                _logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, item.Path ?? item.Name);
 291
 292                var images = await _providerManager.GetAvailableRemoteImages(
 293                    item,
 294                    new RemoteImageQuery(provider.Name)
 295                    {
 296                        IncludeAllLanguages = true,
 297                        IncludeDisabledProviders = false,
 298                    },
 299                    cancellationToken).ConfigureAwait(false);
 300
 301                var list = images.ToList();
 302                int minWidth;
 303
 304                foreach (var imageType in _singularImages)
 305                {
 306                    if (!savedOptions.IsEnabled(imageType))
 307                    {
 308                        continue;
 309                    }
 310
 311                    if (!item.HasImage(imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Co
 312                    {
 313                        minWidth = savedOptions.GetMinWidth(imageType);
 314                        var downloaded = await DownloadImage(item, provider, result, list, minWidth, imageType, cancella
 315
 316                        if (downloaded)
 317                        {
 318                            downloadedImages.Add(imageType);
 319                        }
 320                    }
 321                }
 322
 323                minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
 324                var listWithNoLangFirst = list.OrderByDescending(i => string.IsNullOrEmpty(i.Language));
 325                await DownloadMultiImages(item, ImageType.Backdrop, refreshOptions, backdropLimit, provider, result, lis
 326            }
 327            catch (OperationCanceledException)
 328            {
 329                throw;
 330            }
 331            catch (Exception ex)
 332            {
 333                result.ErrorMessage = ex.Message;
 334                _logger.LogError(ex, "Error in {Provider}", provider.Name);
 335            }
 336        }
 337
 338        /// <summary>
 339        /// Determines if an item already contains the given images.
 340        /// </summary>
 341        /// <param name="item">The item.</param>
 342        /// <param name="images">The images.</param>
 343        /// <param name="savedOptions">The saved options.</param>
 344        /// <param name="backdropLimit">The backdrop limit.</param>
 345        /// <returns><c>true</c> if the specified item contains images; otherwise, <c>false</c>.</returns>
 346        private bool ContainsImages(BaseItem item, List<ImageType> images, TypeOptions savedOptions, int backdropLimit)
 347        {
 348            // Using .Any causes the creation of a DisplayClass aka. variable capture
 130349            for (var i = 0; i < _singularImages.Length; i++)
 350            {
 59351                var type = _singularImages[i];
 59352                if (images.Contains(type) && !item.HasImage(type) && savedOptions.GetLimit(type) > 0)
 353                {
 5354                    return false;
 355                }
 356            }
 357
 6358            if (images.Contains(ImageType.Backdrop) && item.GetImages(ImageType.Backdrop).Count() < backdropLimit)
 359            {
 3360                return false;
 361            }
 362
 3363            return true;
 364        }
 365
 366        private void PruneImages(BaseItem item, IReadOnlyList<ItemImageInfo> images)
 367        {
 32368            foreach (var image in images)
 369            {
 9370                if (image.IsLocalFile)
 371                {
 372                    try
 373                    {
 9374                        _fileSystem.DeleteFile(image.Path);
 9375                    }
 0376                    catch (FileNotFoundException)
 377                    {
 378                        // Nothing to do, already gone
 0379                    }
 0380                    catch (DirectoryNotFoundException)
 381                    {
 382                        // Nothing to do, already gone
 0383                    }
 0384                    catch (UnauthorizedAccessException ex)
 385                    {
 0386                        _logger.LogWarning(ex, "Unable to delete {Image}", image.Path);
 0387                    }
 388                }
 389            }
 390
 7391            item.RemoveImages(images);
 392
 393            // Cleanup old metadata directory for episodes if empty, as long as it's not a virtual item
 7394            if (item is Episode && !item.IsVirtualItem)
 395            {
 0396                var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
 0397                if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDire
 398                {
 0399                    Directory.Delete(oldLocalMetadataDirectory);
 400                }
 401            }
 7402        }
 403
 404        /// <summary>
 405        /// Merges a list of images into the provided item, validating existing images and replacing them or adding new 
 406        /// </summary>
 407        /// <param name="refreshOptions">The refresh options.</param>
 408        /// <param name="dontReplaceImages">List of imageTypes to remove from ReplaceImages.</param>
 409        public void UpdateReplaceImages(ImageRefreshOptions refreshOptions, ICollection<ImageType> dontReplaceImages)
 410        {
 3411            if (refreshOptions is not null)
 412            {
 2413                if (refreshOptions.ReplaceAllImages)
 414                {
 0415                    refreshOptions.ReplaceAllImages = false;
 0416                    refreshOptions.ReplaceImages = AllImageTypes.ToList();
 417                }
 418
 2419                refreshOptions.ReplaceImages = refreshOptions.ReplaceImages.Except(dontReplaceImages).ToList();
 420            }
 3421        }
 422
 423        /// <summary>
 424        /// Merges a list of images into the provided item, validating existing images and replacing them or adding new 
 425        /// </summary>
 426        /// <param name="item">The <see cref="BaseItem"/> to modify.</param>
 427        /// <param name="images">The new images to place in <c>item</c>.</param>
 428        /// <param name="refreshOptions">The refresh options.</param>
 429        /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
 430        public bool MergeImages(BaseItem item, IReadOnlyList<LocalImageInfo> images, ImageRefreshOptions refreshOptions)
 431        {
 67432            var changed = item.ValidateImages();
 67433            var foundImageTypes = new List<ImageType>();
 1340434            for (var i = 0; i < _singularImages.Length; i++)
 435            {
 603436                var type = _singularImages[i];
 603437                var image = GetFirstLocalImageInfoByType(images, type);
 603438                if (image is not null)
 439                {
 4440                    var currentImage = item.GetImageInfo(type, 0);
 441                    // if image file is stored with media, don't replace that later
 4442                    if (item.ContainingFolderPath is not null && item.ContainingFolderPath.Contains(Path.GetDirectoryNam
 443                    {
 0444                        foundImageTypes.Add(type);
 445                    }
 446
 4447                    if (currentImage is null || !string.Equals(currentImage.Path, image.FileInfo.FullName, StringCompari
 448                    {
 2449                        item.SetImagePath(type, image.FileInfo);
 2450                        changed = true;
 451                    }
 452                    else
 453                    {
 2454                        var newDateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo);
 455
 456                        // If date changed then we need to reset saved image dimensions
 2457                        if (currentImage.DateModified != newDateModified && (currentImage.Width > 0 || currentImage.Heig
 458                        {
 1459                            currentImage.Width = 0;
 1460                            currentImage.Height = 0;
 1461                            changed = true;
 462                        }
 463
 2464                        currentImage.DateModified = newDateModified;
 465                    }
 466                }
 467            }
 468
 67469            if (UpdateMultiImages(item, images, ImageType.Backdrop))
 470            {
 3471                changed = true;
 3472                foundImageTypes.Add(ImageType.Backdrop);
 473            }
 474
 67475            if (foundImageTypes.Count > 0)
 476            {
 3477                UpdateReplaceImages(refreshOptions, foundImageTypes);
 478            }
 479
 67480            return changed;
 481        }
 482
 483        private static LocalImageInfo GetFirstLocalImageInfoByType(IReadOnlyList<LocalImageInfo> images, ImageType type)
 484        {
 603485            var len = images.Count;
 1414486            for (var i = 0; i < len; i++)
 487            {
 108488                var image = images[i];
 108489                if (image.Type == type)
 490                {
 4491                    return image;
 492                }
 493            }
 494
 599495            return null;
 496        }
 497
 498        private bool UpdateMultiImages(BaseItem item, IReadOnlyList<LocalImageInfo> images, ImageType type)
 499        {
 67500            var changed = false;
 501
 67502            var newImageFileInfos = images
 67503                .Where(i => i.Type == type)
 67504                .Select(i => i.FileInfo)
 67505                .ToList();
 506
 67507            if (item.AddImages(type, newImageFileInfos))
 508            {
 3509                changed = true;
 510            }
 511
 67512            return changed;
 513        }
 514
 515        private async Task<bool> DownloadImage(
 516            BaseItem item,
 517            IRemoteImageProvider provider,
 518            RefreshResult result,
 519            IEnumerable<RemoteImageInfo> images,
 520            int minWidth,
 521            ImageType type,
 522            CancellationToken cancellationToken)
 523        {
 524            var eligibleImages = images
 525                .Where(i => i.Type == type && (i.Width is null || i.Width >= minWidth))
 526                .ToList();
 527
 528            if (EnableImageStub(item) && eligibleImages.Count > 0)
 529            {
 530                SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
 531                result.UpdateType |= ItemUpdateType.ImageUpdate;
 532                return true;
 533            }
 534
 535            foreach (var image in eligibleImages)
 536            {
 537                var url = image.Url;
 538
 539                try
 540                {
 541                    using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
 542
 543                    // Sometimes providers send back bad urls. Just move to the next image
 544                    if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidde
 545                    {
 546                        _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
 547                        continue;
 548                    }
 549
 550                    if (!response.IsSuccessStatusCode)
 551                    {
 552                        _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response
 553                        break;
 554                    }
 555
 556                    var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 557                    await using (stream.ConfigureAwait(false))
 558                    {
 559                        var mimetype = response.Content.Headers.ContentType?.MediaType;
 560                        if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordin
 561                        {
 562                            mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.P
 563                        }
 564
 565                        await _providerManager.SaveImage(
 566                            item,
 567                            stream,
 568                            mimetype,
 569                            type,
 570                            null,
 571                            cancellationToken).ConfigureAwait(false);
 572                    }
 573
 574                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 575                    return true;
 576                }
 577                catch (HttpRequestException)
 578                {
 579                    break;
 580                }
 581            }
 582
 583            return false;
 584        }
 585
 586        private bool EnableImageStub(BaseItem item)
 587        {
 134588            if (item is LiveTvProgram)
 589            {
 0590                return true;
 591            }
 592
 134593            if (!item.IsFileProtocol)
 594            {
 95595                return true;
 596            }
 597
 39598            if (item is IItemByName and not MusicArtist)
 599            {
 0600                var hasDualAccess = item as IHasDualAccess;
 0601                if (hasDualAccess is null || hasDualAccess.IsAccessedByName)
 602                {
 0603                    return true;
 604                }
 605            }
 606
 607            // We always want to use prefetched images
 39608            return false;
 609        }
 610
 611        private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls)
 612        {
 9613            var newIndex = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count() : 0;
 614
 9615            SaveImageStub(item, imageType, urls, newIndex);
 9616        }
 617
 618        private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls, int newIndex)
 619        {
 9620            var path = string.Join('|', urls.Take(1));
 621
 9622            item.SetImage(
 9623                new ItemImageInfo
 9624                {
 9625                    Path = path,
 9626                    Type = imageType
 9627                },
 9628                newIndex);
 9629        }
 630
 631        private async Task DownloadMultiImages(BaseItem item, ImageType imageType, ImageRefreshOptions refreshOptions, i
 632        {
 633            foreach (var image in images.Where(i => i.Type == imageType))
 634            {
 635                if (item.GetImages(imageType).Count() >= limit)
 636                {
 637                    break;
 638                }
 639
 640                if (image.Width.HasValue && image.Width.Value < minWidth)
 641                {
 642                    continue;
 643                }
 644
 645                var url = image.Url;
 646
 647                if (EnableImageStub(item))
 648                {
 649                    SaveImageStub(item, imageType, new[] { url });
 650                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 651                    continue;
 652                }
 653
 654                try
 655                {
 656                    using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
 657
 658                    // Sometimes providers send back bad urls. Just move to the next image
 659                    if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidde
 660                    {
 661                        _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
 662                        continue;
 663                    }
 664
 665                    if (!response.IsSuccessStatusCode)
 666                    {
 667                        _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response
 668                        break;
 669                    }
 670
 671                    // If there's already an image of the same file size, skip it unless doing a full refresh
 672                    if (response.Content.Headers.ContentLength.HasValue && !refreshOptions.IsReplacingImage(imageType))
 673                    {
 674                        try
 675                        {
 676                            if (item.GetImages(imageType).Any(i => _fileSystem.GetFileInfo(i.Path).Length == response.Co
 677                            {
 678                                response.Content.Dispose();
 679                                continue;
 680                            }
 681                        }
 682                        catch (IOException ex)
 683                        {
 684                            _logger.LogError(ex, "Error examining images");
 685                        }
 686                    }
 687
 688                    var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 689                    await using (stream.ConfigureAwait(false))
 690                    {
 691                        var mimetype = response.Content.Headers.ContentType?.MediaType;
 692                        if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordin
 693                        {
 694                            mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.P
 695                        }
 696
 697                        await _providerManager.SaveImage(
 698                            item,
 699                            stream,
 700                            mimetype,
 701                            imageType,
 702                            null,
 703                            cancellationToken).ConfigureAwait(false);
 704                    }
 705
 706                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 707                }
 708                catch (HttpRequestException)
 709                {
 710                    break;
 711                }
 712            }
 713        }
 714    }
 715}

Methods/Properties

.cctor()
.ctor(Microsoft.Extensions.Logging.ILogger,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Model.IO.IFileSystem)
RemoveImages(MediaBrowser.Controller.Entities.BaseItem,System.Boolean)
ValidateImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IImageProvider>,MediaBrowser.Controller.Providers.ImageRefreshOptions)
ContainsImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.List`1<MediaBrowser.Model.Entities.ImageType>,MediaBrowser.Model.Configuration.TypeOptions,System.Int32)
PruneImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.ItemImageInfo>)
UpdateReplaceImages(MediaBrowser.Controller.Providers.ImageRefreshOptions,System.Collections.Generic.ICollection`1<MediaBrowser.Model.Entities.ImageType>)
MergeImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Providers.LocalImageInfo>,MediaBrowser.Controller.Providers.ImageRefreshOptions)
GetFirstLocalImageInfoByType(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Providers.LocalImageInfo>,MediaBrowser.Model.Entities.ImageType)
UpdateMultiImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Providers.LocalImageInfo>,MediaBrowser.Model.Entities.ImageType)
EnableImageStub(MediaBrowser.Controller.Entities.BaseItem)
SaveImageStub(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Entities.ImageType,System.Collections.Generic.IEnumerable`1<System.String>)
SaveImageStub(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Entities.ImageType,System.Collections.Generic.IEnumerable`1<System.String>,System.Int32)