< 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: 1166
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.Globalization;
 5using System.IO;
 6using System.Linq;
 7using System.Net;
 8using System.Net.Http;
 9using System.Net.Mime;
 10using System.Runtime.ExceptionServices;
 11using System.Threading;
 12using System.Threading.Tasks;
 13using AsyncKeyedLock;
 14using Jellyfin.Data.Enums;
 15using Jellyfin.Data.Events;
 16using Jellyfin.Extensions;
 17using MediaBrowser.Common.Net;
 18using MediaBrowser.Controller;
 19using MediaBrowser.Controller.BaseItemManager;
 20using MediaBrowser.Controller.Configuration;
 21using MediaBrowser.Controller.Dto;
 22using MediaBrowser.Controller.Entities;
 23using MediaBrowser.Controller.Entities.Audio;
 24using MediaBrowser.Controller.Entities.Movies;
 25using MediaBrowser.Controller.Library;
 26using MediaBrowser.Controller.Lyrics;
 27using MediaBrowser.Controller.Providers;
 28using MediaBrowser.Controller.Subtitles;
 29using MediaBrowser.Model.Configuration;
 30using MediaBrowser.Model.Entities;
 31using MediaBrowser.Model.Extensions;
 32using MediaBrowser.Model.IO;
 33using MediaBrowser.Model.Net;
 34using MediaBrowser.Model.Providers;
 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
 9173        private IImageProvider[] _imageProviders = [];
 9174        private IMetadataService[] _metadataServices = [];
 9175        private IMetadataProvider[] _metadataProviders = [];
 9176        private IMetadataSaver[] _savers = [];
 9177        private IExternalId[] _externalIds = [];
 9178        private IExternalUrlProvider[] _externalUrlProviders = [];
 79        private bool _isProcessingRefreshQueue;
 80        private bool _disposed;
 81
 82        /// <summary>
 83        /// Initializes a new instance of the <see cref="ProviderManager"/> class.
 84        /// </summary>
 85        /// <param name="httpClientFactory">The Http client factory.</param>
 86        /// <param name="subtitleManager">The subtitle manager.</param>
 87        /// <param name="configurationManager">The configuration manager.</param>
 88        /// <param name="libraryMonitor">The library monitor.</param>
 89        /// <param name="logger">The logger.</param>
 90        /// <param name="fileSystem">The filesystem.</param>
 91        /// <param name="appPaths">The server application paths.</param>
 92        /// <param name="libraryManager">The library manager.</param>
 93        /// <param name="baseItemManager">The BaseItem manager.</param>
 94        /// <param name="lyricManager">The lyric manager.</param>
 95        /// <param name="memoryCache">The memory cache.</param>
 96        /// <param name="mediaSegmentManager">The media segment manager.</param>
 97        public ProviderManager(
 98            IHttpClientFactory httpClientFactory,
 99            ISubtitleManager subtitleManager,
 100            IServerConfigurationManager configurationManager,
 101            ILibraryMonitor libraryMonitor,
 102            ILogger<ProviderManager> logger,
 103            IFileSystem fileSystem,
 104            IServerApplicationPaths appPaths,
 105            ILibraryManager libraryManager,
 106            IBaseItemManager baseItemManager,
 107            ILyricManager lyricManager,
 108            IMemoryCache memoryCache,
 109            IMediaSegmentManager mediaSegmentManager)
 110        {
 91111            _logger = logger;
 91112            _httpClientFactory = httpClientFactory;
 91113            _configurationManager = configurationManager;
 91114            _libraryMonitor = libraryMonitor;
 91115            _fileSystem = fileSystem;
 91116            _appPaths = appPaths;
 91117            _libraryManager = libraryManager;
 91118            _subtitleManager = subtitleManager;
 91119            _baseItemManager = baseItemManager;
 91120            _lyricManager = lyricManager;
 91121            _memoryCache = memoryCache;
 91122            _mediaSegmentManager = mediaSegmentManager;
 91123        }
 124
 125        /// <inheritdoc/>
 126        public event EventHandler<GenericEventArgs<BaseItem>>? RefreshStarted;
 127
 128        /// <inheritdoc/>
 129        public event EventHandler<GenericEventArgs<BaseItem>>? RefreshCompleted;
 130
 131        /// <inheritdoc/>
 132        public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>>? RefreshProgress;
 133
 134        /// <inheritdoc/>
 135        public void AddParts(
 136            IEnumerable<IImageProvider> imageProviders,
 137            IEnumerable<IMetadataService> metadataServices,
 138            IEnumerable<IMetadataProvider> metadataProviders,
 139            IEnumerable<IMetadataSaver> metadataSavers,
 140            IEnumerable<IExternalId> externalIds,
 141            IEnumerable<IExternalUrlProvider> externalUrlProviders)
 142        {
 91143            _imageProviders = imageProviders.ToArray();
 91144            _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
 91145            _metadataProviders = metadataProviders.ToArray();
 91146            _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
 91147            _externalUrlProviders = externalUrlProviders.OrderBy(i => i.Name).ToArray();
 148
 91149            _savers = metadataSavers.ToArray();
 91150        }
 151
 152        /// <inheritdoc/>
 153        public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken c
 154        {
 59155            var type = item.GetType();
 156
 59157            var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type))
 59158                ?? _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
 159
 59160            if (service is null)
 161            {
 1162                _logger.LogError("Unable to find a metadata service for item of type {TypeName}", type.Name);
 1163                return Task.FromResult(ItemUpdateType.None);
 164            }
 165
 58166            return service.RefreshMetadata(item, options, cancellationToken);
 167        }
 168
 169        /// <inheritdoc/>
 170        public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancel
 171        {
 172            using (await _imageSaveLock.LockAsync(url, cancellationToken).ConfigureAwait(false))
 173            {
 174                if (_memoryCache.TryGetValue(url, out (string ContentType, byte[] ImageContents)? cachedValue)
 175                    && cachedValue is not null)
 176                {
 177                    var imageContents = cachedValue.Value.ImageContents;
 178                    var cacheStream = new MemoryStream(imageContents, 0, imageContents.Length, false);
 179                    await using (cacheStream.ConfigureAwait(false))
 180                    {
 181                        await SaveImage(
 182                            item,
 183                            cacheStream,
 184                            cachedValue.Value.ContentType,
 185                            type,
 186                            imageIndex,
 187                            cancellationToken).ConfigureAwait(false);
 188                        return;
 189                    }
 190                }
 191
 192                var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
 193                using var response = await httpClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
 194
 195                response.EnsureSuccessStatusCode();
 196
 197                var contentType = response.Content.Headers.ContentType?.MediaType;
 198
 199                // Workaround for tvheadend channel icons
 200                // TODO: Isolate this hack into the tvh plugin
 201                if (string.IsNullOrEmpty(contentType))
 202                {
 203                    // Special case for imagecache
 204                    if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase))
 205                    {
 206                        contentType = MediaTypeNames.Image.Png;
 207                    }
 208                }
 209
 210                // some providers don't correctly report media type, extract from url if no extension found
 211                if (contentType is null || contentType.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordinal
 212                {
 213                    // Strip query parameters from url to get actual path.
 214                    contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
 215                }
 216
 217                if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
 218                {
 219                    throw new HttpRequestException($"Request returned '{contentType}' instead of an image type", null, H
 220                }
 221
 222                var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false)
 223                var stream = new MemoryStream(responseBytes, 0, responseBytes.Length, false);
 224                await using (stream.ConfigureAwait(false))
 225                {
 226                    _memoryCache.Set(url, (contentType, responseBytes), TimeSpan.FromSeconds(10));
 227
 228                    await SaveImage(
 229                        item,
 230                        stream,
 231                        contentType,
 232                        type,
 233                        imageIndex,
 234                        cancellationToken).ConfigureAwait(false);
 235                }
 236            }
 237        }
 238
 239        /// <inheritdoc/>
 240        public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, Cancellati
 241        {
 0242            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, 
 243        }
 244
 245        /// <inheritdoc/>
 246        public async Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool
 247        {
 248            if (string.IsNullOrWhiteSpace(source))
 249            {
 250                throw new ArgumentNullException(nameof(source));
 251            }
 252
 253            try
 254            {
 255                var fileStream = AsyncFile.OpenRead(source);
 256                await new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 257                    .SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken)
 258                    .ConfigureAwait(false);
 259            }
 260            finally
 261            {
 262                try
 263                {
 264                    File.Delete(source);
 265                }
 266                catch (Exception ex)
 267                {
 268                    _logger.LogError(ex, "Source file {Source} not found or in use, skip removing", source);
 269                }
 270            }
 271        }
 272
 273        /// <inheritdoc/>
 274        public Task SaveImage(Stream source, string mimeType, string path)
 275        {
 0276            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0277                .SaveImage(source, path);
 278        }
 279
 280        /// <inheritdoc/>
 281        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, 
 282        {
 283            var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
 284
 285            if (!string.IsNullOrEmpty(query.ProviderName))
 286            {
 287                var providerName = query.ProviderName;
 288
 289                providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)
 290            }
 291
 292            if (query.ImageType is not null)
 293            {
 294                providers = providers.Where(i => i.GetSupportedImages(item).Contains(query.ImageType.Value));
 295            }
 296
 297            var preferredLanguage = item.GetPreferredMetadataLanguage();
 298
 299            var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellat
 300
 301            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 302
 303            return results.SelectMany(i => i);
 304        }
 305
 306        /// <summary>
 307        /// Gets the images.
 308        /// </summary>
 309        /// <param name="item">The item.</param>
 310        /// <param name="provider">The provider.</param>
 311        /// <param name="preferredLanguage">The preferred language.</param>
 312        /// <param name="includeAllLanguages">Whether to include all languages in results.</param>
 313        /// <param name="cancellationToken">The cancellation token.</param>
 314        /// <param name="type">The type.</param>
 315        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
 316        private async Task<IEnumerable<RemoteImageInfo>> GetImages(
 317            BaseItem item,
 318            IRemoteImageProvider provider,
 319            string preferredLanguage,
 320            bool includeAllLanguages,
 321            CancellationToken cancellationToken,
 322            ImageType? type = null)
 323        {
 324            bool hasPreferredLanguage = !string.IsNullOrWhiteSpace(preferredLanguage);
 325
 326            try
 327            {
 328                var result = await provider.GetImages(item, cancellationToken).ConfigureAwait(false);
 329
 330                if (type.HasValue)
 331                {
 332                    result = result.Where(i => i.Type == type.Value);
 333                }
 334
 335                if (!includeAllLanguages && hasPreferredLanguage)
 336                {
 337                    // Filter out languages that do not match the preferred languages.
 338                    //
 339                    // TODO: should exception case of "en" (English) eventually be removed?
 340                    result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
 341                                               string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgno
 342                                               string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
 343                }
 344
 345                return result.OrderByLanguageDescending(preferredLanguage);
 346            }
 347            catch (OperationCanceledException)
 348            {
 349                return Enumerable.Empty<RemoteImageInfo>();
 350            }
 351            catch (Exception ex)
 352            {
 353                _logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provide
 354                return Enumerable.Empty<RemoteImageInfo>();
 355            }
 356        }
 357
 358        /// <inheritdoc/>
 359        public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
 360        {
 0361            return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(it
 362        }
 363
 364        private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled)
 365        {
 0366            var options = GetMetadataOptions(item);
 0367            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 368
 0369            return GetImageProvidersInternal(
 0370                item,
 0371                libraryOptions,
 0372                options,
 0373                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0374                includeDisabled).OfType<IRemoteImageProvider>();
 375        }
 376
 377        /// <inheritdoc/>
 378        public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
 379        {
 77380            return GetImageProvidersInternal(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), re
 381        }
 382
 383        private IEnumerable<IImageProvider> GetImageProvidersInternal(BaseItem item, LibraryOptions libraryOptions, Meta
 384        {
 77385            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 77386            var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder;
 387
 77388            return _imageProviders.Where(i => CanRefreshImages(i, item, typeOptions, refreshOptions, includeDisabled))
 77389                .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name))
 77390                .ThenBy(GetDefaultOrder);
 391        }
 392
 393        private bool CanRefreshImages(
 394            IImageProvider provider,
 395            BaseItem item,
 396            TypeOptions? libraryTypeOptions,
 397            ImageRefreshOptions refreshOptions,
 398            bool includeDisabled)
 399        {
 400            try
 401            {
 1002402                if (!provider.Supports(item))
 403                {
 849404                    return false;
 405                }
 152406            }
 1407            catch (Exception ex)
 408            {
 1409                _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.Get
 1410                return false;
 411            }
 412
 152413            if (includeDisabled || provider is ILocalImageProvider)
 414            {
 145415                return true;
 416            }
 417
 7418            if (item.IsLocked && refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh)
 419            {
 1420                return false;
 421            }
 422
 6423            return _baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name);
 850424        }
 425
 426        /// <inheritdoc />
 427        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
 428            where T : BaseItem
 429        {
 93430            var globalMetadataOptions = GetMetadataOptions(item);
 431
 93432            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false);
 433        }
 434
 435        /// <inheritdoc />
 436        public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
 437        {
 53438            return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false)
 439        }
 440
 441        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryO
 442            where T : BaseItem
 443        {
 93444            var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadat
 93445            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 93446            var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
 447
 93448            return _metadataProviders.OfType<IMetadataProvider<T>>()
 93449                .Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInternetMetadata))
 93450                .OrderBy(i =>
 93451                    // local and remote providers will be interleaved in the final order
 93452                    // only relative order within a type matters: consumers of the list filter to one or the other
 93453                    i switch
 93454                    {
 93455                        ILocalMetadataProvider => GetConfiguredOrder(localMetadataReaderOrder, i.Name),
 93456                        IRemoteMetadataProvider => GetConfiguredOrder(metadataFetcherOrder, i.Name),
 93457                        // Default to end
 93458                        _ => int.MaxValue
 93459                    })
 93460                .ThenBy(GetDefaultOrder);
 461        }
 462
 463        private bool CanRefreshMetadata(
 464            IMetadataProvider provider,
 465            BaseItem item,
 466            TypeOptions? libraryTypeOptions,
 467            bool includeDisabled,
 468            bool forceEnableInternetMetadata)
 469        {
 160470            if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider)
 471            {
 1472                return false;
 473            }
 474
 159475            if (includeDisabled)
 476            {
 0477                return true;
 478            }
 479
 480            // If locked only allow local providers
 159481            if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider)
 482            {
 3483                return false;
 484            }
 485
 156486            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 487            {
 109488                return true;
 489            }
 490
 47491            return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
 492        }
 493
 494        private static int GetConfiguredOrder(string[] order, string providerName)
 495        {
 229496            var index = Array.IndexOf(order, providerName);
 497
 229498            if (index != -1)
 499            {
 54500                return index;
 501            }
 502
 503            // default to end
 175504            return int.MaxValue;
 505        }
 506
 507        private static int GetDefaultOrder(object provider)
 508        {
 229509            if (provider is IHasOrder hasOrder)
 510            {
 131511                return hasOrder.Order;
 512            }
 513
 514            // after items that want to be first (~0) but before items that want to be last (~100)
 98515            return 50;
 516        }
 517
 518        /// <inheritdoc/>
 519        public MetadataPluginSummary[] GetAllMetadataPlugins()
 520        {
 0521            return new[]
 0522            {
 0523                GetPluginSummary<Movie>(),
 0524                GetPluginSummary<BoxSet>(),
 0525                GetPluginSummary<Book>(),
 0526                GetPluginSummary<Series>(),
 0527                GetPluginSummary<Season>(),
 0528                GetPluginSummary<Episode>(),
 0529                GetPluginSummary<MusicAlbum>(),
 0530                GetPluginSummary<MusicArtist>(),
 0531                GetPluginSummary<Audio>(),
 0532                GetPluginSummary<AudioBook>(),
 0533                GetPluginSummary<Studio>(),
 0534                GetPluginSummary<MusicVideo>(),
 0535                GetPluginSummary<Video>()
 0536            };
 537        }
 538
 539        private MetadataPluginSummary GetPluginSummary<T>()
 540            where T : BaseItem, new()
 541        {
 542            // Give it a dummy path just so that it looks like a file system item
 0543            var dummy = new T
 0544            {
 0545                Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0546                ParentId = Guid.NewGuid()
 0547            };
 548
 0549            var options = GetMetadataOptions(dummy);
 550
 0551            var summary = new MetadataPluginSummary
 0552            {
 0553                ItemType = typeof(T).Name
 0554            };
 555
 0556            var libraryOptions = new LibraryOptions();
 557
 0558            var imageProviders = GetImageProvidersInternal(
 0559                dummy,
 0560                libraryOptions,
 0561                options,
 0562                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0563                true).ToList();
 564
 0565            var pluginList = summary.Plugins.ToList();
 566
 0567            AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
 0568            AddImagePlugins(pluginList, imageProviders);
 569
 570            // Subtitle fetchers
 0571            var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
 0572            pluginList.AddRange(subtitleProviders.Select(i => new MetadataPlugin
 0573            {
 0574                Name = i.Name,
 0575                Type = MetadataPluginType.SubtitleFetcher
 0576            }));
 577
 578            // Lyric fetchers
 0579            var lyricProviders = _lyricManager.GetSupportedProviders(dummy);
 0580            pluginList.AddRange(lyricProviders.Select(i => new MetadataPlugin
 0581            {
 0582                Name = i.Name,
 0583                Type = MetadataPluginType.LyricFetcher
 0584            }));
 585
 586            // Media segment providers
 0587            var mediaSegmentProviders = _mediaSegmentManager.GetSupportedProviders(dummy);
 0588            pluginList.AddRange(mediaSegmentProviders.Select(i => new MetadataPlugin
 0589            {
 0590                Name = i.Name,
 0591                Type = MetadataPluginType.MediaSegmentProvider
 0592            }));
 593
 0594            summary.Plugins = pluginList.ToArray();
 595
 0596            var supportedImageTypes = imageProviders.OfType<IRemoteImageProvider>()
 0597                .SelectMany(i => i.GetSupportedImages(dummy))
 0598                .ToList();
 599
 0600            supportedImageTypes.AddRange(imageProviders.OfType<IDynamicImageProvider>()
 0601                .SelectMany(i => i.GetSupportedImages(dummy)));
 602
 0603            summary.SupportedImageTypes = supportedImageTypes.Distinct().ToArray();
 604
 0605            return summary;
 606        }
 607
 608        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, LibraryOptions libraryOptions, MetadataOpt
 609            where T : BaseItem
 610        {
 0611            var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true).ToList();
 612
 613            // Locals
 0614            list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin
 0615            {
 0616                Name = i.Name,
 0617                Type = MetadataPluginType.LocalMetadataProvider
 0618            }));
 619
 620            // Fetchers
 0621            list.AddRange(providers.Where(i => i is IRemoteMetadataProvider).Select(i => new MetadataPlugin
 0622            {
 0623                Name = i.Name,
 0624                Type = MetadataPluginType.MetadataFetcher
 0625            }));
 626
 627            // Savers
 0628            list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit,
 0629            {
 0630                Name = i.Name,
 0631                Type = MetadataPluginType.MetadataSaver
 0632            }));
 0633        }
 634
 635        private void AddImagePlugins(List<MetadataPlugin> list, List<IImageProvider> imageProviders)
 636        {
 637            // Locals
 0638            list.AddRange(imageProviders.Where(i => i is ILocalImageProvider).Select(i => new MetadataPlugin
 0639            {
 0640                Name = i.Name,
 0641                Type = MetadataPluginType.LocalImageProvider
 0642            }));
 643
 644            // Fetchers
 0645            list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (i is IRemoteImageProvider)).Select(i 
 0646            {
 0647                Name = i.Name,
 0648                Type = MetadataPluginType.ImageFetcher
 0649            }));
 0650        }
 651
 652        /// <inheritdoc/>
 653        public MetadataOptions GetMetadataOptions(BaseItem item)
 1218654            => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
 655
 656        /// <inheritdoc/>
 657        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
 78658            => SaveMetadataAsync(item, updateType, _savers);
 659
 660        /// <inheritdoc/>
 661        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
 0662            => SaveMetadataAsync(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIg
 663
 664        /// <summary>
 665        /// Saves the metadata.
 666        /// </summary>
 667        /// <param name="item">The item.</param>
 668        /// <param name="updateType">Type of the update.</param>
 669        /// <param name="savers">The savers.</param>
 670        private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> saver
 671        {
 672            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 673
 674            foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)))
 675            {
 676                _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
 677
 678                if (saver is IMetadataFileSaver fileSaver)
 679                {
 680                    string path;
 681
 682                    try
 683                    {
 684                        path = fileSaver.GetSavePath(item);
 685                    }
 686                    catch (Exception ex)
 687                    {
 688                        _logger.LogError(ex, "Error in {Saver} GetSavePath", saver.Name);
 689                        continue;
 690                    }
 691
 692                    try
 693                    {
 694                        _libraryMonitor.ReportFileSystemChangeBeginning(path);
 695                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 696                    }
 697                    catch (Exception ex)
 698                    {
 699                        _logger.LogError(ex, "Error in metadata saver");
 700                    }
 701                    finally
 702                    {
 703                        _libraryMonitor.ReportFileSystemChangeComplete(path, false);
 704                    }
 705                }
 706                else
 707                {
 708                    try
 709                    {
 710                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 711                    }
 712                    catch (Exception ex)
 713                    {
 714                        _logger.LogError(ex, "Error in metadata saver");
 715                    }
 716                }
 717            }
 718        }
 719
 720        /// <summary>
 721        /// Determines whether [is saver enabled for item] [the specified saver].
 722        /// </summary>
 723        private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdat
 724        {
 1048725            var options = GetMetadataOptions(item);
 726
 727            try
 728            {
 1048729                if (!saver.IsEnabledFor(item, updateType))
 730                {
 1048731                    return false;
 732                }
 733
 0734                if (!includeDisabled)
 735                {
 0736                    if (libraryOptions.MetadataSavers is null)
 737                    {
 0738                        if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 739                        {
 0740                            return false;
 741                        }
 742
 0743                        if (!item.IsSaveLocalMetadataEnabled())
 744                        {
 0745                            if (updateType >= ItemUpdateType.MetadataEdit)
 746                            {
 747                                // Manual edit occurred
 748                                // Even if save local is off, save locally anyway if the metadata file already exists
 0749                                if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item
 750                                {
 0751                                    return false;
 752                                }
 753                            }
 754                            else
 755                            {
 756                                // Manual edit did not occur
 757                                // Since local metadata saving is disabled, consider it disabled
 0758                                return false;
 759                            }
 760                        }
 761                    }
 762                    else
 763                    {
 0764                        if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 765                        {
 0766                            return false;
 767                        }
 768                    }
 769                }
 770
 0771                return true;
 772            }
 0773            catch (Exception ex)
 774            {
 0775                _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name);
 0776                return false;
 777            }
 1048778        }
 779
 780        /// <inheritdoc/>
 781        public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TL
 782            where TItemType : BaseItem, new()
 783            where TLookupType : ItemLookupInfo
 784        {
 0785            BaseItem? referenceItem = null;
 786
 0787            if (!searchInfo.ItemId.IsEmpty())
 788            {
 0789                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
 790            }
 791
 0792            return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
 793        }
 794
 795        private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQ
 796            where TItemType : BaseItem, new()
 797            where TLookupType : ItemLookupInfo
 798        {
 799            LibraryOptions libraryOptions;
 800
 801            if (referenceItem is null)
 802            {
 803                // Give it a dummy path just so that it looks like a file system item
 804                var dummy = new TItemType
 805                {
 806                    Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 807                    ParentId = Guid.NewGuid()
 808                };
 809
 810                dummy.SetParent(new Folder());
 811
 812                referenceItem = dummy;
 813                libraryOptions = new LibraryOptions();
 814            }
 815            else
 816            {
 817                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
 818            }
 819
 820            var options = GetMetadataOptions(referenceItem);
 821
 822            var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.I
 823                .OfType<IRemoteSearchProvider<TLookupType>>();
 824
 825            if (!string.IsNullOrEmpty(searchInfo.SearchProviderName))
 826            {
 827                providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.O
 828            }
 829
 830            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
 831            {
 832                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
 833            }
 834
 835            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
 836            {
 837                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
 838            }
 839
 840            var resultList = new List<RemoteSearchResult>();
 841
 842            foreach (var provider in providers)
 843            {
 844                try
 845                {
 846                    var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwa
 847
 848                    foreach (var result in results)
 849                    {
 850                        result.SearchProviderName = provider.Name;
 851
 852                        var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.G
 853
 854                        if (existingMatch is null)
 855                        {
 856                            resultList.Add(result);
 857                        }
 858                        else
 859                        {
 860                            foreach (var providerId in result.ProviderIds)
 861                            {
 862                                existingMatch.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 863                            }
 864
 865                            if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
 866                            {
 867                                existingMatch.ImageUrl = result.ImageUrl;
 868                            }
 869                        }
 870                    }
 871                }
 872#pragma warning disable CA1031 // do not catch general exception types
 873                catch (Exception ex)
 874#pragma warning restore CA1031 // do not catch general exception types
 875                {
 876                    _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
 877                }
 878            }
 879
 880            return resultList;
 881        }
 882
 883        private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
 884        {
 0885            return _externalIds.Where(i =>
 0886            {
 0887                try
 0888                {
 0889                    return i.Supports(item);
 0890                }
 0891                catch (Exception ex)
 0892                {
 0893                    _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name);
 0894                    return false;
 0895                }
 0896            });
 897        }
 898
 899        /// <inheritdoc/>
 900        public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
 901        {
 6902            return _externalUrlProviders
 6903                .SelectMany(p => p
 6904                    .GetExternalUrls(item)
 6905                    .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
 906        }
 907
 908        /// <inheritdoc/>
 909        public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
 910        {
 0911            return GetExternalIds(item)
 0912                .Select(i => new ExternalIdInfo(
 0913                    name: i.ProviderName,
 0914                    key: i.Key,
 0915                    type: i.Type));
 916        }
 917
 918        /// <inheritdoc/>
 919        public HashSet<Guid> GetRefreshQueue()
 1920        {
 921            lock (_refreshQueueLock)
 922            {
 1923                return _refreshQueue.UnorderedItems.Select(x => x.Element.ItemId).ToHashSet();
 924            }
 1925        }
 926
 927        /// <inheritdoc/>
 928        public void OnRefreshStart(BaseItem item)
 929        {
 11930            _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
 11931            _activeRefreshes[item.Id] = 0;
 932            try
 933            {
 11934                RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 11935            }
 0936            catch (Exception ex)
 937            {
 938                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0939                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
 0940            }
 11941        }
 942
 943        /// <inheritdoc/>
 944        public void OnRefreshComplete(BaseItem item)
 945        {
 11946            _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
 11947            _activeRefreshes.TryRemove(item.Id, out _);
 948
 949            try
 950            {
 11951                RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 11952            }
 0953            catch (Exception ex)
 954            {
 955                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0956                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
 0957            }
 11958        }
 959
 960        /// <inheritdoc/>
 961        public double? GetRefreshProgress(Guid id)
 962        {
 0963            if (_activeRefreshes.TryGetValue(id, out double value))
 964            {
 0965                return value;
 966            }
 967
 0968            return null;
 969        }
 970
 971        /// <inheritdoc/>
 972        public void OnRefreshProgress(BaseItem item, double progress)
 973        {
 30974            var id = item.Id;
 30975            _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
 976
 30977            if (!_activeRefreshes.TryGetValue(id, out var current)
 30978                || progress <= current
 30979                || !_activeRefreshes.TryUpdate(id, progress, current))
 980            {
 981                // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
 0982                return;
 983            }
 984
 985            try
 986            {
 30987                RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(
 30988            }
 0989            catch (Exception ex)
 990            {
 991                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0992                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
 0993            }
 30994        }
 995
 996        /// <inheritdoc/>
 997        public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
 998        {
 0999            if (itemId.IsEmpty())
 1000            {
 01001                throw new ArgumentException("Guid can't be empty", nameof(itemId));
 1002            }
 1003
 01004            if (_disposed)
 1005            {
 01006                return;
 1007            }
 1008
 01009            _refreshQueue.Enqueue((itemId, options), priority);
 1010
 1011            lock (_refreshQueueLock)
 1012            {
 01013                if (!_isProcessingRefreshQueue)
 1014                {
 01015                    _isProcessingRefreshQueue = true;
 01016                    Task.Run(StartProcessingRefreshQueue);
 1017                }
 01018            }
 01019        }
 1020
 1021        private async Task StartProcessingRefreshQueue()
 1022        {
 1023            var libraryManager = _libraryManager;
 1024
 1025            if (_disposed)
 1026            {
 1027                return;
 1028            }
 1029
 1030            var cancellationToken = _disposeCancellationTokenSource.Token;
 1031
 1032            while (_refreshQueue.TryDequeue(out var refreshItem, out _))
 1033            {
 1034                if (_disposed)
 1035                {
 1036                    return;
 1037                }
 1038
 1039                try
 1040                {
 1041                    var item = libraryManager.GetItemById(refreshItem.ItemId);
 1042                    if (item is null)
 1043                    {
 1044                        continue;
 1045                    }
 1046
 1047                    var task = item is MusicArtist artist
 1048                        ? RefreshArtist(artist, refreshItem.RefreshOptions, cancellationToken)
 1049                        : RefreshItem(item, refreshItem.RefreshOptions, cancellationToken);
 1050
 1051                    await task.ConfigureAwait(false);
 1052                }
 1053                catch (OperationCanceledException)
 1054                {
 1055                    break;
 1056                }
 1057                catch (Exception ex)
 1058                {
 1059                    _logger.LogError(ex, "Error refreshing item");
 1060                }
 1061            }
 1062
 1063            lock (_refreshQueueLock)
 1064            {
 1065                _isProcessingRefreshQueue = false;
 1066            }
 1067        }
 1068
 1069        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToke
 1070        {
 1071            await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1072
 1073            // Collection folders don't validate their children so we'll have to simulate that here
 1074            switch (item)
 1075            {
 1076                case CollectionFolder collectionFolder:
 1077                    await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(f
 1078                    break;
 1079                case Folder folder:
 1080                    await folder.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken)
 1081                    break;
 1082            }
 1083        }
 1084
 1085        private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFo
 1086        {
 1087            foreach (var child in collectionFolder.GetPhysicalFolders())
 1088            {
 1089                await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1090
 1091                await child.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken).Conf
 1092            }
 1093        }
 1094
 1095        private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellatio
 1096        {
 1097            var albums = _libraryManager
 1098                .GetItemList(new InternalItemsQuery
 1099                {
 1100                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 1101                    ArtistIds = new[] { item.Id },
 1102                    DtoOptions = new DtoOptions(false)
 1103                    {
 1104                        EnableImages = false
 1105                    }
 1106                })
 1107                .OfType<MusicAlbum>();
 1108
 1109            var musicArtists = albums
 1110                .Select(i => i.MusicArtist)
 1111                .Where(i => i is not null)
 1112                .Distinct();
 1113
 1114            var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, t
 1115
 1116            await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
 1117
 1118            try
 1119            {
 1120                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1121            }
 1122            catch (Exception ex)
 1123            {
 1124                _logger.LogError(ex, "Error refreshing library");
 1125            }
 1126        }
 1127
 1128        /// <inheritdoc/>
 1129        public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 1130        {
 01131            return RefreshItem(item, options, cancellationToken);
 1132        }
 1133
 1134        /// <inheritdoc/>
 1135        public void Dispose()
 1136        {
 911137            Dispose(true);
 911138            GC.SuppressFinalize(this);
 911139        }
 1140
 1141        /// <summary>
 1142        /// Releases unmanaged and optionally managed resources.
 1143        /// </summary>
 1144        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 1145        protected virtual void Dispose(bool disposing)
 1146        {
 911147            if (_disposed)
 1148            {
 01149                return;
 1150            }
 1151
 911152            if (disposing)
 1153            {
 911154                if (!_disposeCancellationTokenSource.IsCancellationRequested)
 1155                {
 911156                    _disposeCancellationTokenSource.Cancel();
 1157                }
 1158
 911159                _disposeCancellationTokenSource.Dispose();
 911160                _imageSaveLock.Dispose();
 1161            }
 1162
 911163            _disposed = true;
 911164        }
 1165    }
 1166}

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