< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.ItemImageProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/ItemImageProvider.cs
Line coverage
87%
Covered lines: 109
Uncovered lines: 15
Coverable lines: 124
Total lines: 711
Line coverage: 87.9%
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%341246.66%
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 (UnauthorizedAccessException ex)
 381                    {
 0382                        _logger.LogWarning(ex, "Unable to delete {Image}", image.Path);
 0383                    }
 384                }
 385            }
 386
 7387            item.RemoveImages(images);
 388
 389            // Cleanup old metadata directory for episodes if empty, as long as it's not a virtual item
 7390            if (item is Episode && !item.IsVirtualItem)
 391            {
 0392                var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
 0393                if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDire
 394                {
 0395                    Directory.Delete(oldLocalMetadataDirectory);
 396                }
 397            }
 7398        }
 399
 400        /// <summary>
 401        /// Merges a list of images into the provided item, validating existing images and replacing them or adding new 
 402        /// </summary>
 403        /// <param name="refreshOptions">The refresh options.</param>
 404        /// <param name="dontReplaceImages">List of imageTypes to remove from ReplaceImages.</param>
 405        public void UpdateReplaceImages(ImageRefreshOptions refreshOptions, ICollection<ImageType> dontReplaceImages)
 406        {
 3407            if (refreshOptions is not null)
 408            {
 2409                if (refreshOptions.ReplaceAllImages)
 410                {
 0411                    refreshOptions.ReplaceAllImages = false;
 0412                    refreshOptions.ReplaceImages = AllImageTypes.ToList();
 413                }
 414
 2415                refreshOptions.ReplaceImages = refreshOptions.ReplaceImages.Except(dontReplaceImages).ToList();
 416            }
 3417        }
 418
 419        /// <summary>
 420        /// Merges a list of images into the provided item, validating existing images and replacing them or adding new 
 421        /// </summary>
 422        /// <param name="item">The <see cref="BaseItem"/> to modify.</param>
 423        /// <param name="images">The new images to place in <c>item</c>.</param>
 424        /// <param name="refreshOptions">The refresh options.</param>
 425        /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
 426        public bool MergeImages(BaseItem item, IReadOnlyList<LocalImageInfo> images, ImageRefreshOptions refreshOptions)
 427        {
 67428            var changed = item.ValidateImages();
 67429            var foundImageTypes = new List<ImageType>();
 1340430            for (var i = 0; i < _singularImages.Length; i++)
 431            {
 603432                var type = _singularImages[i];
 603433                var image = GetFirstLocalImageInfoByType(images, type);
 603434                if (image is not null)
 435                {
 4436                    var currentImage = item.GetImageInfo(type, 0);
 437                    // if image file is stored with media, don't replace that later
 4438                    if (item.ContainingFolderPath is not null && item.ContainingFolderPath.Contains(Path.GetDirectoryNam
 439                    {
 0440                        foundImageTypes.Add(type);
 441                    }
 442
 4443                    if (currentImage is null || !string.Equals(currentImage.Path, image.FileInfo.FullName, StringCompari
 444                    {
 2445                        item.SetImagePath(type, image.FileInfo);
 2446                        changed = true;
 447                    }
 448                    else
 449                    {
 2450                        var newDateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo);
 451
 452                        // If date changed then we need to reset saved image dimensions
 2453                        if (currentImage.DateModified != newDateModified && (currentImage.Width > 0 || currentImage.Heig
 454                        {
 1455                            currentImage.Width = 0;
 1456                            currentImage.Height = 0;
 1457                            changed = true;
 458                        }
 459
 2460                        currentImage.DateModified = newDateModified;
 461                    }
 462                }
 463            }
 464
 67465            if (UpdateMultiImages(item, images, ImageType.Backdrop))
 466            {
 3467                changed = true;
 3468                foundImageTypes.Add(ImageType.Backdrop);
 469            }
 470
 67471            if (foundImageTypes.Count > 0)
 472            {
 3473                UpdateReplaceImages(refreshOptions, foundImageTypes);
 474            }
 475
 67476            return changed;
 477        }
 478
 479        private static LocalImageInfo GetFirstLocalImageInfoByType(IReadOnlyList<LocalImageInfo> images, ImageType type)
 480        {
 603481            var len = images.Count;
 1414482            for (var i = 0; i < len; i++)
 483            {
 108484                var image = images[i];
 108485                if (image.Type == type)
 486                {
 4487                    return image;
 488                }
 489            }
 490
 599491            return null;
 492        }
 493
 494        private bool UpdateMultiImages(BaseItem item, IReadOnlyList<LocalImageInfo> images, ImageType type)
 495        {
 67496            var changed = false;
 497
 67498            var newImageFileInfos = images
 67499                .Where(i => i.Type == type)
 67500                .Select(i => i.FileInfo)
 67501                .ToList();
 502
 67503            if (item.AddImages(type, newImageFileInfos))
 504            {
 3505                changed = true;
 506            }
 507
 67508            return changed;
 509        }
 510
 511        private async Task<bool> DownloadImage(
 512            BaseItem item,
 513            IRemoteImageProvider provider,
 514            RefreshResult result,
 515            IEnumerable<RemoteImageInfo> images,
 516            int minWidth,
 517            ImageType type,
 518            CancellationToken cancellationToken)
 519        {
 520            var eligibleImages = images
 521                .Where(i => i.Type == type && (i.Width is null || i.Width >= minWidth))
 522                .ToList();
 523
 524            if (EnableImageStub(item) && eligibleImages.Count > 0)
 525            {
 526                SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
 527                result.UpdateType |= ItemUpdateType.ImageUpdate;
 528                return true;
 529            }
 530
 531            foreach (var image in eligibleImages)
 532            {
 533                var url = image.Url;
 534
 535                try
 536                {
 537                    using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
 538
 539                    // Sometimes providers send back bad urls. Just move to the next image
 540                    if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidde
 541                    {
 542                        _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
 543                        continue;
 544                    }
 545
 546                    if (!response.IsSuccessStatusCode)
 547                    {
 548                        _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response
 549                        break;
 550                    }
 551
 552                    var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 553                    await using (stream.ConfigureAwait(false))
 554                    {
 555                        var mimetype = response.Content.Headers.ContentType?.MediaType;
 556                        if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordin
 557                        {
 558                            mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.P
 559                        }
 560
 561                        await _providerManager.SaveImage(
 562                            item,
 563                            stream,
 564                            mimetype,
 565                            type,
 566                            null,
 567                            cancellationToken).ConfigureAwait(false);
 568                    }
 569
 570                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 571                    return true;
 572                }
 573                catch (HttpRequestException)
 574                {
 575                    break;
 576                }
 577            }
 578
 579            return false;
 580        }
 581
 582        private bool EnableImageStub(BaseItem item)
 583        {
 134584            if (item is LiveTvProgram)
 585            {
 0586                return true;
 587            }
 588
 134589            if (!item.IsFileProtocol)
 590            {
 95591                return true;
 592            }
 593
 39594            if (item is IItemByName and not MusicArtist)
 595            {
 0596                var hasDualAccess = item as IHasDualAccess;
 0597                if (hasDualAccess is null || hasDualAccess.IsAccessedByName)
 598                {
 0599                    return true;
 600                }
 601            }
 602
 603            // We always want to use prefetched images
 39604            return false;
 605        }
 606
 607        private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls)
 608        {
 9609            var newIndex = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count() : 0;
 610
 9611            SaveImageStub(item, imageType, urls, newIndex);
 9612        }
 613
 614        private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls, int newIndex)
 615        {
 9616            var path = string.Join('|', urls.Take(1));
 617
 9618            item.SetImage(
 9619                new ItemImageInfo
 9620                {
 9621                    Path = path,
 9622                    Type = imageType
 9623                },
 9624                newIndex);
 9625        }
 626
 627        private async Task DownloadMultiImages(BaseItem item, ImageType imageType, ImageRefreshOptions refreshOptions, i
 628        {
 629            foreach (var image in images.Where(i => i.Type == imageType))
 630            {
 631                if (item.GetImages(imageType).Count() >= limit)
 632                {
 633                    break;
 634                }
 635
 636                if (image.Width.HasValue && image.Width.Value < minWidth)
 637                {
 638                    continue;
 639                }
 640
 641                var url = image.Url;
 642
 643                if (EnableImageStub(item))
 644                {
 645                    SaveImageStub(item, imageType, new[] { url });
 646                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 647                    continue;
 648                }
 649
 650                try
 651                {
 652                    using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
 653
 654                    // Sometimes providers send back bad urls. Just move to the next image
 655                    if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidde
 656                    {
 657                        _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
 658                        continue;
 659                    }
 660
 661                    if (!response.IsSuccessStatusCode)
 662                    {
 663                        _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response
 664                        break;
 665                    }
 666
 667                    // If there's already an image of the same file size, skip it unless doing a full refresh
 668                    if (response.Content.Headers.ContentLength.HasValue && !refreshOptions.IsReplacingImage(imageType))
 669                    {
 670                        try
 671                        {
 672                            if (item.GetImages(imageType).Any(i => _fileSystem.GetFileInfo(i.Path).Length == response.Co
 673                            {
 674                                response.Content.Dispose();
 675                                continue;
 676                            }
 677                        }
 678                        catch (IOException ex)
 679                        {
 680                            _logger.LogError(ex, "Error examining images");
 681                        }
 682                    }
 683
 684                    var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 685                    await using (stream.ConfigureAwait(false))
 686                    {
 687                        var mimetype = response.Content.Headers.ContentType?.MediaType;
 688                        if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordin
 689                        {
 690                            mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.P
 691                        }
 692
 693                        await _providerManager.SaveImage(
 694                            item,
 695                            stream,
 696                            mimetype,
 697                            imageType,
 698                            null,
 699                            cancellationToken).ConfigureAwait(false);
 700                    }
 701
 702                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 703                }
 704                catch (HttpRequestException)
 705                {
 706                    break;
 707                }
 708            }
 709        }
 710    }
 711}

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)