< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.ProviderManager
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/ProviderManager.cs
Line coverage
31%
Covered lines: 173
Uncovered lines: 377
Coverable lines: 550
Total lines: 1322
Line coverage: 31.4%
Branch coverage
36%
Covered branches: 78
Total branches: 212
Branch coverage: 36.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/14/2026 - 12:11:17 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/15/2026 - 12:13:43 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11722/17/2026 - 12:11:27 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/18/2026 - 12:13:45 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11722/22/2026 - 12:14:10 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/23/2026 - 12:11:45 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/24/2026 - 12:11:42 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/25/2026 - 12:11:31 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/1/2026 - 12:12:03 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/2/2026 - 12:14:15 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/3/2026 - 12:13:24 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/4/2026 - 12:14:01 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/7/2026 - 12:14:13 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11723/8/2026 - 12:12:00 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/13/2026 - 12:14:32 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11723/14/2026 - 12:13:58 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/17/2026 - 12:14:16 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/18/2026 - 12:13:47 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/21/2026 - 12:13:51 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/22/2026 - 12:11:35 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11723/23/2026 - 12:14:11 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/25/2026 - 12:13:35 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/27/2026 - 12:13:57 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11723/28/2026 - 12:14:04 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11724/2/2026 - 12:14:03 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11724/3/2026 - 12:13:45 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11724/4/2026 - 12:14:28 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11724/5/2026 - 12:14:16 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11724/6/2026 - 12:13:55 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11724/7/2026 - 12:14:03 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11724/8/2026 - 12:11:47 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11724/11/2026 - 12:11:37 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11724/12/2026 - 12:13:54 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11724/14/2026 - 12:13:23 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11724/15/2026 - 12:14:34 AM Line coverage: 45.5% (138/303) Branch coverage: 55.1% (64/116) Total lines: 11794/19/2026 - 12:14:27 AM Line coverage: 28.4% (143/502) Branch coverage: 36% (67/186) Total lines: 11794/21/2026 - 12:11:51 AM Line coverage: 28.4% (143/502) Branch coverage: 34.9% (65/186) Total lines: 11794/23/2026 - 12:14:52 AM Line coverage: 28.4% (143/502) Branch coverage: 36.5% (68/186) Total lines: 11794/24/2026 - 12:14:24 AM Line coverage: 28.4% (143/502) Branch coverage: 36% (67/186) Total lines: 11794/26/2026 - 12:12:40 AM Line coverage: 28.4% (143/502) Branch coverage: 34.9% (65/186) Total lines: 11794/27/2026 - 12:15:04 AM Line coverage: 28.4% (143/502) Branch coverage: 36% (67/186) Total lines: 11794/28/2026 - 12:13:10 AM Line coverage: 28.4% (143/502) Branch coverage: 34.9% (65/186) Total lines: 11795/1/2026 - 12:13:05 AM Line coverage: 28.4% (143/502) Branch coverage: 36% (67/186) Total lines: 11795/2/2026 - 12:12:50 AM Line coverage: 28.4% (143/502) Branch coverage: 34.9% (65/186) Total lines: 11795/4/2026 - 12:15:16 AM Line coverage: 31.7% (172/541) Branch coverage: 40.4% (85/210) Total lines: 13055/5/2026 - 12:15:44 AM Line coverage: 31.6% (172/543) Branch coverage: 40.4% (85/210) Total lines: 13075/6/2026 - 12:15:23 AM Line coverage: 31.6% (172/543) Branch coverage: 41.4% (87/210) Total lines: 13075/7/2026 - 12:15:44 AM Line coverage: 31.6% (172/543) Branch coverage: 40.4% (85/210) Total lines: 13075/16/2026 - 12:15:55 AM Line coverage: 31.4% (173/550) Branch coverage: 40% (85/212) Total lines: 13225/19/2026 - 12:15:13 AM Line coverage: 31.4% (173/550) Branch coverage: 41% (87/212) Total lines: 13225/20/2026 - 12:15:44 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13225/24/2026 - 12:15:27 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13225/25/2026 - 12:14:57 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13225/26/2026 - 12:15:12 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 1322 2/13/2026 - 12:11:21 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/14/2026 - 12:11:17 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/15/2026 - 12:13:43 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11722/17/2026 - 12:11:27 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/18/2026 - 12:13:45 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11722/22/2026 - 12:14:10 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/23/2026 - 12:11:45 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/24/2026 - 12:11:42 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/25/2026 - 12:11:31 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/1/2026 - 12:12:03 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/2/2026 - 12:14:15 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/3/2026 - 12:13:24 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/4/2026 - 12:14:01 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/7/2026 - 12:14:13 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11723/8/2026 - 12:12:00 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/13/2026 - 12:14:32 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11723/14/2026 - 12:13:58 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/17/2026 - 12:14:16 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/18/2026 - 12:13:47 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/21/2026 - 12:13:51 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/22/2026 - 12:11:35 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11723/23/2026 - 12:14:11 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11723/25/2026 - 12:13:35 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11723/27/2026 - 12:13:57 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11723/28/2026 - 12:14:04 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11724/2/2026 - 12:14:03 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11724/3/2026 - 12:13:45 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11724/4/2026 - 12:14:28 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11724/5/2026 - 12:14:16 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11724/6/2026 - 12:13:55 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11724/7/2026 - 12:14:03 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11724/8/2026 - 12:11:47 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11724/11/2026 - 12:11:37 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11724/12/2026 - 12:13:54 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11724/14/2026 - 12:13:23 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11724/15/2026 - 12:14:34 AM Line coverage: 45.5% (138/303) Branch coverage: 55.1% (64/116) Total lines: 11794/19/2026 - 12:14:27 AM Line coverage: 28.4% (143/502) Branch coverage: 36% (67/186) Total lines: 11794/21/2026 - 12:11:51 AM Line coverage: 28.4% (143/502) Branch coverage: 34.9% (65/186) Total lines: 11794/23/2026 - 12:14:52 AM Line coverage: 28.4% (143/502) Branch coverage: 36.5% (68/186) Total lines: 11794/24/2026 - 12:14:24 AM Line coverage: 28.4% (143/502) Branch coverage: 36% (67/186) Total lines: 11794/26/2026 - 12:12:40 AM Line coverage: 28.4% (143/502) Branch coverage: 34.9% (65/186) Total lines: 11794/27/2026 - 12:15:04 AM Line coverage: 28.4% (143/502) Branch coverage: 36% (67/186) Total lines: 11794/28/2026 - 12:13:10 AM Line coverage: 28.4% (143/502) Branch coverage: 34.9% (65/186) Total lines: 11795/1/2026 - 12:13:05 AM Line coverage: 28.4% (143/502) Branch coverage: 36% (67/186) Total lines: 11795/2/2026 - 12:12:50 AM Line coverage: 28.4% (143/502) Branch coverage: 34.9% (65/186) Total lines: 11795/4/2026 - 12:15:16 AM Line coverage: 31.7% (172/541) Branch coverage: 40.4% (85/210) Total lines: 13055/5/2026 - 12:15:44 AM Line coverage: 31.6% (172/543) Branch coverage: 40.4% (85/210) Total lines: 13075/6/2026 - 12:15:23 AM Line coverage: 31.6% (172/543) Branch coverage: 41.4% (87/210) Total lines: 13075/7/2026 - 12:15:44 AM Line coverage: 31.6% (172/543) Branch coverage: 40.4% (85/210) Total lines: 13075/16/2026 - 12:15:55 AM Line coverage: 31.4% (173/550) Branch coverage: 40% (85/212) Total lines: 13225/19/2026 - 12:15:13 AM Line coverage: 31.4% (173/550) Branch coverage: 41% (87/212) Total lines: 13225/20/2026 - 12:15:44 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13225/24/2026 - 12:15:27 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13225/25/2026 - 12:14:57 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13225/26/2026 - 12:15:12 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 1322

Coverage delta

Coverage delta 20 -20

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
AddParts(...)100%11100%
RefreshSingleItem(...)100%44100%
SaveImage()0%342180%
SaveImage(...)100%210%
SaveImage()0%620%
SaveImage(...)100%210%
GetAvailableRemoteImages()0%2040%
GetImages()0%2040%
GetRemoteImageProviderInfo(...)100%210%
GetRemoteImageProviders(...)100%210%
GetImageProviders(...)100%11100%
GetImageProvidersInternal(...)100%44100%
CanRefreshImages(...)90%1010100%
GetMetadataProviders(...)100%11100%
GetMetadataProviders(...)100%210%
GetLibraryPathForItem(...)62.5%88100%
GetMetadataSavers(...)100%11100%
GetMetadataProvidersInternal(...)100%11100%
GetOrCreateOrderedProviders(...)90%1010100%
CanRefreshMetadataForCache(...)75%151271.42%
CanRefreshMetadata(...)80%222081.81%
GetConfiguredOrder(...)100%22100%
GetDefaultOrder(...)100%22100%
GetAllMetadataPlugins()100%210%
GetPluginSummary()0%7280%
AddMetadataPlugins(...)0%156120%
AddImagePlugins(...)100%210%
GetMetadataOptions(...)100%22100%
SaveMetadataAsync(...)100%11100%
SaveMetadataAsync(...)100%210%
SaveMetadataAsync()12.5%43817.85%
IsSaverEnabledForItem(...)5.55%1771821.05%
GetRemoteSearchResults(...)0%620%
GetRemoteSearchResults()0%342180%
GetExternalIds(...)100%210%
GetExternalUrls(...)100%11100%
GetExternalIdInfos(...)100%210%
GetRefreshQueue()100%11100%
OnRefreshStart(...)50%2262.5%
OnRefreshComplete(...)50%2262.5%
GetRefreshProgress(...)0%620%
OnRefreshProgress(...)75%9875%
QueueRefresh(...)0%4260%
StartProcessingRefreshQueue()0%110100%
RefreshItem()0%2040%
RefreshCollectionFolderChildren()0%620%
RefreshArtist()100%210%
RefreshFullItem(...)100%210%
Dispose()100%11100%
Dispose(...)50%6690%
OnLibraryOptionsUpdated(...)100%22100%
ClearMetadataProviderCache()100%210%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Globalization;
 5using System.IO;
 6using System.Linq;
 7using System.Net;
 8using System.Net.Http;
 9using System.Net.Mime;
 10using System.Text.Json;
 11using System.Threading;
 12using System.Threading.Tasks;
 13using AsyncKeyedLock;
 14using Jellyfin.Data.Enums;
 15using Jellyfin.Data.Events;
 16using Jellyfin.Extensions;
 17using Jellyfin.Extensions.Json;
 18using MediaBrowser.Common.Net;
 19using MediaBrowser.Controller;
 20using MediaBrowser.Controller.BaseItemManager;
 21using MediaBrowser.Controller.Configuration;
 22using MediaBrowser.Controller.Dto;
 23using MediaBrowser.Controller.Entities;
 24using MediaBrowser.Controller.Entities.Audio;
 25using MediaBrowser.Controller.Entities.Movies;
 26using MediaBrowser.Controller.Library;
 27using MediaBrowser.Controller.Lyrics;
 28using MediaBrowser.Controller.MediaSegments;
 29using MediaBrowser.Controller.Providers;
 30using MediaBrowser.Controller.Subtitles;
 31using MediaBrowser.Model.Configuration;
 32using MediaBrowser.Model.Entities;
 33using MediaBrowser.Model.Extensions;
 34using MediaBrowser.Model.IO;
 35using MediaBrowser.Model.Net;
 36using MediaBrowser.Model.Providers;
 37using MediaBrowser.Model.Querying;
 38using Microsoft.Extensions.Caching.Memory;
 39using Microsoft.Extensions.Logging;
 40using Book = MediaBrowser.Controller.Entities.Book;
 41using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 42using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
 43using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
 44using Season = MediaBrowser.Controller.Entities.TV.Season;
 45using Series = MediaBrowser.Controller.Entities.TV.Series;
 46
 47namespace MediaBrowser.Providers.Manager
 48{
 49    /// <summary>
 50    /// Class ProviderManager.
 51    /// </summary>
 52    public class ProviderManager : IProviderManager, IDisposable
 53    {
 9154        private readonly Lock _refreshQueueLock = new();
 55        private readonly ILogger<ProviderManager> _logger;
 56        private readonly IHttpClientFactory _httpClientFactory;
 57        private readonly ILibraryMonitor _libraryMonitor;
 58        private readonly IFileSystem _fileSystem;
 59        private readonly IServerApplicationPaths _appPaths;
 60        private readonly ILibraryManager _libraryManager;
 61        private readonly ISubtitleManager _subtitleManager;
 62        private readonly ILyricManager _lyricManager;
 63        private readonly IServerConfigurationManager _configurationManager;
 64        private readonly IBaseItemManager _baseItemManager;
 9165        private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new();
 9166        private readonly CancellationTokenSource _disposeCancellationTokenSource = new();
 9167        private readonly PriorityQueue<(Guid ItemId, MetadataRefreshOptions RefreshOptions), RefreshPriority> _refreshQu
 68        private readonly IMemoryCache _memoryCache;
 69        private readonly IMediaSegmentManager _mediaSegmentManager;
 70        private readonly ISimilarItemsManager _similarItemsManager;
 9171        private readonly AsyncKeyedLocker<string> _imageSaveLock = new(o =>
 9172        {
 9173            o.PoolSize = 20;
 9174            o.PoolInitialFill = 1;
 9175        });
 76
 77        /// <summary>
 78        /// Cache for ordered metadata providers per library/item type combination.
 79        /// Key: (LibraryPath, ItemTypeName, IncludeDisabled, ForceEnableInternetMetadata).
 80        /// Value: Array of ordered metadata providers (before per-item filtering).
 81        /// </summary>
 9182        private readonly ConcurrentDictionary<MetadataProviderCacheKey, IMetadataProvider[]> _metadataProviderCache = ne
 83
 9184        private IImageProvider[] _imageProviders = [];
 9185        private IMetadataService[] _metadataServices = [];
 9186        private IMetadataProvider[] _metadataProviders = [];
 9187        private IMetadataSaver[] _savers = [];
 9188        private IExternalId[] _externalIds = [];
 9189        private IExternalUrlProvider[] _externalUrlProviders = [];
 90        private bool _isProcessingRefreshQueue;
 91        private bool _disposed;
 92
 93        /// <summary>
 94        /// Initializes a new instance of the <see cref="ProviderManager"/> class.
 95        /// </summary>
 96        /// <param name="httpClientFactory">The Http client factory.</param>
 97        /// <param name="subtitleManager">The subtitle manager.</param>
 98        /// <param name="configurationManager">The configuration manager.</param>
 99        /// <param name="libraryMonitor">The library monitor.</param>
 100        /// <param name="logger">The logger.</param>
 101        /// <param name="fileSystem">The filesystem.</param>
 102        /// <param name="appPaths">The server application paths.</param>
 103        /// <param name="libraryManager">The library manager.</param>
 104        /// <param name="baseItemManager">The BaseItem manager.</param>
 105        /// <param name="lyricManager">The lyric manager.</param>
 106        /// <param name="memoryCache">The memory cache.</param>
 107        /// <param name="mediaSegmentManager">The media segment manager.</param>
 108        /// <param name="similarItemsManager">The similar items manager.</param>
 109        public ProviderManager(
 110            IHttpClientFactory httpClientFactory,
 111            ISubtitleManager subtitleManager,
 112            IServerConfigurationManager configurationManager,
 113            ILibraryMonitor libraryMonitor,
 114            ILogger<ProviderManager> logger,
 115            IFileSystem fileSystem,
 116            IServerApplicationPaths appPaths,
 117            ILibraryManager libraryManager,
 118            IBaseItemManager baseItemManager,
 119            ILyricManager lyricManager,
 120            IMemoryCache memoryCache,
 121            IMediaSegmentManager mediaSegmentManager,
 122            ISimilarItemsManager similarItemsManager)
 123        {
 91124            _logger = logger;
 91125            _httpClientFactory = httpClientFactory;
 91126            _configurationManager = configurationManager;
 91127            _libraryMonitor = libraryMonitor;
 91128            _fileSystem = fileSystem;
 91129            _appPaths = appPaths;
 91130            _libraryManager = libraryManager;
 91131            _subtitleManager = subtitleManager;
 91132            _baseItemManager = baseItemManager;
 91133            _lyricManager = lyricManager;
 91134            _memoryCache = memoryCache;
 91135            _mediaSegmentManager = mediaSegmentManager;
 91136            _similarItemsManager = similarItemsManager;
 137
 91138            CollectionFolder.LibraryOptionsUpdated += OnLibraryOptionsUpdated;
 91139        }
 140
 141        /// <inheritdoc/>
 142        public event EventHandler<GenericEventArgs<BaseItem>>? RefreshStarted;
 143
 144        /// <inheritdoc/>
 145        public event EventHandler<GenericEventArgs<BaseItem>>? RefreshCompleted;
 146
 147        /// <inheritdoc/>
 148        public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>>? RefreshProgress;
 149
 150        /// <inheritdoc/>
 151        public void AddParts(
 152            IEnumerable<IImageProvider> imageProviders,
 153            IEnumerable<IMetadataService> metadataServices,
 154            IEnumerable<IMetadataProvider> metadataProviders,
 155            IEnumerable<IMetadataSaver> metadataSavers,
 156            IEnumerable<IExternalId> externalIds,
 157            IEnumerable<IExternalUrlProvider> externalUrlProviders)
 158        {
 91159            _imageProviders = imageProviders.ToArray();
 91160            _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
 91161            _metadataProviders = metadataProviders.ToArray();
 91162            _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
 91163            _externalUrlProviders = externalUrlProviders.OrderBy(i => i.Name).ToArray();
 164
 91165            _savers = metadataSavers.ToArray();
 91166        }
 167
 168        /// <inheritdoc/>
 169        public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken c
 170        {
 63171            var type = item.GetType();
 172
 63173            var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type))
 63174                ?? _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
 175
 63176            if (service is null)
 177            {
 1178                _logger.LogError("Unable to find a metadata service for item of type {TypeName}", type.Name);
 1179                return Task.FromResult(ItemUpdateType.None);
 180            }
 181
 62182            return service.RefreshMetadata(item, options, cancellationToken);
 183        }
 184
 185        /// <inheritdoc/>
 186        public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancel
 187        {
 0188            using (await _imageSaveLock.LockAsync(url, cancellationToken).ConfigureAwait(false))
 189            {
 0190                if (_memoryCache.TryGetValue(url, out (string ContentType, byte[] ImageContents)? cachedValue)
 0191                    && cachedValue is not null)
 192                {
 0193                    var imageContents = cachedValue.Value.ImageContents;
 0194                    var cacheStream = new MemoryStream(imageContents, 0, imageContents.Length, false);
 0195                    await using (cacheStream.ConfigureAwait(false))
 196                    {
 0197                        await SaveImage(
 0198                            item,
 0199                            cacheStream,
 0200                            cachedValue.Value.ContentType,
 0201                            type,
 0202                            imageIndex,
 0203                            cancellationToken).ConfigureAwait(false);
 0204                        return;
 205                    }
 206                }
 207
 0208                var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
 0209                using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
 210
 0211                response.EnsureSuccessStatusCode();
 212
 0213                var contentType = response.Content.Headers.ContentType?.MediaType;
 214
 215                // Workaround for tvheadend channel icons
 216                // TODO: Isolate this hack into the tvh plugin
 0217                if (string.IsNullOrEmpty(contentType))
 218                {
 219                    // Special case for imagecache
 0220                    if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase))
 221                    {
 0222                        contentType = MediaTypeNames.Image.Png;
 223                    }
 224                }
 225
 226                // some providers don't correctly report media type, extract from url if no extension found
 0227                if (contentType is null || contentType.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordinal
 228                {
 229                    // Strip query parameters from url to get actual path.
 0230                    contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
 231                }
 232
 0233                if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
 234                {
 0235                    throw new HttpRequestException($"Request returned '{contentType}' instead of an image type", null, H
 236                }
 237
 0238                var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false)
 0239                var stream = new MemoryStream(responseBytes, 0, responseBytes.Length, false);
 0240                await using (stream.ConfigureAwait(false))
 241                {
 0242                    _memoryCache.Set(url, (contentType, responseBytes), TimeSpan.FromSeconds(10));
 243
 0244                    await SaveImage(
 0245                        item,
 0246                        stream,
 0247                        contentType,
 0248                        type,
 0249                        imageIndex,
 0250                        cancellationToken).ConfigureAwait(false);
 251                }
 0252            }
 0253        }
 254
 255        /// <inheritdoc/>
 256        public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, Cancellati
 257        {
 0258            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, 
 259        }
 260
 261        /// <inheritdoc/>
 262        public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool
 263        {
 0264            if (string.IsNullOrWhiteSpace(source))
 265            {
 0266                throw new ArgumentNullException(nameof(source));
 267            }
 268
 269            try
 270            {
 0271                var fileStream = AsyncFile.OpenRead(source);
 0272                await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0273                    .SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken)
 0274                    .ConfigureAwait(false);
 0275            }
 276            finally
 277            {
 278                try
 279                {
 0280                    File.Delete(source);
 0281                }
 0282                catch (Exception ex)
 283                {
 0284                    _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source);
 0285                }
 286            }
 0287        }
 288
 289        /// <inheritdoc/>
 290        public Task SaveImage(Stream source, string mimeType, string path)
 291        {
 0292            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0293                .SaveImage(source, path);
 294        }
 295
 296        /// <inheritdoc/>
 297        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, 
 298        {
 0299            var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
 300
 0301            if (!string.IsNullOrEmpty(query.ProviderName))
 302            {
 0303                var providerName = query.ProviderName;
 304
 0305                providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)
 306            }
 307
 0308            if (query.ImageType is not null)
 309            {
 0310                providers = providers.Where(i => i.GetSupportedImages(item).Contains(query.ImageType.Value));
 311            }
 312
 0313            var preferredLanguage = item.GetPreferredMetadataLanguage();
 314
 0315            var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellat
 316
 0317            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 318
 0319            return results.SelectMany(i => i);
 0320        }
 321
 322        /// <summary>
 323        /// Gets the images.
 324        /// </summary>
 325        /// <param name="item">The item.</param>
 326        /// <param name="provider">The provider.</param>
 327        /// <param name="preferredLanguage">The preferred language.</param>
 328        /// <param name="includeAllLanguages">Whether to include all languages in results.</param>
 329        /// <param name="cancellationToken">The cancellation token.</param>
 330        /// <param name="type">The type.</param>
 331        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
 332        private async Task<IEnumerable<RemoteImageInfo>> GetImages(
 333            BaseItem item,
 334            IRemoteImageProvider provider,
 335            string preferredLanguage,
 336            bool includeAllLanguages,
 337            CancellationToken cancellationToken,
 338            ImageType? type = null)
 339        {
 0340            bool hasPreferredLanguage = !string.IsNullOrWhiteSpace(preferredLanguage);
 341
 342            try
 343            {
 0344                var result = await provider.GetImages(item, cancellationToken).ConfigureAwait(false);
 345
 0346                if (type.HasValue)
 347                {
 0348                    result = result.Where(i => i.Type == type.Value);
 349                }
 350
 0351                if (!includeAllLanguages && hasPreferredLanguage)
 352                {
 353                    // Filter out languages that do not match the preferred languages.
 354                    //
 355                    // TODO: should exception case of "en" (English) eventually be removed?
 0356                    result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
 0357                                               string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgno
 0358                                               string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
 359                }
 360
 0361                return result.OrderByLanguageDescending(preferredLanguage);
 362            }
 0363            catch (OperationCanceledException)
 364            {
 0365                return Enumerable.Empty<RemoteImageInfo>();
 366            }
 0367            catch (Exception ex)
 368            {
 0369                _logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provide
 0370                return Enumerable.Empty<RemoteImageInfo>();
 371            }
 0372        }
 373
 374        /// <inheritdoc/>
 375        public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
 376        {
 0377            return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(it
 378        }
 379
 380        private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled)
 381        {
 0382            var options = GetMetadataOptions(item);
 0383            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 384
 0385            return GetImageProvidersInternal(
 0386                item,
 0387                libraryOptions,
 0388                options,
 0389                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0390                includeDisabled).OfType<IRemoteImageProvider>();
 391        }
 392
 393        /// <inheritdoc/>
 394        public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
 395        {
 81396            return GetImageProvidersInternal(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), re
 397        }
 398
 399        private IEnumerable<IImageProvider> GetImageProvidersInternal(BaseItem item, LibraryOptions libraryOptions, Meta
 400        {
 81401            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 81402            var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder;
 403
 81404            return _imageProviders.Where(i => CanRefreshImages(i, item, typeOptions, refreshOptions, includeDisabled))
 81405                .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name))
 81406                .ThenBy(GetDefaultOrder);
 407        }
 408
 409        private bool CanRefreshImages(
 410            IImageProvider provider,
 411            BaseItem item,
 412            TypeOptions? libraryTypeOptions,
 413            ImageRefreshOptions refreshOptions,
 414            bool includeDisabled)
 415        {
 416            try
 417            {
 1131418                if (!provider.Supports(item))
 419                {
 970420                    return false;
 421                }
 160422            }
 1423            catch (Exception ex)
 424            {
 1425                _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.Get
 1426                return false;
 427            }
 428
 160429            if (includeDisabled || provider is ILocalImageProvider)
 430            {
 153431                return true;
 432            }
 433
 7434            if (item.IsLocked && refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh)
 435            {
 1436                return false;
 437            }
 438
 6439            return _baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name);
 971440        }
 441
 442        /// <inheritdoc />
 443        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
 444            where T : BaseItem
 445        {
 97446            var globalMetadataOptions = GetMetadataOptions(item);
 97447            var libraryPath = GetLibraryPathForItem(item);
 448
 97449            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false, libraryPat
 450        }
 451
 452        /// <summary>
 453        /// Gets metadata providers for the specified item.
 454        /// </summary>
 455        /// <typeparam name="T">The item type.</typeparam>
 456        /// <param name="item">The item.</param>
 457        /// <param name="libraryOptions">The library options.</param>
 458        /// <param name="includeDisabled">Whether to include disabled providers.</param>
 459        /// <returns>The metadata providers.</returns>
 460        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions, b
 461            where T : BaseItem
 462        {
 0463            var globalMetadataOptions = GetMetadataOptions(item);
 0464            var libraryPath = GetLibraryPathForItem(item);
 465
 0466            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, includeDisabled, false, 
 467        }
 468
 469        private static string GetLibraryPathForItem(BaseItem item)
 470        {
 97471            if (item is CollectionFolder collectionFolder)
 472            {
 14473                return collectionFolder.Path ?? string.Empty;
 474            }
 475
 83476            var topParent = item.GetTopParent();
 83477            return topParent?.Path ?? string.Empty;
 478        }
 479
 480        /// <inheritdoc />
 481        public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
 482        {
 34483            return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false)
 484        }
 485
 486        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryO
 487            where T : BaseItem
 488        {
 97489            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 490
 97491            var orderedProviders = GetOrCreateOrderedProviders<T>(item.GetType().Name, libraryOptions, globalMetadataOpt
 492
 97493            return orderedProviders.Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInte
 494        }
 495
 496        private IMetadataProvider<T>[] GetOrCreateOrderedProviders<T>(
 497            string itemTypeName,
 498            LibraryOptions libraryOptions,
 499            MetadataOptions globalMetadataOptions,
 500            bool includeDisabled,
 501            bool forceEnableInternetMetadata,
 502            string libraryPath)
 503            where T : BaseItem
 504        {
 97505            var cacheKey = new MetadataProviderCacheKey(libraryPath, itemTypeName, includeDisabled, forceEnableInternetM
 97506            if (_metadataProviderCache.TryGetValue(cacheKey, out var cachedProviders))
 507            {
 23508                return cachedProviders.OfType<IMetadataProvider<T>>().ToArray();
 509            }
 510
 74511            var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadat
 74512            var typeOptions = libraryOptions.GetTypeOptions(itemTypeName);
 74513            var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
 514
 74515            var orderedProviders = _metadataProviders.OfType<IMetadataProvider<T>>()
 74516                .Where(i => CanRefreshMetadataForCache(i, typeOptions, includeDisabled, forceEnableInternetMetadata))
 74517                .OrderBy(i =>
 74518                    // local and remote providers will be interleaved in the final order
 74519                    // only relative order within a type matters: consumers of the list filter to one or the other
 74520                    i switch
 74521                    {
 74522                        ILocalMetadataProvider => GetConfiguredOrder(localMetadataReaderOrder, i.Name),
 74523                        IRemoteMetadataProvider => GetConfiguredOrder(metadataFetcherOrder, i.Name),
 74524                        // Default to end
 74525                        _ => int.MaxValue
 74526                    })
 74527                .ThenBy(GetDefaultOrder)
 74528                .ToArray();
 529
 74530            _metadataProviderCache.TryAdd(cacheKey, orderedProviders.Cast<IMetadataProvider>().ToArray());
 531
 74532            return orderedProviders;
 533        }
 534
 535        private static bool CanRefreshMetadataForCache(
 536            IMetadataProvider provider,
 537            TypeOptions? libraryTypeOptions,
 538            bool includeDisabled,
 539            bool forceEnableInternetMetadata)
 540        {
 141541            if (includeDisabled)
 542            {
 0543                return true;
 544            }
 545
 141546            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 547            {
 93548                return true;
 549            }
 550
 48551            if (libraryTypeOptions?.MetadataFetchers is { Length: > 0 } metadataFetchers)
 552            {
 0553                return metadataFetchers.Contains(provider.Name, StringComparer.OrdinalIgnoreCase);
 554            }
 555
 48556            return true;
 557        }
 558
 559        private bool CanRefreshMetadata(
 560            IMetadataProvider provider,
 561            BaseItem item,
 562            TypeOptions? libraryTypeOptions,
 563            bool includeDisabled,
 564            bool forceEnableInternetMetadata)
 565        {
 164566            if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider)
 567            {
 1568                return false;
 569            }
 570
 163571            if (includeDisabled)
 572            {
 0573                return true;
 574            }
 575
 576            // If locked only allow local providers
 163577            if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider)
 578            {
 3579                return false;
 580            }
 581
 160582            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 583            {
 113584                return true;
 585            }
 586
 587            // Artists without a folder structure that are derived from metadata have no real path in the library,
 588            // so GetLibraryOptions returns null. Allow all providers through rather than blocking them.
 47589            if (item is MusicArtist && libraryTypeOptions is null)
 590            {
 0591                return true;
 592            }
 593
 47594            return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
 595        }
 596
 597        private static int GetConfiguredOrder(string[] order, string providerName)
 598        {
 237599            var index = Array.IndexOf(order, providerName);
 600
 237601            if (index != -1)
 602            {
 54603                return index;
 604            }
 605
 606            // default to end
 183607            return int.MaxValue;
 608        }
 609
 610        private static int GetDefaultOrder(object provider)
 611        {
 237612            if (provider is IHasOrder hasOrder)
 613            {
 139614                return hasOrder.Order;
 615            }
 616
 617            // after items that want to be first (~0) but before items that want to be last (~100)
 98618            return 50;
 619        }
 620
 621        /// <inheritdoc/>
 622        public MetadataPluginSummary[] GetAllMetadataPlugins()
 623        {
 0624            return new[]
 0625            {
 0626                GetPluginSummary<Movie>(),
 0627                GetPluginSummary<BoxSet>(),
 0628                GetPluginSummary<Book>(),
 0629                GetPluginSummary<Series>(),
 0630                GetPluginSummary<Season>(),
 0631                GetPluginSummary<Episode>(),
 0632                GetPluginSummary<MusicAlbum>(),
 0633                GetPluginSummary<MusicArtist>(),
 0634                GetPluginSummary<Audio>(),
 0635                GetPluginSummary<AudioBook>(),
 0636                GetPluginSummary<Studio>(),
 0637                GetPluginSummary<MusicVideo>(),
 0638                GetPluginSummary<Video>()
 0639            };
 640        }
 641
 642        private MetadataPluginSummary GetPluginSummary<T>()
 643            where T : BaseItem, new()
 644        {
 645            // Give it a dummy path just so that it looks like a file system item
 0646            var dummy = new T
 0647            {
 0648                Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0649                ParentId = Guid.NewGuid()
 0650            };
 651
 0652            var options = GetMetadataOptions(dummy);
 653
 0654            var summary = new MetadataPluginSummary
 0655            {
 0656                ItemType = typeof(T).Name
 0657            };
 658
 0659            var libraryOptions = new LibraryOptions();
 660
 0661            var imageProviders = GetImageProvidersInternal(
 0662                dummy,
 0663                libraryOptions,
 0664                options,
 0665                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0666                true).ToList();
 667
 0668            var pluginList = summary.Plugins.ToList();
 669
 0670            AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
 0671            AddImagePlugins(pluginList, imageProviders);
 672
 673            // Subtitle fetchers
 0674            var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
 0675            pluginList.AddRange(subtitleProviders.Select(i => new MetadataPlugin
 0676            {
 0677                Name = i.Name,
 0678                Type = MetadataPluginType.SubtitleFetcher
 0679            }));
 680
 681            // Lyric fetchers
 0682            var lyricProviders = _lyricManager.GetSupportedProviders(dummy);
 0683            pluginList.AddRange(lyricProviders.Select(i => new MetadataPlugin
 0684            {
 0685                Name = i.Name,
 0686                Type = MetadataPluginType.LyricFetcher
 0687            }));
 688
 689            // Media segment providers
 0690            var mediaSegmentProviders = _mediaSegmentManager.GetSupportedProviders(dummy);
 0691            pluginList.AddRange(mediaSegmentProviders.Select(i => new MetadataPlugin
 0692            {
 0693                Name = i.Name,
 0694                Type = MetadataPluginType.MediaSegmentProvider
 0695            }));
 696
 697            // Similar items providers
 0698            var similarItemsProviders = _similarItemsManager.GetSimilarItemsProviders<T>();
 0699            pluginList.AddRange(similarItemsProviders.Select(i => new MetadataPlugin
 0700            {
 0701                Name = i.Name,
 0702                Type = i.Type
 0703            }));
 704
 0705            summary.Plugins = pluginList.ToArray();
 706
 0707            var supportedImageTypes = imageProviders.OfType<IRemoteImageProvider>()
 0708                .SelectMany(i => i.GetSupportedImages(dummy))
 0709                .ToList();
 710
 0711            supportedImageTypes.AddRange(imageProviders.OfType<IDynamicImageProvider>()
 0712                .SelectMany(i => i.GetSupportedImages(dummy)));
 713
 0714            summary.SupportedImageTypes = supportedImageTypes.Distinct().ToArray();
 715
 0716            return summary;
 717        }
 718
 719        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, LibraryOptions libraryOptions, MetadataOpt
 720            where T : BaseItem
 721        {
 0722            var libraryPath = GetLibraryPathForItem(item);
 0723            var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true, libraryPath).ToLi
 724
 725            // Locals
 0726            list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin
 0727            {
 0728                Name = i.Name,
 0729                Type = MetadataPluginType.LocalMetadataProvider
 0730            }));
 731
 732            // Fetchers
 0733            list.AddRange(providers.Where(i => i is IRemoteMetadataProvider).Select(i => new MetadataPlugin
 0734            {
 0735                Name = i.Name,
 0736                Type = MetadataPluginType.MetadataFetcher
 0737            }));
 738
 739            // Savers
 0740            list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit,
 0741            {
 0742                Name = i.Name,
 0743                Type = MetadataPluginType.MetadataSaver
 0744            }));
 0745        }
 746
 747        private void AddImagePlugins(List<MetadataPlugin> list, List<IImageProvider> imageProviders)
 748        {
 749            // Locals
 0750            list.AddRange(imageProviders.Where(i => i is ILocalImageProvider).Select(i => new MetadataPlugin
 0751            {
 0752                Name = i.Name,
 0753                Type = MetadataPluginType.LocalImageProvider
 0754            }));
 755
 756            // Fetchers
 0757            list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (i is IRemoteImageProvider)).Select(i 
 0758            {
 0759                Name = i.Name,
 0760                Type = MetadataPluginType.ImageFetcher
 0761            }));
 0762        }
 763
 764        /// <inheritdoc/>
 765        public MetadataOptions GetMetadataOptions(BaseItem item)
 1322766            => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
 767
 768        /// <inheritdoc/>
 769        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
 109770            => SaveMetadataAsync(item, updateType, _savers);
 771
 772        /// <inheritdoc/>
 773        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
 0774            => SaveMetadataAsync(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIg
 775
 776        /// <summary>
 777        /// Saves the metadata.
 778        /// </summary>
 779        /// <param name="item">The item.</param>
 780        /// <param name="updateType">Type of the update.</param>
 781        /// <param name="savers">The savers.</param>
 782        private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> saver
 783        {
 109784            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 109785            var applicableSavers = savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)).
 109786            if (applicableSavers.Count == 0)
 787            {
 109788                return;
 789            }
 790
 0791            foreach (var saver in applicableSavers)
 792            {
 0793                _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
 794
 0795                if (saver is IMetadataFileSaver fileSaver)
 796                {
 797                    string path;
 798
 799                    try
 800                    {
 0801                        path = fileSaver.GetSavePath(item);
 0802                    }
 0803                    catch (Exception ex)
 804                    {
 0805                        _logger.LogError(ex, "Error in {Saver} GetSavePath", saver.Name);
 0806                        continue;
 807                    }
 808
 809                    try
 810                    {
 0811                        _libraryMonitor.ReportFileSystemChangeBeginning(path);
 0812                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 0813                        item.DateLastSaved = DateTime.UtcNow;
 0814                    }
 0815                    catch (Exception ex)
 816                    {
 0817                        _logger.LogError(ex, "Error in metadata saver");
 0818                    }
 819                    finally
 820                    {
 0821                        _libraryMonitor.ReportFileSystemChangeComplete(path, false);
 822                    }
 0823                }
 824                else
 825                {
 826                    try
 827                    {
 0828                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 0829                        item.DateLastSaved = DateTime.UtcNow;
 0830                    }
 0831                    catch (Exception ex)
 832                    {
 0833                        _logger.LogError(ex, "Error in metadata saver");
 0834                    }
 835                }
 836            }
 109837        }
 838
 839        /// <summary>
 840        /// Determines whether [is saver enabled for item] [the specified saver].
 841        /// </summary>
 842        private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdat
 843        {
 1144844            var options = GetMetadataOptions(item);
 845
 846            try
 847            {
 1144848                if (!saver.IsEnabledFor(item, updateType))
 849                {
 1144850                    return false;
 851                }
 852
 0853                if (!includeDisabled)
 854                {
 0855                    if (libraryOptions.MetadataSavers is null)
 856                    {
 0857                        if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 858                        {
 0859                            return false;
 860                        }
 861
 0862                        if (!item.IsSaveLocalMetadataEnabled())
 863                        {
 0864                            if (updateType >= ItemUpdateType.MetadataEdit)
 865                            {
 866                                // Manual edit occurred
 867                                // Even if save local is off, save locally anyway if the metadata file already exists
 0868                                if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item
 869                                {
 0870                                    return false;
 871                                }
 872                            }
 873                            else
 874                            {
 875                                // Manual edit did not occur
 876                                // Since local metadata saving is disabled, consider it disabled
 0877                                return false;
 878                            }
 879                        }
 880                    }
 881                    else
 882                    {
 0883                        if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 884                        {
 0885                            return false;
 886                        }
 887                    }
 888                }
 889
 0890                return true;
 891            }
 0892            catch (Exception ex)
 893            {
 0894                _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name);
 0895                return false;
 896            }
 1144897        }
 898
 899        /// <inheritdoc/>
 900        public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TL
 901            where TItemType : BaseItem, new()
 902            where TLookupType : ItemLookupInfo
 903        {
 0904            BaseItem? referenceItem = null;
 905
 0906            if (!searchInfo.ItemId.IsEmpty())
 907            {
 0908                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
 909            }
 910
 0911            return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
 912        }
 913
 914        private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQ
 915            where TItemType : BaseItem, new()
 916            where TLookupType : ItemLookupInfo
 917        {
 918            LibraryOptions libraryOptions;
 919
 0920            if (referenceItem is null)
 921            {
 922                // Give it a dummy path just so that it looks like a file system item
 0923                var dummy = new TItemType
 0924                {
 0925                    Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0926                    ParentId = Guid.NewGuid()
 0927                };
 928
 0929                dummy.SetParent(new Folder());
 930
 0931                referenceItem = dummy;
 0932                libraryOptions = new LibraryOptions();
 933            }
 934            else
 935            {
 0936                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
 937            }
 938
 0939            var options = GetMetadataOptions(referenceItem);
 0940            var libraryPath = GetLibraryPathForItem(referenceItem);
 0941            var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.I
 0942                .OfType<IRemoteSearchProvider<TLookupType>>();
 943
 0944            if (!string.IsNullOrEmpty(searchInfo.SearchProviderName))
 945            {
 0946                providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.O
 947            }
 948
 0949            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
 950            {
 0951                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
 952            }
 953
 0954            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
 955            {
 0956                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
 957            }
 958
 0959            var resultList = new List<RemoteSearchResult>();
 960
 0961            foreach (var provider in providers)
 962            {
 963                try
 964                {
 0965                    var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwa
 966
 0967                    foreach (var result in results)
 968                    {
 0969                        result.SearchProviderName = provider.Name;
 970
 0971                        var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.G
 972
 0973                        if (existingMatch is null)
 974                        {
 0975                            resultList.Add(result);
 976                        }
 977                        else
 978                        {
 0979                            foreach (var providerId in result.ProviderIds)
 980                            {
 0981                                existingMatch.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 982                            }
 983
 0984                            if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
 985                            {
 0986                                existingMatch.ImageUrl = result.ImageUrl;
 987                            }
 988                        }
 989                    }
 0990                }
 991#pragma warning disable CA1031 // do not catch general exception types
 0992                catch (Exception ex)
 993#pragma warning restore CA1031 // do not catch general exception types
 994                {
 0995                    _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
 0996                }
 0997            }
 998
 0999            return resultList;
 01000        }
 1001
 1002        private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
 1003        {
 01004            return _externalIds.Where(i =>
 01005            {
 01006                try
 01007                {
 01008                    return i.Supports(item);
 01009                }
 01010                catch (Exception ex)
 01011                {
 01012                    _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name);
 01013                    return false;
 01014                }
 01015            });
 1016        }
 1017
 1018        /// <inheritdoc/>
 1019        public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
 1020        {
 61021            return _externalUrlProviders
 61022                .SelectMany(p => p
 61023                    .GetExternalUrls(item)
 61024                    .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
 1025        }
 1026
 1027        /// <inheritdoc/>
 1028        public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
 1029        {
 01030            return GetExternalIds(item)
 01031                .Select(i => new ExternalIdInfo(
 01032                    name: i.ProviderName,
 01033                    key: i.Key,
 01034                    type: i.Type));
 1035        }
 1036
 1037        /// <inheritdoc/>
 1038        public HashSet<Guid> GetRefreshQueue()
 11039        {
 1040            lock (_refreshQueueLock)
 1041            {
 11042                return _refreshQueue.UnorderedItems.Select(x => x.Element.ItemId).ToHashSet();
 1043            }
 11044        }
 1045
 1046        /// <inheritdoc/>
 1047        public void OnRefreshStart(BaseItem item)
 1048        {
 161049            _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
 161050            _activeRefreshes[item.Id] = 0;
 1051            try
 1052            {
 161053                RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 161054            }
 01055            catch (Exception ex)
 1056            {
 1057                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01058                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
 01059            }
 161060        }
 1061
 1062        /// <inheritdoc/>
 1063        public void OnRefreshComplete(BaseItem item)
 1064        {
 161065            _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
 161066            _activeRefreshes.TryRemove(item.Id, out _);
 1067
 1068            try
 1069            {
 161070                RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 161071            }
 01072            catch (Exception ex)
 1073            {
 1074                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01075                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
 01076            }
 161077        }
 1078
 1079        /// <inheritdoc/>
 1080        public double? GetRefreshProgress(Guid id)
 1081        {
 01082            if (_activeRefreshes.TryGetValue(id, out double value))
 1083            {
 01084                return value;
 1085            }
 1086
 01087            return null;
 1088        }
 1089
 1090        /// <inheritdoc/>
 1091        public void OnRefreshProgress(BaseItem item, double progress)
 1092        {
 801093            var id = item.Id;
 801094            _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
 1095
 801096            if (!_activeRefreshes.TryGetValue(id, out var current)
 801097                || progress <= current
 801098                || !_activeRefreshes.TryUpdate(id, progress, current))
 1099            {
 1100                // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
 321101                return;
 1102            }
 1103
 1104            try
 1105            {
 481106                RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(
 481107            }
 01108            catch (Exception ex)
 1109            {
 1110                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01111                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
 01112            }
 481113        }
 1114
 1115        /// <inheritdoc/>
 1116        public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
 1117        {
 01118            if (itemId.IsEmpty())
 1119            {
 01120                throw new ArgumentException("Guid can't be empty", nameof(itemId));
 1121            }
 1122
 01123            if (_disposed)
 1124            {
 01125                return;
 1126            }
 1127
 01128            _refreshQueue.Enqueue((itemId, options), priority);
 1129
 1130            lock (_refreshQueueLock)
 1131            {
 01132                if (!_isProcessingRefreshQueue)
 1133                {
 01134                    _isProcessingRefreshQueue = true;
 01135                    Task.Run(StartProcessingRefreshQueue);
 1136                }
 01137            }
 01138        }
 1139
 1140        private async Task StartProcessingRefreshQueue()
 1141        {
 01142            var libraryManager = _libraryManager;
 1143
 01144            if (_disposed)
 1145            {
 01146                return;
 1147            }
 1148
 01149            var cancellationToken = _disposeCancellationTokenSource.Token;
 1150
 01151            libraryManager.ClearIgnoreRuleCache();
 01152            while (_refreshQueue.TryDequeue(out var refreshItem, out _))
 1153            {
 01154                if (_disposed)
 1155                {
 01156                    return;
 1157                }
 1158
 1159                try
 1160                {
 01161                    var item = libraryManager.GetItemById(refreshItem.ItemId);
 01162                    if (item is null)
 1163                    {
 01164                        continue;
 1165                    }
 1166
 01167                    var task = item is MusicArtist artist
 01168                        ? RefreshArtist(artist, refreshItem.RefreshOptions, cancellationToken)
 01169                        : RefreshItem(item, refreshItem.RefreshOptions, cancellationToken);
 1170
 01171                    await task.ConfigureAwait(false);
 01172                }
 01173                catch (OperationCanceledException)
 1174                {
 01175                    break;
 1176                }
 01177                catch (Exception ex)
 1178                {
 01179                    _logger.LogError(ex, "Error refreshing item");
 01180                }
 1181            }
 1182
 1183            lock (_refreshQueueLock)
 1184            {
 01185                _isProcessingRefreshQueue = false;
 01186                libraryManager.ClearIgnoreRuleCache();
 01187            }
 01188        }
 1189
 1190        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToke
 1191        {
 01192            await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1193
 1194            // Collection folders don't validate their children so we'll have to simulate that here
 1195            switch (item)
 1196            {
 1197                case CollectionFolder collectionFolder:
 01198                    await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(f
 01199                    break;
 1200                case Folder folder:
 01201                    await folder.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken)
 1202                    break;
 1203            }
 01204        }
 1205
 1206        private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFo
 1207        {
 01208            foreach (var child in collectionFolder.GetPhysicalFolders())
 1209            {
 01210                await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1211
 01212                await child.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken).Conf
 01213            }
 01214        }
 1215
 1216        private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellatio
 1217        {
 01218            var albums = _libraryManager
 01219                .GetItemList(new InternalItemsQuery
 01220                {
 01221                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 01222                    ArtistIds = new[] { item.Id },
 01223                    DtoOptions = new DtoOptions(false)
 01224                    {
 01225                        EnableImages = false
 01226                    }
 01227                })
 01228                .OfType<MusicAlbum>();
 1229
 01230            var musicArtists = albums
 01231                .Select(i => i.MusicArtist)
 01232                .Where(i => i is not null)
 01233                .Distinct();
 1234
 01235            var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, t
 1236
 01237            await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
 1238
 1239            try
 1240            {
 01241                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 01242            }
 01243            catch (Exception ex)
 1244            {
 01245                _logger.LogError(ex, "Error refreshing library");
 01246            }
 01247        }
 1248
 1249        /// <inheritdoc/>
 1250        public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 1251        {
 01252            return RefreshItem(item, options, cancellationToken);
 1253        }
 1254
 1255        /// <inheritdoc/>
 1256        public void Dispose()
 1257        {
 911258            Dispose(true);
 911259            GC.SuppressFinalize(this);
 911260        }
 1261
 1262        /// <summary>
 1263        /// Releases unmanaged and optionally managed resources.
 1264        /// </summary>
 1265        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 1266        protected virtual void Dispose(bool disposing)
 1267        {
 911268            if (_disposed)
 1269            {
 01270                return;
 1271            }
 1272
 911273            if (disposing)
 1274            {
 911275                CollectionFolder.LibraryOptionsUpdated -= OnLibraryOptionsUpdated;
 1276
 911277                if (!_disposeCancellationTokenSource.IsCancellationRequested)
 1278                {
 911279                    _disposeCancellationTokenSource.Cancel();
 1280                }
 1281
 911282                _disposeCancellationTokenSource.Dispose();
 911283                _imageSaveLock.Dispose();
 1284            }
 1285
 911286            _disposed = true;
 911287        }
 1288
 1289        private void OnLibraryOptionsUpdated(object? sender, LibraryOptionsUpdatedEventArgs e)
 1290        {
 31291            var keysToRemove = _metadataProviderCache.Keys
 31292                .Where(k => string.Equals(k.LibraryPath, e.LibraryPath, StringComparison.Ordinal))
 31293                .ToList();
 1294
 81295            foreach (var key in keysToRemove)
 1296            {
 11297                _metadataProviderCache.TryRemove(key, out _);
 1298            }
 1299
 31300            _logger.LogDebug("Invalidated metadata provider cache for library: {LibraryPath}", e.LibraryPath);
 31301        }
 1302
 1303        internal void ClearMetadataProviderCache()
 1304        {
 01305            _metadataProviderCache.Clear();
 01306            _logger.LogDebug("Cleared entire metadata provider cache");
 01307        }
 1308
 1309        /// <summary>
 1310        /// Cache key for metadata provider lookups.
 1311        /// </summary>
 1312        /// <param name="LibraryPath">The library path for the collection folder.</param>
 1313        /// <param name="ItemTypeName">The item type name.</param>
 1314        /// <param name="IncludeDisabled">Whether to include disabled providers.</param>
 1315        /// <param name="ForceEnableInternetMetadata">Whether internet metadata is force-enabled.</param>
 1316        private readonly record struct MetadataProviderCacheKey(
 1317            string LibraryPath,
 1318            string ItemTypeName,
 1319            bool IncludeDisabled,
 1320            bool ForceEnableInternetMetadata);
 1321    }
 1322}

Methods/Properties

.ctor(System.Net.Http.IHttpClientFactory,MediaBrowser.Controller.Subtitles.ISubtitleManager,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryMonitor,Microsoft.Extensions.Logging.ILogger`1<MediaBrowser.Providers.Manager.ProviderManager>,MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.IServerApplicationPaths,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.BaseItemManager.IBaseItemManager,MediaBrowser.Controller.Lyrics.ILyricManager,Microsoft.Extensions.Caching.Memory.IMemoryCache,MediaBrowser.Controller.MediaSegments.IMediaSegmentManager,MediaBrowser.Controller.Library.ISimilarItemsManager)
AddParts(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IImageProvider>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IMetadataService>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IMetadataProvider>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Library.IMetadataSaver>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IExternalId>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IExternalUrlProvider>)
RefreshSingleItem(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
SaveImage()
SaveImage(MediaBrowser.Controller.Entities.BaseItem,System.IO.Stream,System.String,MediaBrowser.Model.Entities.ImageType,System.Nullable`1<System.Int32>,System.Threading.CancellationToken)
SaveImage()
SaveImage(System.IO.Stream,System.String,System.String)
GetAvailableRemoteImages()
GetImages()
GetRemoteImageProviderInfo(MediaBrowser.Controller.Entities.BaseItem)
GetRemoteImageProviders(MediaBrowser.Controller.Entities.BaseItem,System.Boolean)
GetImageProviders(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.ImageRefreshOptions)
GetImageProvidersInternal(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.LibraryOptions,MediaBrowser.Model.Configuration.MetadataOptions,MediaBrowser.Controller.Providers.ImageRefreshOptions,System.Boolean)
CanRefreshImages(MediaBrowser.Controller.Providers.IImageProvider,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.TypeOptions,MediaBrowser.Controller.Providers.ImageRefreshOptions,System.Boolean)
GetMetadataProviders(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.LibraryOptions)
GetMetadataProviders(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.LibraryOptions,System.Boolean)
GetLibraryPathForItem(MediaBrowser.Controller.Entities.BaseItem)
GetMetadataSavers(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.LibraryOptions)
GetMetadataProvidersInternal(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.LibraryOptions,MediaBrowser.Model.Configuration.MetadataOptions,System.Boolean,System.Boolean,System.String)
GetOrCreateOrderedProviders(System.String,MediaBrowser.Model.Configuration.LibraryOptions,MediaBrowser.Model.Configuration.MetadataOptions,System.Boolean,System.Boolean,System.String)
CanRefreshMetadataForCache(MediaBrowser.Controller.Providers.IMetadataProvider,MediaBrowser.Model.Configuration.TypeOptions,System.Boolean,System.Boolean)
CanRefreshMetadata(MediaBrowser.Controller.Providers.IMetadataProvider,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.TypeOptions,System.Boolean,System.Boolean)
GetConfiguredOrder(System.String[],System.String)
GetDefaultOrder(System.Object)
GetAllMetadataPlugins()
GetPluginSummary()
AddMetadataPlugins(System.Collections.Generic.List`1<MediaBrowser.Model.Configuration.MetadataPlugin>,T,MediaBrowser.Model.Configuration.LibraryOptions,MediaBrowser.Model.Configuration.MetadataOptions)
AddImagePlugins(System.Collections.Generic.List`1<MediaBrowser.Model.Configuration.MetadataPlugin>,System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.IImageProvider>)
GetMetadataOptions(MediaBrowser.Controller.Entities.BaseItem)
SaveMetadataAsync(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType)
SaveMetadataAsync(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Library.ItemUpdateType,System.Collections.Generic.IEnumerable`1<System.String>)
SaveMetadataAsync()
IsSaverEnabledForItem(MediaBrowser.Controller.Library.IMetadataSaver,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.LibraryOptions,MediaBrowser.Controller.Library.ItemUpdateType,System.Boolean)
GetRemoteSearchResults(MediaBrowser.Controller.Providers.RemoteSearchQuery`1<TLookupType>,System.Threading.CancellationToken)
GetRemoteSearchResults()
GetExternalIds(MediaBrowser.Model.Entities.IHasProviderIds)
GetExternalUrls(MediaBrowser.Controller.Entities.BaseItem)
GetExternalIdInfos(MediaBrowser.Model.Entities.IHasProviderIds)
GetRefreshQueue()
OnRefreshStart(MediaBrowser.Controller.Entities.BaseItem)
OnRefreshComplete(MediaBrowser.Controller.Entities.BaseItem)
GetRefreshProgress(System.Guid)
OnRefreshProgress(MediaBrowser.Controller.Entities.BaseItem,System.Double)
QueueRefresh(System.Guid,MediaBrowser.Controller.Providers.MetadataRefreshOptions,MediaBrowser.Controller.Providers.RefreshPriority)
StartProcessingRefreshQueue()
RefreshItem()
RefreshCollectionFolderChildren()
RefreshArtist()
RefreshFullItem(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
Dispose()
Dispose(System.Boolean)
OnLibraryOptionsUpdated(System.Object,MediaBrowser.Controller.Entities.LibraryOptionsUpdatedEventArgs)
ClearMetadataProviderCache()