< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.ProviderManager
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/ProviderManager.cs
Line coverage
32%
Covered lines: 179
Uncovered lines: 376
Coverable lines: 555
Total lines: 1340
Line coverage: 32.2%
Branch coverage
37%
Covered branches: 82
Total branches: 218
Branch coverage: 37.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/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: 13225/27/2026 - 12:15:38 AM Line coverage: 31.4% (173/550) Branch coverage: 37.7% (80/212) Total lines: 13225/28/2026 - 12:15:50 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13225/30/2026 - 12:15:32 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13225/31/2026 - 12:15:44 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/2/2026 - 12:15:49 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13226/3/2026 - 12:16:02 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13226/4/2026 - 12:15:59 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13226/5/2026 - 12:16:38 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/6/2026 - 12:15:50 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13226/7/2026 - 12:16:09 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/8/2026 - 12:16:15 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13226/9/2026 - 12:16:23 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/10/2026 - 12:16:25 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13226/11/2026 - 12:16:04 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/13/2026 - 12:13:22 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13226/14/2026 - 12:16:28 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/19/2026 - 12:16:12 AM Line coverage: 31.4% (173/550) Branch coverage: 37.7% (80/212) Total lines: 13226/20/2026 - 12:13:11 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/24/2026 - 12:15:23 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13226/25/2026 - 12:12:37 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/26/2026 - 12:13:00 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13226/27/2026 - 12:15:59 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/3/2026 - 12:15:32 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13227/5/2026 - 12:16:51 AM Line coverage: 31.4% (173/550) Branch coverage: 37.7% (80/212) Total lines: 13227/6/2026 - 12:16:28 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/7/2026 - 12:16:09 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13227/8/2026 - 12:16:24 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/9/2026 - 12:13:25 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13227/10/2026 - 12:11:57 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/19/2026 - 12:16:13 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13227/21/2026 - 12:16:33 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/25/2026 - 12:14:14 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13227/26/2026 - 12:17:51 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/28/2026 - 12:13:49 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13227/29/2026 - 12:15:53 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13227/30/2026 - 12:15:56 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13228/2/2026 - 12:17:28 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13228/3/2026 - 12:16:46 AM Line coverage: 31.5% (175/554) Branch coverage: 37.6% (82/218) Total lines: 13388/9/2026 - 12:16:58 AM Line coverage: 32.2% (179/555) Branch coverage: 37.6% (82/218) Total lines: 1340 5/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: 13225/27/2026 - 12:15:38 AM Line coverage: 31.4% (173/550) Branch coverage: 37.7% (80/212) Total lines: 13225/28/2026 - 12:15:50 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13225/30/2026 - 12:15:32 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13225/31/2026 - 12:15:44 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/2/2026 - 12:15:49 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13226/3/2026 - 12:16:02 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13226/4/2026 - 12:15:59 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13226/5/2026 - 12:16:38 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/6/2026 - 12:15:50 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13226/7/2026 - 12:16:09 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/8/2026 - 12:16:15 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13226/9/2026 - 12:16:23 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/10/2026 - 12:16:25 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13226/11/2026 - 12:16:04 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/13/2026 - 12:13:22 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13226/14/2026 - 12:16:28 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/19/2026 - 12:16:12 AM Line coverage: 31.4% (173/550) Branch coverage: 37.7% (80/212) Total lines: 13226/20/2026 - 12:13:11 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/24/2026 - 12:15:23 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13226/25/2026 - 12:12:37 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13226/26/2026 - 12:13:00 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13226/27/2026 - 12:15:59 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/3/2026 - 12:15:32 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13227/5/2026 - 12:16:51 AM Line coverage: 31.4% (173/550) Branch coverage: 37.7% (80/212) Total lines: 13227/6/2026 - 12:16:28 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/7/2026 - 12:16:09 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13227/8/2026 - 12:16:24 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/9/2026 - 12:13:25 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13227/10/2026 - 12:11:57 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/19/2026 - 12:16:13 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13227/21/2026 - 12:16:33 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/25/2026 - 12:14:14 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13227/26/2026 - 12:17:51 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13227/28/2026 - 12:13:49 AM Line coverage: 31.4% (173/550) Branch coverage: 36.7% (78/212) Total lines: 13227/29/2026 - 12:15:53 AM Line coverage: 31.4% (173/550) Branch coverage: 38.6% (82/212) Total lines: 13227/30/2026 - 12:15:56 AM Line coverage: 31.4% (173/550) Branch coverage: 37.2% (79/212) Total lines: 13228/2/2026 - 12:17:28 AM Line coverage: 31.4% (173/550) Branch coverage: 38.2% (81/212) Total lines: 13228/3/2026 - 12:16:46 AM Line coverage: 31.5% (175/554) Branch coverage: 37.6% (82/218) Total lines: 13388/9/2026 - 12:16:58 AM Line coverage: 32.2% (179/555) Branch coverage: 37.6% (82/218) Total lines: 1340

Coverage delta

Coverage delta 2 -2

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(...)78.57%141492.85%
GetMetadataProviders(...)100%11100%
GetMetadataProviders(...)100%210%
GetLibraryPathForItem(...)62.5%88100%
GetMetadataSavers(...)100%11100%
GetMetadataProvidersInternal(...)100%11100%
GetOrCreateOrderedProviders(...)90%1010100%
CanRefreshMetadataForCache(...)75%151271.42%
CanRefreshMetadata(...)77.27%282276.92%
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(...)87.5%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%11100%

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    {
 9254        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;
 9265        private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new();
 9266        private readonly CancellationTokenSource _disposeCancellationTokenSource = new();
 9267        private readonly PriorityQueue<(Guid ItemId, MetadataRefreshOptions RefreshOptions), RefreshPriority> _refreshQu
 68        private readonly IMemoryCache _memoryCache;
 69        private readonly IMediaSegmentManager _mediaSegmentManager;
 70        private readonly ISimilarItemsManager _similarItemsManager;
 9271        private readonly AsyncKeyedLocker<string> _imageSaveLock = new(o =>
 9272        {
 9273            o.PoolSize = 20;
 9274            o.PoolInitialFill = 1;
 9275        });
 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>
 9282        private readonly ConcurrentDictionary<MetadataProviderCacheKey, IMetadataProvider[]> _metadataProviderCache = ne
 83
 9284        private IImageProvider[] _imageProviders = [];
 9285        private IMetadataService[] _metadataServices = [];
 9286        private IMetadataProvider[] _metadataProviders = [];
 9287        private IMetadataSaver[] _savers = [];
 9288        private IExternalId[] _externalIds = [];
 9289        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        {
 92124            _logger = logger;
 92125            _httpClientFactory = httpClientFactory;
 92126            _configurationManager = configurationManager;
 92127            _libraryMonitor = libraryMonitor;
 92128            _fileSystem = fileSystem;
 92129            _appPaths = appPaths;
 92130            _libraryManager = libraryManager;
 92131            _subtitleManager = subtitleManager;
 92132            _baseItemManager = baseItemManager;
 92133            _lyricManager = lyricManager;
 92134            _memoryCache = memoryCache;
 92135            _mediaSegmentManager = mediaSegmentManager;
 92136            _similarItemsManager = similarItemsManager;
 137
 92138            CollectionFolder.LibraryOptionsUpdated += OnLibraryOptionsUpdated;
 92139        }
 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        {
 92159            _imageProviders = imageProviders.ToArray();
 92160            _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
 92161            _metadataProviders = metadataProviders.ToArray();
 92162            _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
 92163            _externalUrlProviders = externalUrlProviders.OrderBy(i => i.Name).ToArray();
 164
 92165            _savers = metadataSavers.ToArray();
 166
 92167            ClearMetadataProviderCache();
 92168        }
 169
 170        /// <inheritdoc/>
 171        public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken c
 172        {
 67173            var type = item.GetType();
 174
 67175            var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type))
 67176                ?? _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
 177
 67178            if (service is null)
 179            {
 1180                _logger.LogError("Unable to find a metadata service for item of type {TypeName}", type.Name);
 1181                return Task.FromResult(ItemUpdateType.None);
 182            }
 183
 66184            return service.RefreshMetadata(item, options, cancellationToken);
 185        }
 186
 187        /// <inheritdoc/>
 188        public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancel
 189        {
 0190            using (await _imageSaveLock.LockAsync(url, cancellationToken).ConfigureAwait(false))
 191            {
 0192                if (_memoryCache.TryGetValue(url, out (string ContentType, byte[] ImageContents)? cachedValue)
 0193                    && cachedValue is not null)
 194                {
 0195                    var imageContents = cachedValue.Value.ImageContents;
 0196                    var cacheStream = new MemoryStream(imageContents, 0, imageContents.Length, false);
 0197                    await using (cacheStream.ConfigureAwait(false))
 198                    {
 0199                        await SaveImage(
 0200                            item,
 0201                            cacheStream,
 0202                            cachedValue.Value.ContentType,
 0203                            type,
 0204                            imageIndex,
 0205                            cancellationToken).ConfigureAwait(false);
 0206                        return;
 207                    }
 208                }
 209
 0210                var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
 0211                using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
 212
 0213                response.EnsureSuccessStatusCode();
 214
 0215                var contentType = response.Content.Headers.ContentType?.MediaType;
 216
 217                // Workaround for tvheadend channel icons
 218                // TODO: Isolate this hack into the tvh plugin
 0219                if (string.IsNullOrEmpty(contentType))
 220                {
 221                    // Special case for imagecache
 0222                    if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase))
 223                    {
 0224                        contentType = MediaTypeNames.Image.Png;
 225                    }
 226                }
 227
 228                // some providers don't correctly report media type, extract from url if no extension found
 0229                if (contentType is null || contentType.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordinal
 230                {
 231                    // Strip query parameters from url to get actual path.
 0232                    contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
 233                }
 234
 0235                if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
 236                {
 0237                    throw new HttpRequestException($"Request returned '{contentType}' instead of an image type", null, H
 238                }
 239
 0240                var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false)
 0241                var stream = new MemoryStream(responseBytes, 0, responseBytes.Length, false);
 0242                await using (stream.ConfigureAwait(false))
 243                {
 0244                    _memoryCache.Set(url, (contentType, responseBytes), TimeSpan.FromSeconds(10));
 245
 0246                    await SaveImage(
 0247                        item,
 0248                        stream,
 0249                        contentType,
 0250                        type,
 0251                        imageIndex,
 0252                        cancellationToken).ConfigureAwait(false);
 253                }
 0254            }
 0255        }
 256
 257        /// <inheritdoc/>
 258        public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, Cancellati
 259        {
 0260            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, 
 261        }
 262
 263        /// <inheritdoc/>
 264        public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool
 265        {
 0266            if (string.IsNullOrWhiteSpace(source))
 267            {
 0268                throw new ArgumentNullException(nameof(source));
 269            }
 270
 271            try
 272            {
 0273                var fileStream = AsyncFile.OpenRead(source);
 0274                await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0275                    .SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken)
 0276                    .ConfigureAwait(false);
 0277            }
 278            finally
 279            {
 280                try
 281                {
 0282                    File.Delete(source);
 0283                }
 0284                catch (Exception ex)
 285                {
 0286                    _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source);
 0287                }
 288            }
 0289        }
 290
 291        /// <inheritdoc/>
 292        public Task SaveImage(Stream source, string mimeType, string path)
 293        {
 0294            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0295                .SaveImage(source, path);
 296        }
 297
 298        /// <inheritdoc/>
 299        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, 
 300        {
 0301            var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
 302
 0303            if (!string.IsNullOrEmpty(query.ProviderName))
 304            {
 0305                var providerName = query.ProviderName;
 306
 0307                providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)
 308            }
 309
 0310            if (query.ImageType is not null)
 311            {
 0312                providers = providers.Where(i => i.GetSupportedImages(item).Contains(query.ImageType.Value));
 313            }
 314
 0315            var preferredLanguage = item.GetPreferredMetadataLanguage();
 316
 0317            var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellat
 318
 0319            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 320
 0321            return results.SelectMany(i => i);
 0322        }
 323
 324        /// <summary>
 325        /// Gets the images.
 326        /// </summary>
 327        /// <param name="item">The item.</param>
 328        /// <param name="provider">The provider.</param>
 329        /// <param name="preferredLanguage">The preferred language.</param>
 330        /// <param name="includeAllLanguages">Whether to include all languages in results.</param>
 331        /// <param name="cancellationToken">The cancellation token.</param>
 332        /// <param name="type">The type.</param>
 333        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
 334        private async Task<IEnumerable<RemoteImageInfo>> GetImages(
 335            BaseItem item,
 336            IRemoteImageProvider provider,
 337            string preferredLanguage,
 338            bool includeAllLanguages,
 339            CancellationToken cancellationToken,
 340            ImageType? type = null)
 341        {
 0342            bool hasPreferredLanguage = !string.IsNullOrWhiteSpace(preferredLanguage);
 343
 344            try
 345            {
 0346                var result = await provider.GetImages(item, cancellationToken).ConfigureAwait(false);
 347
 0348                if (type.HasValue)
 349                {
 0350                    result = result.Where(i => i.Type == type.Value);
 351                }
 352
 0353                if (!includeAllLanguages && hasPreferredLanguage)
 354                {
 355                    // Filter out languages that do not match the preferred languages.
 356                    //
 357                    // TODO: should exception case of "en" (English) eventually be removed?
 0358                    result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
 0359                                               string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgno
 0360                                               string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
 361                }
 362
 0363                return result.OrderByLanguageDescending(preferredLanguage);
 364            }
 0365            catch (OperationCanceledException)
 366            {
 0367                return Enumerable.Empty<RemoteImageInfo>();
 368            }
 0369            catch (Exception ex)
 370            {
 0371                _logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provide
 0372                return Enumerable.Empty<RemoteImageInfo>();
 373            }
 0374        }
 375
 376        /// <inheritdoc/>
 377        public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
 378        {
 0379            return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(it
 380        }
 381
 382        private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled)
 383        {
 0384            var options = GetMetadataOptions(item);
 0385            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 386
 0387            return GetImageProvidersInternal(
 0388                item,
 0389                libraryOptions,
 0390                options,
 0391                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0392                includeDisabled).OfType<IRemoteImageProvider>();
 393        }
 394
 395        /// <inheritdoc/>
 396        public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
 397        {
 85398            return GetImageProvidersInternal(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), re
 399        }
 400
 401        private IEnumerable<IImageProvider> GetImageProvidersInternal(BaseItem item, LibraryOptions libraryOptions, Meta
 402        {
 85403            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 85404            var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder;
 405
 85406            return _imageProviders.Where(i => CanRefreshImages(i, item, typeOptions, refreshOptions, includeDisabled))
 85407                .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name))
 85408                .ThenBy(GetDefaultOrder);
 409        }
 410
 411        private bool CanRefreshImages(
 412            IImageProvider provider,
 413            BaseItem item,
 414            TypeOptions? libraryTypeOptions,
 415            ImageRefreshOptions refreshOptions,
 416            bool includeDisabled)
 417        {
 418            try
 419            {
 1268420                if (!provider.Supports(item))
 421                {
 1099422                    return false;
 423                }
 168424            }
 1425            catch (Exception ex)
 426            {
 1427                _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.Get
 1428                return false;
 429            }
 430
 168431            if (includeDisabled || provider is ILocalImageProvider)
 432            {
 161433                return true;
 434            }
 435
 7436            if (item.IsLocked && refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh)
 437            {
 1438                return false;
 439            }
 440
 441            // Extras have no identity of their own in an online database, so remote artwork for them
 442            // is always some other item's. Local and dynamic providers still apply, so an extra can
 443            // keep an embedded thumbnail or an extracted frame.
 6444            if (item.ExtraType.HasValue && provider is IRemoteImageProvider)
 445            {
 0446                return false;
 447            }
 448
 6449            return _baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name);
 1100450        }
 451
 452        /// <inheritdoc />
 453        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
 454            where T : BaseItem
 455        {
 101456            var globalMetadataOptions = GetMetadataOptions(item);
 101457            var libraryPath = GetLibraryPathForItem(item);
 458
 101459            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false, libraryPat
 460        }
 461
 462        /// <summary>
 463        /// Gets metadata providers for the specified item.
 464        /// </summary>
 465        /// <typeparam name="T">The item type.</typeparam>
 466        /// <param name="item">The item.</param>
 467        /// <param name="libraryOptions">The library options.</param>
 468        /// <param name="includeDisabled">Whether to include disabled providers.</param>
 469        /// <returns>The metadata providers.</returns>
 470        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions, b
 471            where T : BaseItem
 472        {
 0473            var globalMetadataOptions = GetMetadataOptions(item);
 0474            var libraryPath = GetLibraryPathForItem(item);
 475
 0476            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, includeDisabled, false, 
 477        }
 478
 479        private static string GetLibraryPathForItem(BaseItem item)
 480        {
 101481            if (item is CollectionFolder collectionFolder)
 482            {
 17483                return collectionFolder.Path ?? string.Empty;
 484            }
 485
 84486            var topParent = item.GetTopParent();
 84487            return topParent?.Path ?? string.Empty;
 488        }
 489
 490        /// <inheritdoc />
 491        public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
 492        {
 36493            return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false)
 494        }
 495
 496        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryO
 497            where T : BaseItem
 498        {
 101499            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 500
 101501            var orderedProviders = GetOrCreateOrderedProviders<T>(item.GetType().Name, libraryOptions, globalMetadataOpt
 502
 101503            return orderedProviders.Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInte
 504        }
 505
 506        private IMetadataProvider<T>[] GetOrCreateOrderedProviders<T>(
 507            string itemTypeName,
 508            LibraryOptions libraryOptions,
 509            MetadataOptions globalMetadataOptions,
 510            bool includeDisabled,
 511            bool forceEnableInternetMetadata,
 512            string libraryPath)
 513            where T : BaseItem
 514        {
 101515            var cacheKey = new MetadataProviderCacheKey(libraryPath, itemTypeName, includeDisabled, forceEnableInternetM
 101516            if (_metadataProviderCache.TryGetValue(cacheKey, out var cachedProviders))
 517            {
 25518                return cachedProviders.OfType<IMetadataProvider<T>>().ToArray();
 519            }
 520
 76521            var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadat
 76522            var typeOptions = libraryOptions.GetTypeOptions(itemTypeName);
 76523            var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
 524
 76525            var orderedProviders = _metadataProviders.OfType<IMetadataProvider<T>>()
 76526                .Where(i => CanRefreshMetadataForCache(i, typeOptions, includeDisabled, forceEnableInternetMetadata))
 76527                .OrderBy(i =>
 76528                    // local and remote providers will be interleaved in the final order
 76529                    // only relative order within a type matters: consumers of the list filter to one or the other
 76530                    i switch
 76531                    {
 76532                        ILocalMetadataProvider => GetConfiguredOrder(localMetadataReaderOrder, i.Name),
 76533                        IRemoteMetadataProvider => GetConfiguredOrder(metadataFetcherOrder, i.Name),
 76534                        // Default to end
 76535                        _ => int.MaxValue
 76536                    })
 76537                .ThenBy(GetDefaultOrder)
 76538                .ToArray();
 539
 76540            _metadataProviderCache.TryAdd(cacheKey, orderedProviders.Cast<IMetadataProvider>().ToArray());
 541
 76542            return orderedProviders;
 543        }
 544
 545        private static bool CanRefreshMetadataForCache(
 546            IMetadataProvider provider,
 547            TypeOptions? libraryTypeOptions,
 548            bool includeDisabled,
 549            bool forceEnableInternetMetadata)
 550        {
 143551            if (includeDisabled)
 552            {
 0553                return true;
 554            }
 555
 143556            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 557            {
 95558                return true;
 559            }
 560
 48561            if (libraryTypeOptions?.MetadataFetchers is { Length: > 0 } metadataFetchers)
 562            {
 0563                return metadataFetchers.Contains(provider.Name, StringComparer.OrdinalIgnoreCase);
 564            }
 565
 48566            return true;
 567        }
 568
 569        private bool CanRefreshMetadata(
 570            IMetadataProvider provider,
 571            BaseItem item,
 572            TypeOptions? libraryTypeOptions,
 573            bool includeDisabled,
 574            bool forceEnableInternetMetadata)
 575        {
 168576            if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider)
 577            {
 1578                return false;
 579            }
 580
 167581            if (includeDisabled)
 582            {
 0583                return true;
 584            }
 585
 586            // If locked only allow local providers
 167587            if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider)
 588            {
 3589                return false;
 590            }
 591
 164592            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 593            {
 117594                return true;
 595            }
 596
 597            // An extra is a local file belonging to another item and has no identity of its own in an
 598            // online database. Looking it up matches whatever the surrounding folder happens to be
 599            // called and overwrites the extra's name with a different item's title.
 47600            if (item.ExtraType.HasValue)
 601            {
 0602                return false;
 603            }
 604
 605            // Artists without a folder structure that are derived from metadata have no real path in the library,
 606            // so GetLibraryOptions returns null. Allow all providers through rather than blocking them.
 47607            if (item is MusicArtist && libraryTypeOptions is null)
 608            {
 0609                return true;
 610            }
 611
 47612            return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
 613        }
 614
 615        private static int GetConfiguredOrder(string[] order, string providerName)
 616        {
 245617            var index = Array.IndexOf(order, providerName);
 618
 245619            if (index != -1)
 620            {
 54621                return index;
 622            }
 623
 624            // default to end
 191625            return int.MaxValue;
 626        }
 627
 628        private static int GetDefaultOrder(object provider)
 629        {
 245630            if (provider is IHasOrder hasOrder)
 631            {
 147632                return hasOrder.Order;
 633            }
 634
 635            // after items that want to be first (~0) but before items that want to be last (~100)
 98636            return 50;
 637        }
 638
 639        /// <inheritdoc/>
 640        public MetadataPluginSummary[] GetAllMetadataPlugins()
 641        {
 0642            return new[]
 0643            {
 0644                GetPluginSummary<Movie>(),
 0645                GetPluginSummary<BoxSet>(),
 0646                GetPluginSummary<Book>(),
 0647                GetPluginSummary<Series>(),
 0648                GetPluginSummary<Season>(),
 0649                GetPluginSummary<Episode>(),
 0650                GetPluginSummary<MusicAlbum>(),
 0651                GetPluginSummary<MusicArtist>(),
 0652                GetPluginSummary<Audio>(),
 0653                GetPluginSummary<AudioBook>(),
 0654                GetPluginSummary<Studio>(),
 0655                GetPluginSummary<MusicVideo>(),
 0656                GetPluginSummary<Video>()
 0657            };
 658        }
 659
 660        private MetadataPluginSummary GetPluginSummary<T>()
 661            where T : BaseItem, new()
 662        {
 663            // Give it a dummy path just so that it looks like a file system item
 0664            var dummy = new T
 0665            {
 0666                Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0667                ParentId = Guid.NewGuid()
 0668            };
 669
 0670            var options = GetMetadataOptions(dummy);
 671
 0672            var summary = new MetadataPluginSummary
 0673            {
 0674                ItemType = typeof(T).Name
 0675            };
 676
 0677            var libraryOptions = new LibraryOptions();
 678
 0679            var imageProviders = GetImageProvidersInternal(
 0680                dummy,
 0681                libraryOptions,
 0682                options,
 0683                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0684                true).ToList();
 685
 0686            var pluginList = summary.Plugins.ToList();
 687
 0688            AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
 0689            AddImagePlugins(pluginList, imageProviders);
 690
 691            // Subtitle fetchers
 0692            var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
 0693            pluginList.AddRange(subtitleProviders.Select(i => new MetadataPlugin
 0694            {
 0695                Name = i.Name,
 0696                Type = MetadataPluginType.SubtitleFetcher
 0697            }));
 698
 699            // Lyric fetchers
 0700            var lyricProviders = _lyricManager.GetSupportedProviders(dummy);
 0701            pluginList.AddRange(lyricProviders.Select(i => new MetadataPlugin
 0702            {
 0703                Name = i.Name,
 0704                Type = MetadataPluginType.LyricFetcher
 0705            }));
 706
 707            // Media segment providers
 0708            var mediaSegmentProviders = _mediaSegmentManager.GetSupportedProviders(dummy);
 0709            pluginList.AddRange(mediaSegmentProviders.Select(i => new MetadataPlugin
 0710            {
 0711                Name = i.Name,
 0712                Type = MetadataPluginType.MediaSegmentProvider
 0713            }));
 714
 715            // Similar items providers
 0716            var similarItemsProviders = _similarItemsManager.GetSimilarItemsProviders<T>();
 0717            pluginList.AddRange(similarItemsProviders.Select(i => new MetadataPlugin
 0718            {
 0719                Name = i.Name,
 0720                Type = i.Type
 0721            }));
 722
 0723            summary.Plugins = pluginList.ToArray();
 724
 0725            var supportedImageTypes = imageProviders.OfType<IRemoteImageProvider>()
 0726                .SelectMany(i => i.GetSupportedImages(dummy))
 0727                .ToList();
 728
 0729            supportedImageTypes.AddRange(imageProviders.OfType<IDynamicImageProvider>()
 0730                .SelectMany(i => i.GetSupportedImages(dummy)));
 731
 0732            summary.SupportedImageTypes = supportedImageTypes.Distinct().ToArray();
 733
 0734            return summary;
 735        }
 736
 737        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, LibraryOptions libraryOptions, MetadataOpt
 738            where T : BaseItem
 739        {
 0740            var libraryPath = GetLibraryPathForItem(item);
 0741            var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true, libraryPath).ToLi
 742
 743            // Locals
 0744            list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin
 0745            {
 0746                Name = i.Name,
 0747                Type = MetadataPluginType.LocalMetadataProvider
 0748            }));
 749
 750            // Fetchers
 0751            list.AddRange(providers.Where(i => i is IRemoteMetadataProvider).Select(i => new MetadataPlugin
 0752            {
 0753                Name = i.Name,
 0754                Type = MetadataPluginType.MetadataFetcher
 0755            }));
 756
 757            // Savers
 0758            list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit,
 0759            {
 0760                Name = i.Name,
 0761                Type = MetadataPluginType.MetadataSaver
 0762            }));
 0763        }
 764
 765        private void AddImagePlugins(List<MetadataPlugin> list, List<IImageProvider> imageProviders)
 766        {
 767            // Locals
 0768            list.AddRange(imageProviders.Where(i => i is ILocalImageProvider).Select(i => new MetadataPlugin
 0769            {
 0770                Name = i.Name,
 0771                Type = MetadataPluginType.LocalImageProvider
 0772            }));
 773
 774            // Fetchers
 0775            list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (i is IRemoteImageProvider)).Select(i 
 0776            {
 0777                Name = i.Name,
 0778                Type = MetadataPluginType.ImageFetcher
 0779            }));
 0780        }
 781
 782        /// <inheritdoc/>
 783        public MetadataOptions GetMetadataOptions(BaseItem item)
 1394784            => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
 785
 786        /// <inheritdoc/>
 787        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
 115788            => SaveMetadataAsync(item, updateType, _savers);
 789
 790        /// <inheritdoc/>
 791        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
 0792            => SaveMetadataAsync(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIg
 793
 794        /// <summary>
 795        /// Saves the metadata.
 796        /// </summary>
 797        /// <param name="item">The item.</param>
 798        /// <param name="updateType">Type of the update.</param>
 799        /// <param name="savers">The savers.</param>
 800        private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> saver
 801        {
 115802            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 115803            var applicableSavers = savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)).
 115804            if (applicableSavers.Count == 0)
 805            {
 115806                return;
 807            }
 808
 0809            foreach (var saver in applicableSavers)
 810            {
 0811                _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
 812
 0813                if (saver is IMetadataFileSaver fileSaver)
 814                {
 815                    string path;
 816
 817                    try
 818                    {
 0819                        path = fileSaver.GetSavePath(item);
 0820                    }
 0821                    catch (Exception ex)
 822                    {
 0823                        _logger.LogError(ex, "Error in {Saver} GetSavePath", saver.Name);
 0824                        continue;
 825                    }
 826
 827                    try
 828                    {
 0829                        _libraryMonitor.ReportFileSystemChangeBeginning(path);
 0830                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 0831                        item.DateLastSaved = DateTime.UtcNow;
 0832                    }
 0833                    catch (Exception ex)
 834                    {
 0835                        _logger.LogError(ex, "Error in metadata saver");
 0836                    }
 837                    finally
 838                    {
 0839                        _libraryMonitor.ReportFileSystemChangeComplete(path, false);
 840                    }
 0841                }
 842                else
 843                {
 844                    try
 845                    {
 0846                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 0847                        item.DateLastSaved = DateTime.UtcNow;
 0848                    }
 0849                    catch (Exception ex)
 850                    {
 0851                        _logger.LogError(ex, "Error in metadata saver");
 0852                    }
 853                }
 854            }
 115855        }
 856
 857        /// <summary>
 858        /// Determines whether [is saver enabled for item] [the specified saver].
 859        /// </summary>
 860        private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdat
 861        {
 1208862            var options = GetMetadataOptions(item);
 863
 864            try
 865            {
 1208866                if (!saver.IsEnabledFor(item, updateType))
 867                {
 1208868                    return false;
 869                }
 870
 0871                if (!includeDisabled)
 872                {
 0873                    if (libraryOptions.MetadataSavers is null)
 874                    {
 0875                        if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 876                        {
 0877                            return false;
 878                        }
 879
 0880                        if (!item.IsSaveLocalMetadataEnabled())
 881                        {
 0882                            if (updateType >= ItemUpdateType.MetadataEdit)
 883                            {
 884                                // Manual edit occurred
 885                                // Even if save local is off, save locally anyway if the metadata file already exists
 0886                                if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item
 887                                {
 0888                                    return false;
 889                                }
 890                            }
 891                            else
 892                            {
 893                                // Manual edit did not occur
 894                                // Since local metadata saving is disabled, consider it disabled
 0895                                return false;
 896                            }
 897                        }
 898                    }
 899                    else
 900                    {
 0901                        if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 902                        {
 0903                            return false;
 904                        }
 905                    }
 906                }
 907
 0908                return true;
 909            }
 0910            catch (Exception ex)
 911            {
 0912                _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name);
 0913                return false;
 914            }
 1208915        }
 916
 917        /// <inheritdoc/>
 918        public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TL
 919            where TItemType : BaseItem, new()
 920            where TLookupType : ItemLookupInfo
 921        {
 0922            BaseItem? referenceItem = null;
 923
 0924            if (!searchInfo.ItemId.IsEmpty())
 925            {
 0926                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
 927            }
 928
 0929            return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
 930        }
 931
 932        private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQ
 933            where TItemType : BaseItem, new()
 934            where TLookupType : ItemLookupInfo
 935        {
 936            LibraryOptions libraryOptions;
 937
 0938            if (referenceItem is null)
 939            {
 940                // Give it a dummy path just so that it looks like a file system item
 0941                var dummy = new TItemType
 0942                {
 0943                    Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0944                    ParentId = Guid.NewGuid()
 0945                };
 946
 0947                dummy.SetParent(new Folder());
 948
 0949                referenceItem = dummy;
 0950                libraryOptions = new LibraryOptions();
 951            }
 952            else
 953            {
 0954                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
 955            }
 956
 0957            var options = GetMetadataOptions(referenceItem);
 0958            var libraryPath = GetLibraryPathForItem(referenceItem);
 0959            var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.I
 0960                .OfType<IRemoteSearchProvider<TLookupType>>();
 961
 0962            if (!string.IsNullOrEmpty(searchInfo.SearchProviderName))
 963            {
 0964                providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.O
 965            }
 966
 0967            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
 968            {
 0969                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
 970            }
 971
 0972            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
 973            {
 0974                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
 975            }
 976
 0977            var resultList = new List<RemoteSearchResult>();
 978
 0979            foreach (var provider in providers)
 980            {
 981                try
 982                {
 0983                    var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwa
 984
 0985                    foreach (var result in results)
 986                    {
 0987                        result.SearchProviderName = provider.Name;
 988
 0989                        var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.G
 990
 0991                        if (existingMatch is null)
 992                        {
 0993                            resultList.Add(result);
 994                        }
 995                        else
 996                        {
 0997                            foreach (var providerId in result.ProviderIds)
 998                            {
 0999                                existingMatch.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 1000                            }
 1001
 01002                            if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
 1003                            {
 01004                                existingMatch.ImageUrl = result.ImageUrl;
 1005                            }
 1006                        }
 1007                    }
 01008                }
 1009#pragma warning disable CA1031 // do not catch general exception types
 01010                catch (Exception ex)
 1011#pragma warning restore CA1031 // do not catch general exception types
 1012                {
 01013                    _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
 01014                }
 01015            }
 1016
 01017            return resultList;
 01018        }
 1019
 1020        private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
 1021        {
 01022            return _externalIds.Where(i =>
 01023            {
 01024                try
 01025                {
 01026                    return i.Supports(item);
 01027                }
 01028                catch (Exception ex)
 01029                {
 01030                    _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name);
 01031                    return false;
 01032                }
 01033            });
 1034        }
 1035
 1036        /// <inheritdoc/>
 1037        public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
 1038        {
 61039            return _externalUrlProviders
 61040                .SelectMany(p => p
 61041                    .GetExternalUrls(item)
 61042                    .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
 1043        }
 1044
 1045        /// <inheritdoc/>
 1046        public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
 1047        {
 01048            return GetExternalIds(item)
 01049                .Select(i => new ExternalIdInfo(
 01050                    name: i.ProviderName,
 01051                    key: i.Key,
 01052                    type: i.Type));
 1053        }
 1054
 1055        /// <inheritdoc/>
 1056        public HashSet<Guid> GetRefreshQueue()
 11057        {
 1058            lock (_refreshQueueLock)
 1059            {
 11060                return _refreshQueue.UnorderedItems.Select(x => x.Element.ItemId).ToHashSet();
 1061            }
 11062        }
 1063
 1064        /// <inheritdoc/>
 1065        public void OnRefreshStart(BaseItem item)
 1066        {
 181067            _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
 181068            _activeRefreshes[item.Id] = 0;
 1069            try
 1070            {
 181071                RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 181072            }
 01073            catch (Exception ex)
 1074            {
 1075                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01076                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
 01077            }
 181078        }
 1079
 1080        /// <inheritdoc/>
 1081        public void OnRefreshComplete(BaseItem item)
 1082        {
 181083            _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
 181084            _activeRefreshes.TryRemove(item.Id, out _);
 1085
 1086            try
 1087            {
 181088                RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 181089            }
 01090            catch (Exception ex)
 1091            {
 1092                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01093                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
 01094            }
 181095        }
 1096
 1097        /// <inheritdoc/>
 1098        public double? GetRefreshProgress(Guid id)
 1099        {
 01100            if (_activeRefreshes.TryGetValue(id, out double value))
 1101            {
 01102                return value;
 1103            }
 1104
 01105            return null;
 1106        }
 1107
 1108        /// <inheritdoc/>
 1109        public void OnRefreshProgress(BaseItem item, double progress)
 1110        {
 901111            var id = item.Id;
 901112            _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
 1113
 901114            if (!_activeRefreshes.TryGetValue(id, out var current)
 901115                || progress <= current
 901116                || !_activeRefreshes.TryUpdate(id, progress, current))
 1117            {
 1118                // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
 341119                return;
 1120            }
 1121
 1122            try
 1123            {
 561124                RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(
 561125            }
 01126            catch (Exception ex)
 1127            {
 1128                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01129                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
 01130            }
 561131        }
 1132
 1133        /// <inheritdoc/>
 1134        public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
 1135        {
 01136            if (itemId.IsEmpty())
 1137            {
 01138                throw new ArgumentException("Guid can't be empty", nameof(itemId));
 1139            }
 1140
 01141            if (_disposed)
 1142            {
 01143                return;
 1144            }
 1145
 01146            _refreshQueue.Enqueue((itemId, options), priority);
 1147
 1148            lock (_refreshQueueLock)
 1149            {
 01150                if (!_isProcessingRefreshQueue)
 1151                {
 01152                    _isProcessingRefreshQueue = true;
 01153                    Task.Run(StartProcessingRefreshQueue);
 1154                }
 01155            }
 01156        }
 1157
 1158        private async Task StartProcessingRefreshQueue()
 1159        {
 01160            var libraryManager = _libraryManager;
 1161
 01162            if (_disposed)
 1163            {
 01164                return;
 1165            }
 1166
 01167            var cancellationToken = _disposeCancellationTokenSource.Token;
 1168
 01169            libraryManager.ClearIgnoreRuleCache();
 01170            while (_refreshQueue.TryDequeue(out var refreshItem, out _))
 1171            {
 01172                if (_disposed)
 1173                {
 01174                    return;
 1175                }
 1176
 1177                try
 1178                {
 01179                    var item = libraryManager.GetItemById(refreshItem.ItemId);
 01180                    if (item is null)
 1181                    {
 01182                        continue;
 1183                    }
 1184
 01185                    var task = item is MusicArtist artist
 01186                        ? RefreshArtist(artist, refreshItem.RefreshOptions, cancellationToken)
 01187                        : RefreshItem(item, refreshItem.RefreshOptions, cancellationToken);
 1188
 01189                    await task.ConfigureAwait(false);
 01190                }
 01191                catch (OperationCanceledException)
 1192                {
 01193                    break;
 1194                }
 01195                catch (Exception ex)
 1196                {
 01197                    _logger.LogError(ex, "Error refreshing item");
 01198                }
 1199            }
 1200
 1201            lock (_refreshQueueLock)
 1202            {
 01203                _isProcessingRefreshQueue = false;
 01204                libraryManager.ClearIgnoreRuleCache();
 01205            }
 01206        }
 1207
 1208        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToke
 1209        {
 01210            await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1211
 1212            // Collection folders don't validate their children so we'll have to simulate that here
 1213            switch (item)
 1214            {
 1215                case CollectionFolder collectionFolder:
 01216                    await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(f
 01217                    break;
 1218                case Folder folder:
 01219                    await folder.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken)
 1220                    break;
 1221            }
 01222        }
 1223
 1224        private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFo
 1225        {
 01226            foreach (var child in collectionFolder.GetPhysicalFolders())
 1227            {
 01228                await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1229
 01230                await child.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken).Conf
 01231            }
 01232        }
 1233
 1234        private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellatio
 1235        {
 01236            var albums = _libraryManager
 01237                .GetItemList(new InternalItemsQuery
 01238                {
 01239                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 01240                    ArtistIds = new[] { item.Id },
 01241                    DtoOptions = new DtoOptions(false)
 01242                    {
 01243                        EnableImages = false
 01244                    }
 01245                })
 01246                .OfType<MusicAlbum>();
 1247
 01248            var musicArtists = albums
 01249                .Select(i => i.MusicArtist)
 01250                .Where(i => i is not null)
 01251                .Distinct();
 1252
 01253            var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, t
 1254
 01255            await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
 1256
 1257            try
 1258            {
 01259                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 01260            }
 01261            catch (Exception ex)
 1262            {
 01263                _logger.LogError(ex, "Error refreshing library");
 01264            }
 01265        }
 1266
 1267        /// <inheritdoc/>
 1268        public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 1269        {
 01270            return RefreshItem(item, options, cancellationToken);
 1271        }
 1272
 1273        /// <inheritdoc/>
 1274        public void Dispose()
 1275        {
 921276            Dispose(true);
 921277            GC.SuppressFinalize(this);
 921278        }
 1279
 1280        /// <summary>
 1281        /// Releases unmanaged and optionally managed resources.
 1282        /// </summary>
 1283        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 1284        protected virtual void Dispose(bool disposing)
 1285        {
 921286            if (_disposed)
 1287            {
 01288                return;
 1289            }
 1290
 921291            if (disposing)
 1292            {
 921293                CollectionFolder.LibraryOptionsUpdated -= OnLibraryOptionsUpdated;
 1294
 921295                if (!_disposeCancellationTokenSource.IsCancellationRequested)
 1296                {
 921297                    _disposeCancellationTokenSource.Cancel();
 1298                }
 1299
 921300                _disposeCancellationTokenSource.Dispose();
 921301                _imageSaveLock.Dispose();
 1302            }
 1303
 921304            _disposed = true;
 921305        }
 1306
 1307        private void OnLibraryOptionsUpdated(object? sender, LibraryOptionsUpdatedEventArgs e)
 1308        {
 31309            var keysToRemove = _metadataProviderCache.Keys
 31310                .Where(k => string.Equals(k.LibraryPath, e.LibraryPath, StringComparison.Ordinal))
 31311                .ToList();
 1312
 81313            foreach (var key in keysToRemove)
 1314            {
 11315                _metadataProviderCache.TryRemove(key, out _);
 1316            }
 1317
 31318            _logger.LogDebug("Invalidated metadata provider cache for library: {LibraryPath}", e.LibraryPath);
 31319        }
 1320
 1321        internal void ClearMetadataProviderCache()
 1322        {
 921323            _metadataProviderCache.Clear();
 921324            _logger.LogDebug("Cleared entire metadata provider cache");
 921325        }
 1326
 1327        /// <summary>
 1328        /// Cache key for metadata provider lookups.
 1329        /// </summary>
 1330        /// <param name="LibraryPath">The library path for the collection folder.</param>
 1331        /// <param name="ItemTypeName">The item type name.</param>
 1332        /// <param name="IncludeDisabled">Whether to include disabled providers.</param>
 1333        /// <param name="ForceEnableInternetMetadata">Whether internet metadata is force-enabled.</param>
 1334        private readonly record struct MetadataProviderCacheKey(
 1335            string LibraryPath,
 1336            string ItemTypeName,
 1337            bool IncludeDisabled,
 1338            bool ForceEnableInternetMetadata);
 1339    }
 1340}

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()