< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.ProviderManager
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/ProviderManager.cs
Line coverage
45%
Covered lines: 136
Uncovered lines: 165
Coverable lines: 301
Total lines: 1165
Line coverage: 45.1%
Branch coverage
54%
Covered branches: 61
Total branches: 112
Branch coverage: 54.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

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 Microsoft.Extensions.Caching.Memory;
 35using Microsoft.Extensions.Logging;
 36using Book = MediaBrowser.Controller.Entities.Book;
 37using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 38using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
 39using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
 40using Season = MediaBrowser.Controller.Entities.TV.Season;
 41using Series = MediaBrowser.Controller.Entities.TV.Series;
 42
 43namespace MediaBrowser.Providers.Manager
 44{
 45    /// <summary>
 46    /// Class ProviderManager.
 47    /// </summary>
 48    public class ProviderManager : IProviderManager, IDisposable
 49    {
 9150        private readonly Lock _refreshQueueLock = new();
 51        private readonly ILogger<ProviderManager> _logger;
 52        private readonly IHttpClientFactory _httpClientFactory;
 53        private readonly ILibraryMonitor _libraryMonitor;
 54        private readonly IFileSystem _fileSystem;
 55        private readonly IServerApplicationPaths _appPaths;
 56        private readonly ILibraryManager _libraryManager;
 57        private readonly ISubtitleManager _subtitleManager;
 58        private readonly ILyricManager _lyricManager;
 59        private readonly IServerConfigurationManager _configurationManager;
 60        private readonly IBaseItemManager _baseItemManager;
 9161        private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new();
 9162        private readonly CancellationTokenSource _disposeCancellationTokenSource = new();
 9163        private readonly PriorityQueue<(Guid ItemId, MetadataRefreshOptions RefreshOptions), RefreshPriority> _refreshQu
 64        private readonly IMemoryCache _memoryCache;
 65        private readonly IMediaSegmentManager _mediaSegmentManager;
 9166        private readonly AsyncKeyedLocker<string> _imageSaveLock = new(o =>
 9167        {
 9168            o.PoolSize = 20;
 9169            o.PoolInitialFill = 1;
 9170        });
 71
 9172        private IImageProvider[] _imageProviders = [];
 9173        private IMetadataService[] _metadataServices = [];
 9174        private IMetadataProvider[] _metadataProviders = [];
 9175        private IMetadataSaver[] _savers = [];
 9176        private IExternalId[] _externalIds = [];
 9177        private IExternalUrlProvider[] _externalUrlProviders = [];
 78        private bool _isProcessingRefreshQueue;
 79        private bool _disposed;
 80
 81        /// <summary>
 82        /// Initializes a new instance of the <see cref="ProviderManager"/> class.
 83        /// </summary>
 84        /// <param name="httpClientFactory">The Http client factory.</param>
 85        /// <param name="subtitleManager">The subtitle manager.</param>
 86        /// <param name="configurationManager">The configuration manager.</param>
 87        /// <param name="libraryMonitor">The library monitor.</param>
 88        /// <param name="logger">The logger.</param>
 89        /// <param name="fileSystem">The filesystem.</param>
 90        /// <param name="appPaths">The server application paths.</param>
 91        /// <param name="libraryManager">The library manager.</param>
 92        /// <param name="baseItemManager">The BaseItem manager.</param>
 93        /// <param name="lyricManager">The lyric manager.</param>
 94        /// <param name="memoryCache">The memory cache.</param>
 95        /// <param name="mediaSegmentManager">The media segment manager.</param>
 96        public ProviderManager(
 97            IHttpClientFactory httpClientFactory,
 98            ISubtitleManager subtitleManager,
 99            IServerConfigurationManager configurationManager,
 100            ILibraryMonitor libraryMonitor,
 101            ILogger<ProviderManager> logger,
 102            IFileSystem fileSystem,
 103            IServerApplicationPaths appPaths,
 104            ILibraryManager libraryManager,
 105            IBaseItemManager baseItemManager,
 106            ILyricManager lyricManager,
 107            IMemoryCache memoryCache,
 108            IMediaSegmentManager mediaSegmentManager)
 109        {
 91110            _logger = logger;
 91111            _httpClientFactory = httpClientFactory;
 91112            _configurationManager = configurationManager;
 91113            _libraryMonitor = libraryMonitor;
 91114            _fileSystem = fileSystem;
 91115            _appPaths = appPaths;
 91116            _libraryManager = libraryManager;
 91117            _subtitleManager = subtitleManager;
 91118            _baseItemManager = baseItemManager;
 91119            _lyricManager = lyricManager;
 91120            _memoryCache = memoryCache;
 91121            _mediaSegmentManager = mediaSegmentManager;
 91122        }
 123
 124        /// <inheritdoc/>
 125        public event EventHandler<GenericEventArgs<BaseItem>>? RefreshStarted;
 126
 127        /// <inheritdoc/>
 128        public event EventHandler<GenericEventArgs<BaseItem>>? RefreshCompleted;
 129
 130        /// <inheritdoc/>
 131        public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>>? RefreshProgress;
 132
 133        /// <inheritdoc/>
 134        public void AddParts(
 135            IEnumerable<IImageProvider> imageProviders,
 136            IEnumerable<IMetadataService> metadataServices,
 137            IEnumerable<IMetadataProvider> metadataProviders,
 138            IEnumerable<IMetadataSaver> metadataSavers,
 139            IEnumerable<IExternalId> externalIds,
 140            IEnumerable<IExternalUrlProvider> externalUrlProviders)
 141        {
 91142            _imageProviders = imageProviders.ToArray();
 91143            _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
 91144            _metadataProviders = metadataProviders.ToArray();
 91145            _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
 91146            _externalUrlProviders = externalUrlProviders.OrderBy(i => i.Name).ToArray();
 147
 91148            _savers = metadataSavers.ToArray();
 91149        }
 150
 151        /// <inheritdoc/>
 152        public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken c
 153        {
 55154            var type = item.GetType();
 155
 55156            var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type))
 55157                ?? _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
 158
 55159            if (service is null)
 160            {
 1161                _logger.LogError("Unable to find a metadata service for item of type {TypeName}", type.Name);
 1162                return Task.FromResult(ItemUpdateType.None);
 163            }
 164
 54165            return service.RefreshMetadata(item, options, cancellationToken);
 166        }
 167
 168        /// <inheritdoc/>
 169        public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancel
 170        {
 171            using (await _imageSaveLock.LockAsync(url, cancellationToken).ConfigureAwait(false))
 172            {
 173                if (_memoryCache.TryGetValue(url, out (string ContentType, byte[] ImageContents)? cachedValue)
 174                    && cachedValue is not null)
 175                {
 176                    var imageContents = cachedValue.Value.ImageContents;
 177                    var cacheStream = new MemoryStream(imageContents, 0, imageContents.Length, false);
 178                    await using (cacheStream.ConfigureAwait(false))
 179                    {
 180                        await SaveImage(
 181                            item,
 182                            cacheStream,
 183                            cachedValue.Value.ContentType,
 184                            type,
 185                            imageIndex,
 186                            cancellationToken).ConfigureAwait(false);
 187                        return;
 188                    }
 189                }
 190
 191                var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
 192                using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
 193
 194                response.EnsureSuccessStatusCode();
 195
 196                var contentType = response.Content.Headers.ContentType?.MediaType;
 197
 198                // Workaround for tvheadend channel icons
 199                // TODO: Isolate this hack into the tvh plugin
 200                if (string.IsNullOrEmpty(contentType))
 201                {
 202                    // Special case for imagecache
 203                    if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase))
 204                    {
 205                        contentType = MediaTypeNames.Image.Png;
 206                    }
 207                }
 208
 209                // some providers don't correctly report media type, extract from url if no extension found
 210                if (contentType is null || contentType.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordinal
 211                {
 212                    // Strip query parameters from url to get actual path.
 213                    contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
 214                }
 215
 216                if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
 217                {
 218                    throw new HttpRequestException($"Request returned '{contentType}' instead of an image type", null, H
 219                }
 220
 221                var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false)
 222                var stream = new MemoryStream(responseBytes, 0, responseBytes.Length, false);
 223                await using (stream.ConfigureAwait(false))
 224                {
 225                    _memoryCache.Set(url, (contentType, responseBytes), TimeSpan.FromSeconds(10));
 226
 227                    await SaveImage(
 228                        item,
 229                        stream,
 230                        contentType,
 231                        type,
 232                        imageIndex,
 233                        cancellationToken).ConfigureAwait(false);
 234                }
 235            }
 236        }
 237
 238        /// <inheritdoc/>
 239        public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, Cancellati
 240        {
 0241            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, 
 242        }
 243
 244        /// <inheritdoc/>
 245        public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool
 246        {
 247            if (string.IsNullOrWhiteSpace(source))
 248            {
 249                throw new ArgumentNullException(nameof(source));
 250            }
 251
 252            try
 253            {
 254                var fileStream = AsyncFile.OpenRead(source);
 255                await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 256                    .SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken)
 257                    .ConfigureAwait(false);
 258            }
 259            finally
 260            {
 261                try
 262                {
 263                    File.Delete(source);
 264                }
 265                catch (Exception ex)
 266                {
 267                    _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source);
 268                }
 269            }
 270        }
 271
 272        /// <inheritdoc/>
 273        public Task SaveImage(Stream source, string mimeType, string path)
 274        {
 0275            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0276                .SaveImage(source, path);
 277        }
 278
 279        /// <inheritdoc/>
 280        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, 
 281        {
 282            var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
 283
 284            if (!string.IsNullOrEmpty(query.ProviderName))
 285            {
 286                var providerName = query.ProviderName;
 287
 288                providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)
 289            }
 290
 291            if (query.ImageType is not null)
 292            {
 293                providers = providers.Where(i => i.GetSupportedImages(item).Contains(query.ImageType.Value));
 294            }
 295
 296            var preferredLanguage = item.GetPreferredMetadataLanguage();
 297
 298            var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellat
 299
 300            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 301
 302            return results.SelectMany(i => i);
 303        }
 304
 305        /// <summary>
 306        /// Gets the images.
 307        /// </summary>
 308        /// <param name="item">The item.</param>
 309        /// <param name="provider">The provider.</param>
 310        /// <param name="preferredLanguage">The preferred language.</param>
 311        /// <param name="includeAllLanguages">Whether to include all languages in results.</param>
 312        /// <param name="cancellationToken">The cancellation token.</param>
 313        /// <param name="type">The type.</param>
 314        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
 315        private async Task<IEnumerable<RemoteImageInfo>> GetImages(
 316            BaseItem item,
 317            IRemoteImageProvider provider,
 318            string preferredLanguage,
 319            bool includeAllLanguages,
 320            CancellationToken cancellationToken,
 321            ImageType? type = null)
 322        {
 323            bool hasPreferredLanguage = !string.IsNullOrWhiteSpace(preferredLanguage);
 324
 325            try
 326            {
 327                var result = await provider.GetImages(item, cancellationToken).ConfigureAwait(false);
 328
 329                if (type.HasValue)
 330                {
 331                    result = result.Where(i => i.Type == type.Value);
 332                }
 333
 334                if (!includeAllLanguages && hasPreferredLanguage)
 335                {
 336                    // Filter out languages that do not match the preferred languages.
 337                    //
 338                    // TODO: should exception case of "en" (English) eventually be removed?
 339                    result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
 340                                               string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgno
 341                                               string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
 342                }
 343
 344                return result.OrderByLanguageDescending(preferredLanguage);
 345            }
 346            catch (OperationCanceledException)
 347            {
 348                return Enumerable.Empty<RemoteImageInfo>();
 349            }
 350            catch (Exception ex)
 351            {
 352                _logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provide
 353                return Enumerable.Empty<RemoteImageInfo>();
 354            }
 355        }
 356
 357        /// <inheritdoc/>
 358        public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
 359        {
 0360            return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(it
 361        }
 362
 363        private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled)
 364        {
 0365            var options = GetMetadataOptions(item);
 0366            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 367
 0368            return GetImageProvidersInternal(
 0369                item,
 0370                libraryOptions,
 0371                options,
 0372                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0373                includeDisabled).OfType<IRemoteImageProvider>();
 374        }
 375
 376        /// <inheritdoc/>
 377        public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
 378        {
 73379            return GetImageProvidersInternal(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), re
 380        }
 381
 382        private IEnumerable<IImageProvider> GetImageProvidersInternal(BaseItem item, LibraryOptions libraryOptions, Meta
 383        {
 73384            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 73385            var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder;
 386
 73387            return _imageProviders.Where(i => CanRefreshImages(i, item, typeOptions, refreshOptions, includeDisabled))
 73388                .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name))
 73389                .ThenBy(GetDefaultOrder);
 390        }
 391
 392        private bool CanRefreshImages(
 393            IImageProvider provider,
 394            BaseItem item,
 395            TypeOptions? libraryTypeOptions,
 396            ImageRefreshOptions refreshOptions,
 397            bool includeDisabled)
 398        {
 399            try
 400            {
 930401                if (!provider.Supports(item))
 402                {
 785403                    return false;
 404                }
 144405            }
 1406            catch (Exception ex)
 407            {
 1408                _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.Get
 1409                return false;
 410            }
 411
 144412            if (includeDisabled || provider is ILocalImageProvider)
 413            {
 137414                return true;
 415            }
 416
 7417            if (item.IsLocked && refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh)
 418            {
 1419                return false;
 420            }
 421
 6422            return _baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name);
 786423        }
 424
 425        /// <inheritdoc />
 426        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
 427            where T : BaseItem
 428        {
 89429            var globalMetadataOptions = GetMetadataOptions(item);
 430
 89431            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false);
 432        }
 433
 434        /// <inheritdoc />
 435        public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
 436        {
 49437            return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false)
 438        }
 439
 440        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryO
 441            where T : BaseItem
 442        {
 89443            var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadat
 89444            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 89445            var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
 446
 89447            return _metadataProviders.OfType<IMetadataProvider<T>>()
 89448                .Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInternetMetadata))
 89449                .OrderBy(i =>
 89450                    // local and remote providers will be interleaved in the final order
 89451                    // only relative order within a type matters: consumers of the list filter to one or the other
 89452                    i switch
 89453                    {
 89454                        ILocalMetadataProvider => GetConfiguredOrder(localMetadataReaderOrder, i.Name),
 89455                        IRemoteMetadataProvider => GetConfiguredOrder(metadataFetcherOrder, i.Name),
 89456                        // Default to end
 89457                        _ => int.MaxValue
 89458                    })
 89459                .ThenBy(GetDefaultOrder);
 460        }
 461
 462        private bool CanRefreshMetadata(
 463            IMetadataProvider provider,
 464            BaseItem item,
 465            TypeOptions? libraryTypeOptions,
 466            bool includeDisabled,
 467            bool forceEnableInternetMetadata)
 468        {
 156469            if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider)
 470            {
 1471                return false;
 472            }
 473
 155474            if (includeDisabled)
 475            {
 0476                return true;
 477            }
 478
 479            // If locked only allow local providers
 155480            if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider)
 481            {
 3482                return false;
 483            }
 484
 152485            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 486            {
 105487                return true;
 488            }
 489
 47490            return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
 491        }
 492
 493        private static int GetConfiguredOrder(string[] order, string providerName)
 494        {
 221495            var index = Array.IndexOf(order, providerName);
 496
 221497            if (index != -1)
 498            {
 54499                return index;
 500            }
 501
 502            // default to end
 167503            return int.MaxValue;
 504        }
 505
 506        private static int GetDefaultOrder(object provider)
 507        {
 221508            if (provider is IHasOrder hasOrder)
 509            {
 123510                return hasOrder.Order;
 511            }
 512
 513            // after items that want to be first (~0) but before items that want to be last (~100)
 98514            return 50;
 515        }
 516
 517        /// <inheritdoc/>
 518        public MetadataPluginSummary[] GetAllMetadataPlugins()
 519        {
 0520            return new[]
 0521            {
 0522                GetPluginSummary<Movie>(),
 0523                GetPluginSummary<BoxSet>(),
 0524                GetPluginSummary<Book>(),
 0525                GetPluginSummary<Series>(),
 0526                GetPluginSummary<Season>(),
 0527                GetPluginSummary<Episode>(),
 0528                GetPluginSummary<MusicAlbum>(),
 0529                GetPluginSummary<MusicArtist>(),
 0530                GetPluginSummary<Audio>(),
 0531                GetPluginSummary<AudioBook>(),
 0532                GetPluginSummary<Studio>(),
 0533                GetPluginSummary<MusicVideo>(),
 0534                GetPluginSummary<Video>()
 0535            };
 536        }
 537
 538        private MetadataPluginSummary GetPluginSummary<T>()
 539            where T : BaseItem, new()
 540        {
 541            // Give it a dummy path just so that it looks like a file system item
 0542            var dummy = new T
 0543            {
 0544                Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0545                ParentId = Guid.NewGuid()
 0546            };
 547
 0548            var options = GetMetadataOptions(dummy);
 549
 0550            var summary = new MetadataPluginSummary
 0551            {
 0552                ItemType = typeof(T).Name
 0553            };
 554
 0555            var libraryOptions = new LibraryOptions();
 556
 0557            var imageProviders = GetImageProvidersInternal(
 0558                dummy,
 0559                libraryOptions,
 0560                options,
 0561                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0562                true).ToList();
 563
 0564            var pluginList = summary.Plugins.ToList();
 565
 0566            AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
 0567            AddImagePlugins(pluginList, imageProviders);
 568
 569            // Subtitle fetchers
 0570            var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
 0571            pluginList.AddRange(subtitleProviders.Select(i => new MetadataPlugin
 0572            {
 0573                Name = i.Name,
 0574                Type = MetadataPluginType.SubtitleFetcher
 0575            }));
 576
 577            // Lyric fetchers
 0578            var lyricProviders = _lyricManager.GetSupportedProviders(dummy);
 0579            pluginList.AddRange(lyricProviders.Select(i => new MetadataPlugin
 0580            {
 0581                Name = i.Name,
 0582                Type = MetadataPluginType.LyricFetcher
 0583            }));
 584
 585            // Media segment providers
 0586            var mediaSegmentProviders = _mediaSegmentManager.GetSupportedProviders(dummy);
 0587            pluginList.AddRange(mediaSegmentProviders.Select(i => new MetadataPlugin
 0588            {
 0589                Name = i.Name,
 0590                Type = MetadataPluginType.MediaSegmentProvider
 0591            }));
 592
 0593            summary.Plugins = pluginList.ToArray();
 594
 0595            var supportedImageTypes = imageProviders.OfType<IRemoteImageProvider>()
 0596                .SelectMany(i => i.GetSupportedImages(dummy))
 0597                .ToList();
 598
 0599            supportedImageTypes.AddRange(imageProviders.OfType<IDynamicImageProvider>()
 0600                .SelectMany(i => i.GetSupportedImages(dummy)));
 601
 0602            summary.SupportedImageTypes = supportedImageTypes.Distinct().ToArray();
 603
 0604            return summary;
 605        }
 606
 607        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, LibraryOptions libraryOptions, MetadataOpt
 608            where T : BaseItem
 609        {
 0610            var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true).ToList();
 611
 612            // Locals
 0613            list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin
 0614            {
 0615                Name = i.Name,
 0616                Type = MetadataPluginType.LocalMetadataProvider
 0617            }));
 618
 619            // Fetchers
 0620            list.AddRange(providers.Where(i => i is IRemoteMetadataProvider).Select(i => new MetadataPlugin
 0621            {
 0622                Name = i.Name,
 0623                Type = MetadataPluginType.MetadataFetcher
 0624            }));
 625
 626            // Savers
 0627            list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit,
 0628            {
 0629                Name = i.Name,
 0630                Type = MetadataPluginType.MetadataSaver
 0631            }));
 0632        }
 633
 634        private void AddImagePlugins(List<MetadataPlugin> list, List<IImageProvider> imageProviders)
 635        {
 636            // Locals
 0637            list.AddRange(imageProviders.Where(i => i is ILocalImageProvider).Select(i => new MetadataPlugin
 0638            {
 0639                Name = i.Name,
 0640                Type = MetadataPluginType.LocalImageProvider
 0641            }));
 642
 643            // Fetchers
 0644            list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (i is IRemoteImageProvider)).Select(i 
 0645            {
 0646                Name = i.Name,
 0647                Type = MetadataPluginType.ImageFetcher
 0648            }));
 0649        }
 650
 651        /// <inheritdoc/>
 652        public MetadataOptions GetMetadataOptions(BaseItem item)
 1250653            => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
 654
 655        /// <inheritdoc/>
 656        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
 87657            => SaveMetadataAsync(item, updateType, _savers);
 658
 659        /// <inheritdoc/>
 660        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
 0661            => SaveMetadataAsync(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIg
 662
 663        /// <summary>
 664        /// Saves the metadata.
 665        /// </summary>
 666        /// <param name="item">The item.</param>
 667        /// <param name="updateType">Type of the update.</param>
 668        /// <param name="savers">The savers.</param>
 669        private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> saver
 670        {
 671            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 672
 673            foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)))
 674            {
 675                _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
 676
 677                if (saver is IMetadataFileSaver fileSaver)
 678                {
 679                    string path;
 680
 681                    try
 682                    {
 683                        path = fileSaver.GetSavePath(item);
 684                    }
 685                    catch (Exception ex)
 686                    {
 687                        _logger.LogError(ex, "Error in {Saver} GetSavePath", saver.Name);
 688                        continue;
 689                    }
 690
 691                    try
 692                    {
 693                        _libraryMonitor.ReportFileSystemChangeBeginning(path);
 694                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 695                    }
 696                    catch (Exception ex)
 697                    {
 698                        _logger.LogError(ex, "Error in metadata saver");
 699                    }
 700                    finally
 701                    {
 702                        _libraryMonitor.ReportFileSystemChangeComplete(path, false);
 703                    }
 704                }
 705                else
 706                {
 707                    try
 708                    {
 709                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 710                    }
 711                    catch (Exception ex)
 712                    {
 713                        _logger.LogError(ex, "Error in metadata saver");
 714                    }
 715                }
 716            }
 717        }
 718
 719        /// <summary>
 720        /// Determines whether [is saver enabled for item] [the specified saver].
 721        /// </summary>
 722        private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdat
 723        {
 1088724            var options = GetMetadataOptions(item);
 725
 726            try
 727            {
 1088728                if (!saver.IsEnabledFor(item, updateType))
 729                {
 1088730                    return false;
 731                }
 732
 0733                if (!includeDisabled)
 734                {
 0735                    if (libraryOptions.MetadataSavers is null)
 736                    {
 0737                        if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 738                        {
 0739                            return false;
 740                        }
 741
 0742                        if (!item.IsSaveLocalMetadataEnabled())
 743                        {
 0744                            if (updateType >= ItemUpdateType.MetadataEdit)
 745                            {
 746                                // Manual edit occurred
 747                                // Even if save local is off, save locally anyway if the metadata file already exists
 0748                                if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item
 749                                {
 0750                                    return false;
 751                                }
 752                            }
 753                            else
 754                            {
 755                                // Manual edit did not occur
 756                                // Since local metadata saving is disabled, consider it disabled
 0757                                return false;
 758                            }
 759                        }
 760                    }
 761                    else
 762                    {
 0763                        if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 764                        {
 0765                            return false;
 766                        }
 767                    }
 768                }
 769
 0770                return true;
 771            }
 0772            catch (Exception ex)
 773            {
 0774                _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name);
 0775                return false;
 776            }
 1088777        }
 778
 779        /// <inheritdoc/>
 780        public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TL
 781            where TItemType : BaseItem, new()
 782            where TLookupType : ItemLookupInfo
 783        {
 0784            BaseItem? referenceItem = null;
 785
 0786            if (!searchInfo.ItemId.IsEmpty())
 787            {
 0788                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
 789            }
 790
 0791            return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
 792        }
 793
 794        private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQ
 795            where TItemType : BaseItem, new()
 796            where TLookupType : ItemLookupInfo
 797        {
 798            LibraryOptions libraryOptions;
 799
 800            if (referenceItem is null)
 801            {
 802                // Give it a dummy path just so that it looks like a file system item
 803                var dummy = new TItemType
 804                {
 805                    Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 806                    ParentId = Guid.NewGuid()
 807                };
 808
 809                dummy.SetParent(new Folder());
 810
 811                referenceItem = dummy;
 812                libraryOptions = new LibraryOptions();
 813            }
 814            else
 815            {
 816                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
 817            }
 818
 819            var options = GetMetadataOptions(referenceItem);
 820
 821            var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.I
 822                .OfType<IRemoteSearchProvider<TLookupType>>();
 823
 824            if (!string.IsNullOrEmpty(searchInfo.SearchProviderName))
 825            {
 826                providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.O
 827            }
 828
 829            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
 830            {
 831                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
 832            }
 833
 834            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
 835            {
 836                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
 837            }
 838
 839            var resultList = new List<RemoteSearchResult>();
 840
 841            foreach (var provider in providers)
 842            {
 843                try
 844                {
 845                    var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwa
 846
 847                    foreach (var result in results)
 848                    {
 849                        result.SearchProviderName = provider.Name;
 850
 851                        var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.G
 852
 853                        if (existingMatch is null)
 854                        {
 855                            resultList.Add(result);
 856                        }
 857                        else
 858                        {
 859                            foreach (var providerId in result.ProviderIds)
 860                            {
 861                                existingMatch.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 862                            }
 863
 864                            if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
 865                            {
 866                                existingMatch.ImageUrl = result.ImageUrl;
 867                            }
 868                        }
 869                    }
 870                }
 871#pragma warning disable CA1031 // do not catch general exception types
 872                catch (Exception ex)
 873#pragma warning restore CA1031 // do not catch general exception types
 874                {
 875                    _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
 876                }
 877            }
 878
 879            return resultList;
 880        }
 881
 882        private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
 883        {
 0884            return _externalIds.Where(i =>
 0885            {
 0886                try
 0887                {
 0888                    return i.Supports(item);
 0889                }
 0890                catch (Exception ex)
 0891                {
 0892                    _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name);
 0893                    return false;
 0894                }
 0895            });
 896        }
 897
 898        /// <inheritdoc/>
 899        public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
 900        {
 6901            return _externalUrlProviders
 6902                .SelectMany(p => p
 6903                    .GetExternalUrls(item)
 6904                    .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
 905        }
 906
 907        /// <inheritdoc/>
 908        public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
 909        {
 0910            return GetExternalIds(item)
 0911                .Select(i => new ExternalIdInfo(
 0912                    name: i.ProviderName,
 0913                    key: i.Key,
 0914                    type: i.Type));
 915        }
 916
 917        /// <inheritdoc/>
 918        public HashSet<Guid> GetRefreshQueue()
 1919        {
 920            lock (_refreshQueueLock)
 921            {
 1922                return _refreshQueue.UnorderedItems.Select(x => x.Element.ItemId).ToHashSet();
 923            }
 1924        }
 925
 926        /// <inheritdoc/>
 927        public void OnRefreshStart(BaseItem item)
 928        {
 12929            _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
 12930            _activeRefreshes[item.Id] = 0;
 931            try
 932            {
 12933                RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 12934            }
 0935            catch (Exception ex)
 936            {
 937                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0938                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
 0939            }
 12940        }
 941
 942        /// <inheritdoc/>
 943        public void OnRefreshComplete(BaseItem item)
 944        {
 12945            _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
 12946            _activeRefreshes.TryRemove(item.Id, out _);
 947
 948            try
 949            {
 12950                RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 12951            }
 0952            catch (Exception ex)
 953            {
 954                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0955                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
 0956            }
 12957        }
 958
 959        /// <inheritdoc/>
 960        public double? GetRefreshProgress(Guid id)
 961        {
 0962            if (_activeRefreshes.TryGetValue(id, out double value))
 963            {
 0964                return value;
 965            }
 966
 0967            return null;
 968        }
 969
 970        /// <inheritdoc/>
 971        public void OnRefreshProgress(BaseItem item, double progress)
 972        {
 33973            var id = item.Id;
 33974            _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
 975
 33976            if (!_activeRefreshes.TryGetValue(id, out var current)
 33977                || progress <= current
 33978                || !_activeRefreshes.TryUpdate(id, progress, current))
 979            {
 980                // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
 0981                return;
 982            }
 983
 984            try
 985            {
 33986                RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(
 33987            }
 0988            catch (Exception ex)
 989            {
 990                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0991                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
 0992            }
 33993        }
 994
 995        /// <inheritdoc/>
 996        public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
 997        {
 0998            if (itemId.IsEmpty())
 999            {
 01000                throw new ArgumentException("Guid can't be empty", nameof(itemId));
 1001            }
 1002
 01003            if (_disposed)
 1004            {
 01005                return;
 1006            }
 1007
 01008            _refreshQueue.Enqueue((itemId, options), priority);
 1009
 1010            lock (_refreshQueueLock)
 1011            {
 01012                if (!_isProcessingRefreshQueue)
 1013                {
 01014                    _isProcessingRefreshQueue = true;
 01015                    Task.Run(StartProcessingRefreshQueue);
 1016                }
 01017            }
 01018        }
 1019
 1020        private async Task StartProcessingRefreshQueue()
 1021        {
 1022            var libraryManager = _libraryManager;
 1023
 1024            if (_disposed)
 1025            {
 1026                return;
 1027            }
 1028
 1029            var cancellationToken = _disposeCancellationTokenSource.Token;
 1030
 1031            while (_refreshQueue.TryDequeue(out var refreshItem, out _))
 1032            {
 1033                if (_disposed)
 1034                {
 1035                    return;
 1036                }
 1037
 1038                try
 1039                {
 1040                    var item = libraryManager.GetItemById(refreshItem.ItemId);
 1041                    if (item is null)
 1042                    {
 1043                        continue;
 1044                    }
 1045
 1046                    var task = item is MusicArtist artist
 1047                        ? RefreshArtist(artist, refreshItem.RefreshOptions, cancellationToken)
 1048                        : RefreshItem(item, refreshItem.RefreshOptions, cancellationToken);
 1049
 1050                    await task.ConfigureAwait(false);
 1051                }
 1052                catch (OperationCanceledException)
 1053                {
 1054                    break;
 1055                }
 1056                catch (Exception ex)
 1057                {
 1058                    _logger.LogError(ex, "Error refreshing item");
 1059                }
 1060            }
 1061
 1062            lock (_refreshQueueLock)
 1063            {
 1064                _isProcessingRefreshQueue = false;
 1065            }
 1066        }
 1067
 1068        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToke
 1069        {
 1070            await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1071
 1072            // Collection folders don't validate their children so we'll have to simulate that here
 1073            switch (item)
 1074            {
 1075                case CollectionFolder collectionFolder:
 1076                    await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(f
 1077                    break;
 1078                case Folder folder:
 1079                    await folder.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken)
 1080                    break;
 1081            }
 1082        }
 1083
 1084        private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFo
 1085        {
 1086            foreach (var child in collectionFolder.GetPhysicalFolders())
 1087            {
 1088                await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1089
 1090                await child.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken).Conf
 1091            }
 1092        }
 1093
 1094        private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellatio
 1095        {
 1096            var albums = _libraryManager
 1097                .GetItemList(new InternalItemsQuery
 1098                {
 1099                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 1100                    ArtistIds = new[] { item.Id },
 1101                    DtoOptions = new DtoOptions(false)
 1102                    {
 1103                        EnableImages = false
 1104                    }
 1105                })
 1106                .OfType<MusicAlbum>();
 1107
 1108            var musicArtists = albums
 1109                .Select(i => i.MusicArtist)
 1110                .Where(i => i is not null)
 1111                .Distinct();
 1112
 1113            var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, t
 1114
 1115            await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
 1116
 1117            try
 1118            {
 1119                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1120            }
 1121            catch (Exception ex)
 1122            {
 1123                _logger.LogError(ex, "Error refreshing library");
 1124            }
 1125        }
 1126
 1127        /// <inheritdoc/>
 1128        public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 1129        {
 01130            return RefreshItem(item, options, cancellationToken);
 1131        }
 1132
 1133        /// <inheritdoc/>
 1134        public void Dispose()
 1135        {
 911136            Dispose(true);
 911137            GC.SuppressFinalize(this);
 911138        }
 1139
 1140        /// <summary>
 1141        /// Releases unmanaged and optionally managed resources.
 1142        /// </summary>
 1143        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 1144        protected virtual void Dispose(bool disposing)
 1145        {
 911146            if (_disposed)
 1147            {
 01148                return;
 1149            }
 1150
 911151            if (disposing)
 1152            {
 911153                if (!_disposeCancellationTokenSource.IsCancellationRequested)
 1154                {
 911155                    _disposeCancellationTokenSource.Cancel();
 1156                }
 1157
 911158                _disposeCancellationTokenSource.Dispose();
 911159                _imageSaveLock.Dispose();
 1160            }
 1161
 911162            _disposed = true;
 911163        }
 1164    }
 1165}

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(MediaBrowser.Controller.Entities.BaseItem,System.IO.Stream,System.String,MediaBrowser.Model.Entities.ImageType,System.Nullable`1<System.Int32>,System.Threading.CancellationToken)
SaveImage(System.IO.Stream,System.String,System.String)
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)
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)
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>)
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)
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)
RefreshFullItem(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
Dispose()
Dispose(System.Boolean)