< 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: 172
Uncovered lines: 371
Coverable lines: 543
Total lines: 1307
Line coverage: 31.6%
Branch coverage
41%
Covered branches: 87
Total branches: 210
Branch coverage: 41.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11721/25/2026 - 12:13:25 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11721/29/2026 - 12:13:32 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11721/31/2026 - 12:11:27 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/2/2026 - 12:13:58 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/4/2026 - 12:13:52 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/5/2026 - 12:13:49 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/6/2026 - 12:13:21 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11722/7/2026 - 12:13:45 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/8/2026 - 12:14:11 AM Line coverage: 45.5% (137/301) Branch coverage: 56.2% (63/112) Total lines: 11722/9/2026 - 12:11:32 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11722/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: 1307 1/23/2026 - 12:11:06 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11721/25/2026 - 12:13:25 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11721/29/2026 - 12:13:32 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11721/31/2026 - 12:11:27 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/2/2026 - 12:13:58 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/4/2026 - 12:13:52 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/5/2026 - 12:13:49 AM Line coverage: 45.5% (137/301) Branch coverage: 58% (65/112) Total lines: 11722/6/2026 - 12:13:21 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11722/7/2026 - 12:13:45 AM Line coverage: 45.5% (137/301) Branch coverage: 57.1% (64/112) Total lines: 11722/8/2026 - 12:14:11 AM Line coverage: 45.5% (137/301) Branch coverage: 56.2% (63/112) Total lines: 11722/9/2026 - 12:11:32 AM Line coverage: 45.5% (137/301) Branch coverage: 55.3% (62/112) Total lines: 11722/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: 1307

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(...)100%1010100%
GetMetadataProviders(...)100%11100%
GetMetadataProviders(...)100%210%
GetLibraryPathForItem(...)75%88100%
GetMetadataSavers(...)100%11100%
GetMetadataProvidersInternal(...)100%11100%
GetOrCreateOrderedProviders(...)90%1010100%
CanRefreshMetadataForCache(...)83.33%151271.42%
CanRefreshMetadata(...)85%222081.81%
GetConfiguredOrder(...)100%22100%
GetDefaultOrder(...)100%22100%
GetAllMetadataPlugins()100%210%
GetPluginSummary()0%4260%
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(...)100%2262.5%
GetRefreshProgress(...)0%620%
OnRefreshProgress(...)100%9875%
QueueRefresh(...)0%4260%
StartProcessingRefreshQueue()0%110100%
RefreshItem()0%2040%
RefreshCollectionFolderChildren()0%620%
RefreshArtist()100%210%
RefreshFullItem(...)100%210%
Dispose()100%11100%
Dispose(...)83.33%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.IO;
 5using System.Linq;
 6using System.Net;
 7using System.Net.Http;
 8using System.Net.Mime;
 9using System.Threading;
 10using System.Threading.Tasks;
 11using AsyncKeyedLock;
 12using Jellyfin.Data.Enums;
 13using Jellyfin.Data.Events;
 14using Jellyfin.Extensions;
 15using MediaBrowser.Common.Net;
 16using MediaBrowser.Controller;
 17using MediaBrowser.Controller.BaseItemManager;
 18using MediaBrowser.Controller.Configuration;
 19using MediaBrowser.Controller.Dto;
 20using MediaBrowser.Controller.Entities;
 21using MediaBrowser.Controller.Entities.Audio;
 22using MediaBrowser.Controller.Entities.Movies;
 23using MediaBrowser.Controller.Library;
 24using MediaBrowser.Controller.Lyrics;
 25using MediaBrowser.Controller.MediaSegments;
 26using MediaBrowser.Controller.Providers;
 27using MediaBrowser.Controller.Subtitles;
 28using MediaBrowser.Model.Configuration;
 29using MediaBrowser.Model.Entities;
 30using MediaBrowser.Model.Extensions;
 31using MediaBrowser.Model.IO;
 32using MediaBrowser.Model.Net;
 33using MediaBrowser.Model.Providers;
 34using MediaBrowser.Model.Querying;
 35using Microsoft.Extensions.Caching.Memory;
 36using Microsoft.Extensions.Logging;
 37using Book = MediaBrowser.Controller.Entities.Book;
 38using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 39using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
 40using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
 41using Season = MediaBrowser.Controller.Entities.TV.Season;
 42using Series = MediaBrowser.Controller.Entities.TV.Series;
 43
 44namespace MediaBrowser.Providers.Manager
 45{
 46    /// <summary>
 47    /// Class ProviderManager.
 48    /// </summary>
 49    public class ProviderManager : IProviderManager, IDisposable
 50    {
 9151        private readonly Lock _refreshQueueLock = new();
 52        private readonly ILogger<ProviderManager> _logger;
 53        private readonly IHttpClientFactory _httpClientFactory;
 54        private readonly ILibraryMonitor _libraryMonitor;
 55        private readonly IFileSystem _fileSystem;
 56        private readonly IServerApplicationPaths _appPaths;
 57        private readonly ILibraryManager _libraryManager;
 58        private readonly ISubtitleManager _subtitleManager;
 59        private readonly ILyricManager _lyricManager;
 60        private readonly IServerConfigurationManager _configurationManager;
 61        private readonly IBaseItemManager _baseItemManager;
 9162        private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new();
 9163        private readonly CancellationTokenSource _disposeCancellationTokenSource = new();
 9164        private readonly PriorityQueue<(Guid ItemId, MetadataRefreshOptions RefreshOptions), RefreshPriority> _refreshQu
 65        private readonly IMemoryCache _memoryCache;
 66        private readonly IMediaSegmentManager _mediaSegmentManager;
 9167        private readonly AsyncKeyedLocker<string> _imageSaveLock = new(o =>
 9168        {
 9169            o.PoolSize = 20;
 9170            o.PoolInitialFill = 1;
 9171        });
 72
 73        /// <summary>
 74        /// Cache for ordered metadata providers per library/item type combination.
 75        /// Key: (LibraryPath, ItemTypeName, IncludeDisabled, ForceEnableInternetMetadata).
 76        /// Value: Array of ordered metadata providers (before per-item filtering).
 77        /// </summary>
 9178        private readonly ConcurrentDictionary<MetadataProviderCacheKey, IMetadataProvider[]> _metadataProviderCache = ne
 79
 9180        private IImageProvider[] _imageProviders = [];
 9181        private IMetadataService[] _metadataServices = [];
 9182        private IMetadataProvider[] _metadataProviders = [];
 9183        private IMetadataSaver[] _savers = [];
 9184        private IExternalId[] _externalIds = [];
 9185        private IExternalUrlProvider[] _externalUrlProviders = [];
 86        private bool _isProcessingRefreshQueue;
 87        private bool _disposed;
 88
 89        /// <summary>
 90        /// Initializes a new instance of the <see cref="ProviderManager"/> class.
 91        /// </summary>
 92        /// <param name="httpClientFactory">The Http client factory.</param>
 93        /// <param name="subtitleManager">The subtitle manager.</param>
 94        /// <param name="configurationManager">The configuration manager.</param>
 95        /// <param name="libraryMonitor">The library monitor.</param>
 96        /// <param name="logger">The logger.</param>
 97        /// <param name="fileSystem">The filesystem.</param>
 98        /// <param name="appPaths">The server application paths.</param>
 99        /// <param name="libraryManager">The library manager.</param>
 100        /// <param name="baseItemManager">The BaseItem manager.</param>
 101        /// <param name="lyricManager">The lyric manager.</param>
 102        /// <param name="memoryCache">The memory cache.</param>
 103        /// <param name="mediaSegmentManager">The media segment manager.</param>
 104        public ProviderManager(
 105            IHttpClientFactory httpClientFactory,
 106            ISubtitleManager subtitleManager,
 107            IServerConfigurationManager configurationManager,
 108            ILibraryMonitor libraryMonitor,
 109            ILogger<ProviderManager> logger,
 110            IFileSystem fileSystem,
 111            IServerApplicationPaths appPaths,
 112            ILibraryManager libraryManager,
 113            IBaseItemManager baseItemManager,
 114            ILyricManager lyricManager,
 115            IMemoryCache memoryCache,
 116            IMediaSegmentManager mediaSegmentManager)
 117        {
 91118            _logger = logger;
 91119            _httpClientFactory = httpClientFactory;
 91120            _configurationManager = configurationManager;
 91121            _libraryMonitor = libraryMonitor;
 91122            _fileSystem = fileSystem;
 91123            _appPaths = appPaths;
 91124            _libraryManager = libraryManager;
 91125            _subtitleManager = subtitleManager;
 91126            _baseItemManager = baseItemManager;
 91127            _lyricManager = lyricManager;
 91128            _memoryCache = memoryCache;
 91129            _mediaSegmentManager = mediaSegmentManager;
 130
 91131            CollectionFolder.LibraryOptionsUpdated += OnLibraryOptionsUpdated;
 91132        }
 133
 134        /// <inheritdoc/>
 135        public event EventHandler<GenericEventArgs<BaseItem>>? RefreshStarted;
 136
 137        /// <inheritdoc/>
 138        public event EventHandler<GenericEventArgs<BaseItem>>? RefreshCompleted;
 139
 140        /// <inheritdoc/>
 141        public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>>? RefreshProgress;
 142
 143        /// <inheritdoc/>
 144        public void AddParts(
 145            IEnumerable<IImageProvider> imageProviders,
 146            IEnumerable<IMetadataService> metadataServices,
 147            IEnumerable<IMetadataProvider> metadataProviders,
 148            IEnumerable<IMetadataSaver> metadataSavers,
 149            IEnumerable<IExternalId> externalIds,
 150            IEnumerable<IExternalUrlProvider> externalUrlProviders)
 151        {
 91152            _imageProviders = imageProviders.ToArray();
 91153            _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
 91154            _metadataProviders = metadataProviders.ToArray();
 91155            _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
 91156            _externalUrlProviders = externalUrlProviders.OrderBy(i => i.Name).ToArray();
 157
 91158            _savers = metadataSavers.ToArray();
 91159        }
 160
 161        /// <inheritdoc/>
 162        public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken c
 163        {
 63164            var type = item.GetType();
 165
 63166            var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type))
 63167                ?? _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
 168
 63169            if (service is null)
 170            {
 1171                _logger.LogError("Unable to find a metadata service for item of type {TypeName}", type.Name);
 1172                return Task.FromResult(ItemUpdateType.None);
 173            }
 174
 62175            return service.RefreshMetadata(item, options, cancellationToken);
 176        }
 177
 178        /// <inheritdoc/>
 179        public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancel
 180        {
 0181            using (await _imageSaveLock.LockAsync(url, cancellationToken).ConfigureAwait(false))
 182            {
 0183                if (_memoryCache.TryGetValue(url, out (string ContentType, byte[] ImageContents)? cachedValue)
 0184                    && cachedValue is not null)
 185                {
 0186                    var imageContents = cachedValue.Value.ImageContents;
 0187                    var cacheStream = new MemoryStream(imageContents, 0, imageContents.Length, false);
 0188                    await using (cacheStream.ConfigureAwait(false))
 189                    {
 0190                        await SaveImage(
 0191                            item,
 0192                            cacheStream,
 0193                            cachedValue.Value.ContentType,
 0194                            type,
 0195                            imageIndex,
 0196                            cancellationToken).ConfigureAwait(false);
 0197                        return;
 198                    }
 199                }
 200
 0201                var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
 0202                using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
 203
 0204                response.EnsureSuccessStatusCode();
 205
 0206                var contentType = response.Content.Headers.ContentType?.MediaType;
 207
 208                // Workaround for tvheadend channel icons
 209                // TODO: Isolate this hack into the tvh plugin
 0210                if (string.IsNullOrEmpty(contentType))
 211                {
 212                    // Special case for imagecache
 0213                    if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase))
 214                    {
 0215                        contentType = MediaTypeNames.Image.Png;
 216                    }
 217                }
 218
 219                // some providers don't correctly report media type, extract from url if no extension found
 0220                if (contentType is null || contentType.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordinal
 221                {
 222                    // Strip query parameters from url to get actual path.
 0223                    contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
 224                }
 225
 0226                if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
 227                {
 0228                    throw new HttpRequestException($"Request returned '{contentType}' instead of an image type", null, H
 229                }
 230
 0231                var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false)
 0232                var stream = new MemoryStream(responseBytes, 0, responseBytes.Length, false);
 0233                await using (stream.ConfigureAwait(false))
 234                {
 0235                    _memoryCache.Set(url, (contentType, responseBytes), TimeSpan.FromSeconds(10));
 236
 0237                    await SaveImage(
 0238                        item,
 0239                        stream,
 0240                        contentType,
 0241                        type,
 0242                        imageIndex,
 0243                        cancellationToken).ConfigureAwait(false);
 244                }
 0245            }
 0246        }
 247
 248        /// <inheritdoc/>
 249        public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, Cancellati
 250        {
 0251            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, 
 252        }
 253
 254        /// <inheritdoc/>
 255        public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool
 256        {
 0257            if (string.IsNullOrWhiteSpace(source))
 258            {
 0259                throw new ArgumentNullException(nameof(source));
 260            }
 261
 262            try
 263            {
 0264                var fileStream = AsyncFile.OpenRead(source);
 0265                await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0266                    .SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken)
 0267                    .ConfigureAwait(false);
 0268            }
 269            finally
 270            {
 271                try
 272                {
 0273                    File.Delete(source);
 0274                }
 0275                catch (Exception ex)
 276                {
 0277                    _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source);
 0278                }
 279            }
 0280        }
 281
 282        /// <inheritdoc/>
 283        public Task SaveImage(Stream source, string mimeType, string path)
 284        {
 0285            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0286                .SaveImage(source, path);
 287        }
 288
 289        /// <inheritdoc/>
 290        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, 
 291        {
 0292            var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
 293
 0294            if (!string.IsNullOrEmpty(query.ProviderName))
 295            {
 0296                var providerName = query.ProviderName;
 297
 0298                providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)
 299            }
 300
 0301            if (query.ImageType is not null)
 302            {
 0303                providers = providers.Where(i => i.GetSupportedImages(item).Contains(query.ImageType.Value));
 304            }
 305
 0306            var preferredLanguage = item.GetPreferredMetadataLanguage();
 307
 0308            var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellat
 309
 0310            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 311
 0312            return results.SelectMany(i => i);
 0313        }
 314
 315        /// <summary>
 316        /// Gets the images.
 317        /// </summary>
 318        /// <param name="item">The item.</param>
 319        /// <param name="provider">The provider.</param>
 320        /// <param name="preferredLanguage">The preferred language.</param>
 321        /// <param name="includeAllLanguages">Whether to include all languages in results.</param>
 322        /// <param name="cancellationToken">The cancellation token.</param>
 323        /// <param name="type">The type.</param>
 324        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
 325        private async Task<IEnumerable<RemoteImageInfo>> GetImages(
 326            BaseItem item,
 327            IRemoteImageProvider provider,
 328            string preferredLanguage,
 329            bool includeAllLanguages,
 330            CancellationToken cancellationToken,
 331            ImageType? type = null)
 332        {
 0333            bool hasPreferredLanguage = !string.IsNullOrWhiteSpace(preferredLanguage);
 334
 335            try
 336            {
 0337                var result = await provider.GetImages(item, cancellationToken).ConfigureAwait(false);
 338
 0339                if (type.HasValue)
 340                {
 0341                    result = result.Where(i => i.Type == type.Value);
 342                }
 343
 0344                if (!includeAllLanguages && hasPreferredLanguage)
 345                {
 346                    // Filter out languages that do not match the preferred languages.
 347                    //
 348                    // TODO: should exception case of "en" (English) eventually be removed?
 0349                    result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
 0350                                               string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgno
 0351                                               string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
 352                }
 353
 0354                return result.OrderByLanguageDescending(preferredLanguage);
 355            }
 0356            catch (OperationCanceledException)
 357            {
 0358                return Enumerable.Empty<RemoteImageInfo>();
 359            }
 0360            catch (Exception ex)
 361            {
 0362                _logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provide
 0363                return Enumerable.Empty<RemoteImageInfo>();
 364            }
 0365        }
 366
 367        /// <inheritdoc/>
 368        public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
 369        {
 0370            return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(it
 371        }
 372
 373        private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled)
 374        {
 0375            var options = GetMetadataOptions(item);
 0376            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 377
 0378            return GetImageProvidersInternal(
 0379                item,
 0380                libraryOptions,
 0381                options,
 0382                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0383                includeDisabled).OfType<IRemoteImageProvider>();
 384        }
 385
 386        /// <inheritdoc/>
 387        public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
 388        {
 81389            return GetImageProvidersInternal(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), re
 390        }
 391
 392        private IEnumerable<IImageProvider> GetImageProvidersInternal(BaseItem item, LibraryOptions libraryOptions, Meta
 393        {
 81394            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 81395            var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder;
 396
 81397            return _imageProviders.Where(i => CanRefreshImages(i, item, typeOptions, refreshOptions, includeDisabled))
 81398                .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name))
 81399                .ThenBy(GetDefaultOrder);
 400        }
 401
 402        private bool CanRefreshImages(
 403            IImageProvider provider,
 404            BaseItem item,
 405            TypeOptions? libraryTypeOptions,
 406            ImageRefreshOptions refreshOptions,
 407            bool includeDisabled)
 408        {
 409            try
 410            {
 1131411                if (!provider.Supports(item))
 412                {
 970413                    return false;
 414                }
 160415            }
 1416            catch (Exception ex)
 417            {
 1418                _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.Get
 1419                return false;
 420            }
 421
 160422            if (includeDisabled || provider is ILocalImageProvider)
 423            {
 153424                return true;
 425            }
 426
 7427            if (item.IsLocked && refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh)
 428            {
 1429                return false;
 430            }
 431
 6432            return _baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name);
 971433        }
 434
 435        /// <inheritdoc />
 436        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
 437            where T : BaseItem
 438        {
 96439            var globalMetadataOptions = GetMetadataOptions(item);
 96440            var libraryPath = GetLibraryPathForItem(item);
 441
 96442            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false, libraryPat
 443        }
 444
 445        /// <summary>
 446        /// Gets metadata providers for the specified item.
 447        /// </summary>
 448        /// <typeparam name="T">The item type.</typeparam>
 449        /// <param name="item">The item.</param>
 450        /// <param name="libraryOptions">The library options.</param>
 451        /// <param name="includeDisabled">Whether to include disabled providers.</param>
 452        /// <returns>The metadata providers.</returns>
 453        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions, b
 454            where T : BaseItem
 455        {
 0456            var globalMetadataOptions = GetMetadataOptions(item);
 0457            var libraryPath = GetLibraryPathForItem(item);
 458
 0459            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, includeDisabled, false, 
 460        }
 461
 462        private static string GetLibraryPathForItem(BaseItem item)
 463        {
 96464            if (item is CollectionFolder collectionFolder)
 465            {
 14466                return collectionFolder.Path ?? string.Empty;
 467            }
 468
 82469            var topParent = item.GetTopParent();
 82470            return topParent?.Path ?? string.Empty;
 471        }
 472
 473        /// <inheritdoc />
 474        public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
 475        {
 34476            return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false)
 477        }
 478
 479        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryO
 480            where T : BaseItem
 481        {
 96482            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 483
 96484            var orderedProviders = GetOrCreateOrderedProviders<T>(item.GetType().Name, libraryOptions, globalMetadataOpt
 485
 96486            return orderedProviders.Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInte
 487        }
 488
 489        private IMetadataProvider<T>[] GetOrCreateOrderedProviders<T>(
 490            string itemTypeName,
 491            LibraryOptions libraryOptions,
 492            MetadataOptions globalMetadataOptions,
 493            bool includeDisabled,
 494            bool forceEnableInternetMetadata,
 495            string libraryPath)
 496            where T : BaseItem
 497        {
 96498            var cacheKey = new MetadataProviderCacheKey(libraryPath, itemTypeName, includeDisabled, forceEnableInternetM
 96499            if (_metadataProviderCache.TryGetValue(cacheKey, out var cachedProviders))
 500            {
 22501                return cachedProviders.OfType<IMetadataProvider<T>>().ToArray();
 502            }
 503
 74504            var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadat
 74505            var typeOptions = libraryOptions.GetTypeOptions(itemTypeName);
 74506            var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
 507
 74508            var orderedProviders = _metadataProviders.OfType<IMetadataProvider<T>>()
 74509                .Where(i => CanRefreshMetadataForCache(i, typeOptions, includeDisabled, forceEnableInternetMetadata))
 74510                .OrderBy(i =>
 74511                    // local and remote providers will be interleaved in the final order
 74512                    // only relative order within a type matters: consumers of the list filter to one or the other
 74513                    i switch
 74514                    {
 74515                        ILocalMetadataProvider => GetConfiguredOrder(localMetadataReaderOrder, i.Name),
 74516                        IRemoteMetadataProvider => GetConfiguredOrder(metadataFetcherOrder, i.Name),
 74517                        // Default to end
 74518                        _ => int.MaxValue
 74519                    })
 74520                .ThenBy(GetDefaultOrder)
 74521                .ToArray();
 522
 74523            _metadataProviderCache.TryAdd(cacheKey, orderedProviders.Cast<IMetadataProvider>().ToArray());
 524
 74525            return orderedProviders;
 526        }
 527
 528        private static bool CanRefreshMetadataForCache(
 529            IMetadataProvider provider,
 530            TypeOptions? libraryTypeOptions,
 531            bool includeDisabled,
 532            bool forceEnableInternetMetadata)
 533        {
 141534            if (includeDisabled)
 535            {
 0536                return true;
 537            }
 538
 141539            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 540            {
 93541                return true;
 542            }
 543
 48544            if (libraryTypeOptions?.MetadataFetchers is { Length: > 0 } metadataFetchers)
 545            {
 0546                return metadataFetchers.Contains(provider.Name, StringComparer.OrdinalIgnoreCase);
 547            }
 548
 48549            return true;
 550        }
 551
 552        private bool CanRefreshMetadata(
 553            IMetadataProvider provider,
 554            BaseItem item,
 555            TypeOptions? libraryTypeOptions,
 556            bool includeDisabled,
 557            bool forceEnableInternetMetadata)
 558        {
 163559            if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider)
 560            {
 1561                return false;
 562            }
 563
 162564            if (includeDisabled)
 565            {
 0566                return true;
 567            }
 568
 569            // If locked only allow local providers
 162570            if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider)
 571            {
 3572                return false;
 573            }
 574
 159575            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 576            {
 112577                return true;
 578            }
 579
 580            // Artists without a folder structure that are derived from metadata have no real path in the library,
 581            // so GetLibraryOptions returns null. Allow all providers through rather than blocking them.
 47582            if (item is MusicArtist && libraryTypeOptions is null)
 583            {
 0584                return true;
 585            }
 586
 47587            return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
 588        }
 589
 590        private static int GetConfiguredOrder(string[] order, string providerName)
 591        {
 237592            var index = Array.IndexOf(order, providerName);
 593
 237594            if (index != -1)
 595            {
 54596                return index;
 597            }
 598
 599            // default to end
 183600            return int.MaxValue;
 601        }
 602
 603        private static int GetDefaultOrder(object provider)
 604        {
 237605            if (provider is IHasOrder hasOrder)
 606            {
 139607                return hasOrder.Order;
 608            }
 609
 610            // after items that want to be first (~0) but before items that want to be last (~100)
 98611            return 50;
 612        }
 613
 614        /// <inheritdoc/>
 615        public MetadataPluginSummary[] GetAllMetadataPlugins()
 616        {
 0617            return new[]
 0618            {
 0619                GetPluginSummary<Movie>(),
 0620                GetPluginSummary<BoxSet>(),
 0621                GetPluginSummary<Book>(),
 0622                GetPluginSummary<Series>(),
 0623                GetPluginSummary<Season>(),
 0624                GetPluginSummary<Episode>(),
 0625                GetPluginSummary<MusicAlbum>(),
 0626                GetPluginSummary<MusicArtist>(),
 0627                GetPluginSummary<Audio>(),
 0628                GetPluginSummary<AudioBook>(),
 0629                GetPluginSummary<Studio>(),
 0630                GetPluginSummary<MusicVideo>(),
 0631                GetPluginSummary<Video>()
 0632            };
 633        }
 634
 635        private MetadataPluginSummary GetPluginSummary<T>()
 636            where T : BaseItem, new()
 637        {
 638            // Give it a dummy path just so that it looks like a file system item
 0639            var dummy = new T
 0640            {
 0641                Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0642                ParentId = Guid.NewGuid()
 0643            };
 644
 0645            var options = GetMetadataOptions(dummy);
 646
 0647            var summary = new MetadataPluginSummary
 0648            {
 0649                ItemType = typeof(T).Name
 0650            };
 651
 0652            var libraryOptions = new LibraryOptions();
 653
 0654            var imageProviders = GetImageProvidersInternal(
 0655                dummy,
 0656                libraryOptions,
 0657                options,
 0658                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0659                true).ToList();
 660
 0661            var pluginList = summary.Plugins.ToList();
 662
 0663            AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
 0664            AddImagePlugins(pluginList, imageProviders);
 665
 666            // Subtitle fetchers
 0667            var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
 0668            pluginList.AddRange(subtitleProviders.Select(i => new MetadataPlugin
 0669            {
 0670                Name = i.Name,
 0671                Type = MetadataPluginType.SubtitleFetcher
 0672            }));
 673
 674            // Lyric fetchers
 0675            var lyricProviders = _lyricManager.GetSupportedProviders(dummy);
 0676            pluginList.AddRange(lyricProviders.Select(i => new MetadataPlugin
 0677            {
 0678                Name = i.Name,
 0679                Type = MetadataPluginType.LyricFetcher
 0680            }));
 681
 682            // Media segment providers
 0683            var mediaSegmentProviders = _mediaSegmentManager.GetSupportedProviders(dummy);
 0684            pluginList.AddRange(mediaSegmentProviders.Select(i => new MetadataPlugin
 0685            {
 0686                Name = i.Name,
 0687                Type = MetadataPluginType.MediaSegmentProvider
 0688            }));
 689
 0690            summary.Plugins = pluginList.ToArray();
 691
 0692            var supportedImageTypes = imageProviders.OfType<IRemoteImageProvider>()
 0693                .SelectMany(i => i.GetSupportedImages(dummy))
 0694                .ToList();
 695
 0696            supportedImageTypes.AddRange(imageProviders.OfType<IDynamicImageProvider>()
 0697                .SelectMany(i => i.GetSupportedImages(dummy)));
 698
 0699            summary.SupportedImageTypes = supportedImageTypes.Distinct().ToArray();
 700
 0701            return summary;
 702        }
 703
 704        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, LibraryOptions libraryOptions, MetadataOpt
 705            where T : BaseItem
 706        {
 0707            var libraryPath = GetLibraryPathForItem(item);
 0708            var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true, libraryPath).ToLi
 709
 710            // Locals
 0711            list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin
 0712            {
 0713                Name = i.Name,
 0714                Type = MetadataPluginType.LocalMetadataProvider
 0715            }));
 716
 717            // Fetchers
 0718            list.AddRange(providers.Where(i => i is IRemoteMetadataProvider).Select(i => new MetadataPlugin
 0719            {
 0720                Name = i.Name,
 0721                Type = MetadataPluginType.MetadataFetcher
 0722            }));
 723
 724            // Savers
 0725            list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit,
 0726            {
 0727                Name = i.Name,
 0728                Type = MetadataPluginType.MetadataSaver
 0729            }));
 0730        }
 731
 732        private void AddImagePlugins(List<MetadataPlugin> list, List<IImageProvider> imageProviders)
 733        {
 734            // Locals
 0735            list.AddRange(imageProviders.Where(i => i is ILocalImageProvider).Select(i => new MetadataPlugin
 0736            {
 0737                Name = i.Name,
 0738                Type = MetadataPluginType.LocalImageProvider
 0739            }));
 740
 741            // Fetchers
 0742            list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (i is IRemoteImageProvider)).Select(i 
 0743            {
 0744                Name = i.Name,
 0745                Type = MetadataPluginType.ImageFetcher
 0746            }));
 0747        }
 748
 749        /// <inheritdoc/>
 750        public MetadataOptions GetMetadataOptions(BaseItem item)
 1321751            => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
 752
 753        /// <inheritdoc/>
 754        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
 109755            => SaveMetadataAsync(item, updateType, _savers);
 756
 757        /// <inheritdoc/>
 758        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
 0759            => SaveMetadataAsync(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIg
 760
 761        /// <summary>
 762        /// Saves the metadata.
 763        /// </summary>
 764        /// <param name="item">The item.</param>
 765        /// <param name="updateType">Type of the update.</param>
 766        /// <param name="savers">The savers.</param>
 767        private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> saver
 768        {
 109769            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 109770            var applicableSavers = savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)).
 109771            if (applicableSavers.Count == 0)
 772            {
 109773                return;
 774            }
 775
 0776            foreach (var saver in applicableSavers)
 777            {
 0778                _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
 779
 0780                if (saver is IMetadataFileSaver fileSaver)
 781                {
 782                    string path;
 783
 784                    try
 785                    {
 0786                        path = fileSaver.GetSavePath(item);
 0787                    }
 0788                    catch (Exception ex)
 789                    {
 0790                        _logger.LogError(ex, "Error in {Saver} GetSavePath", saver.Name);
 0791                        continue;
 792                    }
 793
 794                    try
 795                    {
 0796                        _libraryMonitor.ReportFileSystemChangeBeginning(path);
 0797                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 0798                        item.DateLastSaved = DateTime.UtcNow;
 0799                    }
 0800                    catch (Exception ex)
 801                    {
 0802                        _logger.LogError(ex, "Error in metadata saver");
 0803                    }
 804                    finally
 805                    {
 0806                        _libraryMonitor.ReportFileSystemChangeComplete(path, false);
 807                    }
 0808                }
 809                else
 810                {
 811                    try
 812                    {
 0813                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 0814                        item.DateLastSaved = DateTime.UtcNow;
 0815                    }
 0816                    catch (Exception ex)
 817                    {
 0818                        _logger.LogError(ex, "Error in metadata saver");
 0819                    }
 820                }
 821            }
 109822        }
 823
 824        /// <summary>
 825        /// Determines whether [is saver enabled for item] [the specified saver].
 826        /// </summary>
 827        private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdat
 828        {
 1144829            var options = GetMetadataOptions(item);
 830
 831            try
 832            {
 1144833                if (!saver.IsEnabledFor(item, updateType))
 834                {
 1144835                    return false;
 836                }
 837
 0838                if (!includeDisabled)
 839                {
 0840                    if (libraryOptions.MetadataSavers is null)
 841                    {
 0842                        if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 843                        {
 0844                            return false;
 845                        }
 846
 0847                        if (!item.IsSaveLocalMetadataEnabled())
 848                        {
 0849                            if (updateType >= ItemUpdateType.MetadataEdit)
 850                            {
 851                                // Manual edit occurred
 852                                // Even if save local is off, save locally anyway if the metadata file already exists
 0853                                if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item
 854                                {
 0855                                    return false;
 856                                }
 857                            }
 858                            else
 859                            {
 860                                // Manual edit did not occur
 861                                // Since local metadata saving is disabled, consider it disabled
 0862                                return false;
 863                            }
 864                        }
 865                    }
 866                    else
 867                    {
 0868                        if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 869                        {
 0870                            return false;
 871                        }
 872                    }
 873                }
 874
 0875                return true;
 876            }
 0877            catch (Exception ex)
 878            {
 0879                _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name);
 0880                return false;
 881            }
 1144882        }
 883
 884        /// <inheritdoc/>
 885        public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TL
 886            where TItemType : BaseItem, new()
 887            where TLookupType : ItemLookupInfo
 888        {
 0889            BaseItem? referenceItem = null;
 890
 0891            if (!searchInfo.ItemId.IsEmpty())
 892            {
 0893                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
 894            }
 895
 0896            return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
 897        }
 898
 899        private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQ
 900            where TItemType : BaseItem, new()
 901            where TLookupType : ItemLookupInfo
 902        {
 903            LibraryOptions libraryOptions;
 904
 0905            if (referenceItem is null)
 906            {
 907                // Give it a dummy path just so that it looks like a file system item
 0908                var dummy = new TItemType
 0909                {
 0910                    Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0911                    ParentId = Guid.NewGuid()
 0912                };
 913
 0914                dummy.SetParent(new Folder());
 915
 0916                referenceItem = dummy;
 0917                libraryOptions = new LibraryOptions();
 918            }
 919            else
 920            {
 0921                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
 922            }
 923
 0924            var options = GetMetadataOptions(referenceItem);
 0925            var libraryPath = GetLibraryPathForItem(referenceItem);
 0926            var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.I
 0927                .OfType<IRemoteSearchProvider<TLookupType>>();
 928
 0929            if (!string.IsNullOrEmpty(searchInfo.SearchProviderName))
 930            {
 0931                providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.O
 932            }
 933
 0934            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
 935            {
 0936                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
 937            }
 938
 0939            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
 940            {
 0941                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
 942            }
 943
 0944            var resultList = new List<RemoteSearchResult>();
 945
 0946            foreach (var provider in providers)
 947            {
 948                try
 949                {
 0950                    var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwa
 951
 0952                    foreach (var result in results)
 953                    {
 0954                        result.SearchProviderName = provider.Name;
 955
 0956                        var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.G
 957
 0958                        if (existingMatch is null)
 959                        {
 0960                            resultList.Add(result);
 961                        }
 962                        else
 963                        {
 0964                            foreach (var providerId in result.ProviderIds)
 965                            {
 0966                                existingMatch.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 967                            }
 968
 0969                            if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
 970                            {
 0971                                existingMatch.ImageUrl = result.ImageUrl;
 972                            }
 973                        }
 974                    }
 0975                }
 976#pragma warning disable CA1031 // do not catch general exception types
 0977                catch (Exception ex)
 978#pragma warning restore CA1031 // do not catch general exception types
 979                {
 0980                    _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
 0981                }
 0982            }
 983
 0984            return resultList;
 0985        }
 986
 987        private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
 988        {
 0989            return _externalIds.Where(i =>
 0990            {
 0991                try
 0992                {
 0993                    return i.Supports(item);
 0994                }
 0995                catch (Exception ex)
 0996                {
 0997                    _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name);
 0998                    return false;
 0999                }
 01000            });
 1001        }
 1002
 1003        /// <inheritdoc/>
 1004        public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
 1005        {
 61006            return _externalUrlProviders
 61007                .SelectMany(p => p
 61008                    .GetExternalUrls(item)
 61009                    .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
 1010        }
 1011
 1012        /// <inheritdoc/>
 1013        public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
 1014        {
 01015            return GetExternalIds(item)
 01016                .Select(i => new ExternalIdInfo(
 01017                    name: i.ProviderName,
 01018                    key: i.Key,
 01019                    type: i.Type));
 1020        }
 1021
 1022        /// <inheritdoc/>
 1023        public HashSet<Guid> GetRefreshQueue()
 11024        {
 1025            lock (_refreshQueueLock)
 1026            {
 11027                return _refreshQueue.UnorderedItems.Select(x => x.Element.ItemId).ToHashSet();
 1028            }
 11029        }
 1030
 1031        /// <inheritdoc/>
 1032        public void OnRefreshStart(BaseItem item)
 1033        {
 171034            _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
 171035            _activeRefreshes[item.Id] = 0;
 1036            try
 1037            {
 171038                RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 171039            }
 01040            catch (Exception ex)
 1041            {
 1042                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01043                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
 01044            }
 171045        }
 1046
 1047        /// <inheritdoc/>
 1048        public void OnRefreshComplete(BaseItem item)
 1049        {
 171050            _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
 171051            _activeRefreshes.TryRemove(item.Id, out _);
 1052
 1053            try
 1054            {
 171055                RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 171056            }
 01057            catch (Exception ex)
 1058            {
 1059                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01060                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
 01061            }
 171062        }
 1063
 1064        /// <inheritdoc/>
 1065        public double? GetRefreshProgress(Guid id)
 1066        {
 01067            if (_activeRefreshes.TryGetValue(id, out double value))
 1068            {
 01069                return value;
 1070            }
 1071
 01072            return null;
 1073        }
 1074
 1075        /// <inheritdoc/>
 1076        public void OnRefreshProgress(BaseItem item, double progress)
 1077        {
 821078            var id = item.Id;
 821079            _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
 1080
 821081            if (!_activeRefreshes.TryGetValue(id, out var current)
 821082                || progress <= current
 821083                || !_activeRefreshes.TryUpdate(id, progress, current))
 1084            {
 1085                // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
 321086                return;
 1087            }
 1088
 1089            try
 1090            {
 501091                RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(
 501092            }
 01093            catch (Exception ex)
 1094            {
 1095                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01096                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
 01097            }
 501098        }
 1099
 1100        /// <inheritdoc/>
 1101        public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
 1102        {
 01103            if (itemId.IsEmpty())
 1104            {
 01105                throw new ArgumentException("Guid can't be empty", nameof(itemId));
 1106            }
 1107
 01108            if (_disposed)
 1109            {
 01110                return;
 1111            }
 1112
 01113            _refreshQueue.Enqueue((itemId, options), priority);
 1114
 1115            lock (_refreshQueueLock)
 1116            {
 01117                if (!_isProcessingRefreshQueue)
 1118                {
 01119                    _isProcessingRefreshQueue = true;
 01120                    Task.Run(StartProcessingRefreshQueue);
 1121                }
 01122            }
 01123        }
 1124
 1125        private async Task StartProcessingRefreshQueue()
 1126        {
 01127            var libraryManager = _libraryManager;
 1128
 01129            if (_disposed)
 1130            {
 01131                return;
 1132            }
 1133
 01134            var cancellationToken = _disposeCancellationTokenSource.Token;
 1135
 01136            libraryManager.ClearIgnoreRuleCache();
 01137            while (_refreshQueue.TryDequeue(out var refreshItem, out _))
 1138            {
 01139                if (_disposed)
 1140                {
 01141                    return;
 1142                }
 1143
 1144                try
 1145                {
 01146                    var item = libraryManager.GetItemById(refreshItem.ItemId);
 01147                    if (item is null)
 1148                    {
 01149                        continue;
 1150                    }
 1151
 01152                    var task = item is MusicArtist artist
 01153                        ? RefreshArtist(artist, refreshItem.RefreshOptions, cancellationToken)
 01154                        : RefreshItem(item, refreshItem.RefreshOptions, cancellationToken);
 1155
 01156                    await task.ConfigureAwait(false);
 01157                }
 01158                catch (OperationCanceledException)
 1159                {
 01160                    break;
 1161                }
 01162                catch (Exception ex)
 1163                {
 01164                    _logger.LogError(ex, "Error refreshing item");
 01165                }
 1166            }
 1167
 1168            lock (_refreshQueueLock)
 1169            {
 01170                _isProcessingRefreshQueue = false;
 01171                libraryManager.ClearIgnoreRuleCache();
 01172            }
 01173        }
 1174
 1175        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToke
 1176        {
 01177            await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1178
 1179            // Collection folders don't validate their children so we'll have to simulate that here
 1180            switch (item)
 1181            {
 1182                case CollectionFolder collectionFolder:
 01183                    await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(f
 01184                    break;
 1185                case Folder folder:
 01186                    await folder.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken)
 1187                    break;
 1188            }
 01189        }
 1190
 1191        private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFo
 1192        {
 01193            foreach (var child in collectionFolder.GetPhysicalFolders())
 1194            {
 01195                await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1196
 01197                await child.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken).Conf
 01198            }
 01199        }
 1200
 1201        private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellatio
 1202        {
 01203            var albums = _libraryManager
 01204                .GetItemList(new InternalItemsQuery
 01205                {
 01206                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 01207                    ArtistIds = new[] { item.Id },
 01208                    DtoOptions = new DtoOptions(false)
 01209                    {
 01210                        EnableImages = false
 01211                    }
 01212                })
 01213                .OfType<MusicAlbum>();
 1214
 01215            var musicArtists = albums
 01216                .Select(i => i.MusicArtist)
 01217                .Where(i => i is not null)
 01218                .Distinct();
 1219
 01220            var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, t
 1221
 01222            await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
 1223
 1224            try
 1225            {
 01226                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 01227            }
 01228            catch (Exception ex)
 1229            {
 01230                _logger.LogError(ex, "Error refreshing library");
 01231            }
 01232        }
 1233
 1234        /// <inheritdoc/>
 1235        public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 1236        {
 01237            return RefreshItem(item, options, cancellationToken);
 1238        }
 1239
 1240        /// <inheritdoc/>
 1241        public void Dispose()
 1242        {
 911243            Dispose(true);
 911244            GC.SuppressFinalize(this);
 911245        }
 1246
 1247        /// <summary>
 1248        /// Releases unmanaged and optionally managed resources.
 1249        /// </summary>
 1250        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 1251        protected virtual void Dispose(bool disposing)
 1252        {
 911253            if (_disposed)
 1254            {
 01255                return;
 1256            }
 1257
 911258            if (disposing)
 1259            {
 911260                CollectionFolder.LibraryOptionsUpdated -= OnLibraryOptionsUpdated;
 1261
 911262                if (!_disposeCancellationTokenSource.IsCancellationRequested)
 1263                {
 911264                    _disposeCancellationTokenSource.Cancel();
 1265                }
 1266
 911267                _disposeCancellationTokenSource.Dispose();
 911268                _imageSaveLock.Dispose();
 1269            }
 1270
 911271            _disposed = true;
 911272        }
 1273
 1274        private void OnLibraryOptionsUpdated(object? sender, LibraryOptionsUpdatedEventArgs e)
 1275        {
 31276            var keysToRemove = _metadataProviderCache.Keys
 31277                .Where(k => string.Equals(k.LibraryPath, e.LibraryPath, StringComparison.Ordinal))
 31278                .ToList();
 1279
 81280            foreach (var key in keysToRemove)
 1281            {
 11282                _metadataProviderCache.TryRemove(key, out _);
 1283            }
 1284
 31285            _logger.LogDebug("Invalidated metadata provider cache for library: {LibraryPath}", e.LibraryPath);
 31286        }
 1287
 1288        internal void ClearMetadataProviderCache()
 1289        {
 01290            _metadataProviderCache.Clear();
 01291            _logger.LogDebug("Cleared entire metadata provider cache");
 01292        }
 1293
 1294        /// <summary>
 1295        /// Cache key for metadata provider lookups.
 1296        /// </summary>
 1297        /// <param name="LibraryPath">The library path for the collection folder.</param>
 1298        /// <param name="ItemTypeName">The item type name.</param>
 1299        /// <param name="IncludeDisabled">Whether to include disabled providers.</param>
 1300        /// <param name="ForceEnableInternetMetadata">Whether internet metadata is force-enabled.</param>
 1301        private readonly record struct MetadataProviderCacheKey(
 1302            string LibraryPath,
 1303            string ItemTypeName,
 1304            bool IncludeDisabled,
 1305            bool ForceEnableInternetMetadata);
 1306    }
 1307}

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