< 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: 137
Uncovered lines: 164
Coverable lines: 301
Total lines: 1174
Line coverage: 45.5%
Branch coverage
55%
Covered branches: 62
Total branches: 112
Branch coverage: 55.3%
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        {
 60154            var type = item.GetType();
 155
 60156            var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type))
 60157                ?? _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
 158
 60159            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
 59165            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        {
 77379            return GetImageProvidersInternal(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), re
 380        }
 381
 382        private IEnumerable<IImageProvider> GetImageProvidersInternal(BaseItem item, LibraryOptions libraryOptions, Meta
 383        {
 77384            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 77385            var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder;
 386
 77387            return _imageProviders.Where(i => CanRefreshImages(i, item, typeOptions, refreshOptions, includeDisabled))
 77388                .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name))
 77389                .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            {
 1002401                if (!provider.Supports(item))
 402                {
 849403                    return false;
 404                }
 152405            }
 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
 152412            if (includeDisabled || provider is ILocalImageProvider)
 413            {
 145414                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);
 850423        }
 424
 425        /// <inheritdoc />
 426        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
 427            where T : BaseItem
 428        {
 93429            var globalMetadataOptions = GetMetadataOptions(item);
 430
 93431            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false);
 432        }
 433
 434        /// <inheritdoc />
 435        public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
 436        {
 53437            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        {
 93443            var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadat
 93444            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 93445            var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
 446
 93447            return _metadataProviders.OfType<IMetadataProvider<T>>()
 93448                .Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInternetMetadata))
 93449                .OrderBy(i =>
 93450                    // local and remote providers will be interleaved in the final order
 93451                    // only relative order within a type matters: consumers of the list filter to one or the other
 93452                    i switch
 93453                    {
 93454                        ILocalMetadataProvider => GetConfiguredOrder(localMetadataReaderOrder, i.Name),
 93455                        IRemoteMetadataProvider => GetConfiguredOrder(metadataFetcherOrder, i.Name),
 93456                        // Default to end
 93457                        _ => int.MaxValue
 93458                    })
 93459                .ThenBy(GetDefaultOrder);
 460        }
 461
 462        private bool CanRefreshMetadata(
 463            IMetadataProvider provider,
 464            BaseItem item,
 465            TypeOptions? libraryTypeOptions,
 466            bool includeDisabled,
 467            bool forceEnableInternetMetadata)
 468        {
 160469            if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider)
 470            {
 1471                return false;
 472            }
 473
 159474            if (includeDisabled)
 475            {
 0476                return true;
 477            }
 478
 479            // If locked only allow local providers
 159480            if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider)
 481            {
 3482                return false;
 483            }
 484
 156485            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 486            {
 109487                return true;
 488            }
 489
 47490            return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
 491        }
 492
 493        private static int GetConfiguredOrder(string[] order, string providerName)
 494        {
 229495            var index = Array.IndexOf(order, providerName);
 496
 229497            if (index != -1)
 498            {
 54499                return index;
 500            }
 501
 502            // default to end
 175503            return int.MaxValue;
 504        }
 505
 506        private static int GetDefaultOrder(object provider)
 507        {
 229508            if (provider is IHasOrder hasOrder)
 509            {
 131510                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)
 1274653            => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
 654
 655        /// <inheritdoc/>
 656        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
 85657            => 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            var applicableSavers = savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)).
 673            if (applicableSavers.Count == 0)
 674            {
 675                return;
 676            }
 677
 678            foreach (var saver in applicableSavers)
 679            {
 680                _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
 681
 682                if (saver is IMetadataFileSaver fileSaver)
 683                {
 684                    string path;
 685
 686                    try
 687                    {
 688                        path = fileSaver.GetSavePath(item);
 689                    }
 690                    catch (Exception ex)
 691                    {
 692                        _logger.LogError(ex, "Error in {Saver} GetSavePath", saver.Name);
 693                        continue;
 694                    }
 695
 696                    try
 697                    {
 698                        _libraryMonitor.ReportFileSystemChangeBeginning(path);
 699                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 700                        item.DateLastSaved = DateTime.UtcNow;
 701                    }
 702                    catch (Exception ex)
 703                    {
 704                        _logger.LogError(ex, "Error in metadata saver");
 705                    }
 706                    finally
 707                    {
 708                        _libraryMonitor.ReportFileSystemChangeComplete(path, false);
 709                    }
 710                }
 711                else
 712                {
 713                    try
 714                    {
 715                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 716                        item.DateLastSaved = DateTime.UtcNow;
 717                    }
 718                    catch (Exception ex)
 719                    {
 720                        _logger.LogError(ex, "Error in metadata saver");
 721                    }
 722                }
 723            }
 724
 725            _libraryManager.CreateItem(item, null);
 726        }
 727
 728        /// <summary>
 729        /// Determines whether [is saver enabled for item] [the specified saver].
 730        /// </summary>
 731        private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdat
 732        {
 1104733            var options = GetMetadataOptions(item);
 734
 735            try
 736            {
 1104737                if (!saver.IsEnabledFor(item, updateType))
 738                {
 1104739                    return false;
 740                }
 741
 0742                if (!includeDisabled)
 743                {
 0744                    if (libraryOptions.MetadataSavers is null)
 745                    {
 0746                        if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 747                        {
 0748                            return false;
 749                        }
 750
 0751                        if (!item.IsSaveLocalMetadataEnabled())
 752                        {
 0753                            if (updateType >= ItemUpdateType.MetadataEdit)
 754                            {
 755                                // Manual edit occurred
 756                                // Even if save local is off, save locally anyway if the metadata file already exists
 0757                                if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item
 758                                {
 0759                                    return false;
 760                                }
 761                            }
 762                            else
 763                            {
 764                                // Manual edit did not occur
 765                                // Since local metadata saving is disabled, consider it disabled
 0766                                return false;
 767                            }
 768                        }
 769                    }
 770                    else
 771                    {
 0772                        if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 773                        {
 0774                            return false;
 775                        }
 776                    }
 777                }
 778
 0779                return true;
 780            }
 0781            catch (Exception ex)
 782            {
 0783                _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name);
 0784                return false;
 785            }
 1104786        }
 787
 788        /// <inheritdoc/>
 789        public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TL
 790            where TItemType : BaseItem, new()
 791            where TLookupType : ItemLookupInfo
 792        {
 0793            BaseItem? referenceItem = null;
 794
 0795            if (!searchInfo.ItemId.IsEmpty())
 796            {
 0797                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
 798            }
 799
 0800            return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
 801        }
 802
 803        private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQ
 804            where TItemType : BaseItem, new()
 805            where TLookupType : ItemLookupInfo
 806        {
 807            LibraryOptions libraryOptions;
 808
 809            if (referenceItem is null)
 810            {
 811                // Give it a dummy path just so that it looks like a file system item
 812                var dummy = new TItemType
 813                {
 814                    Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 815                    ParentId = Guid.NewGuid()
 816                };
 817
 818                dummy.SetParent(new Folder());
 819
 820                referenceItem = dummy;
 821                libraryOptions = new LibraryOptions();
 822            }
 823            else
 824            {
 825                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
 826            }
 827
 828            var options = GetMetadataOptions(referenceItem);
 829
 830            var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.I
 831                .OfType<IRemoteSearchProvider<TLookupType>>();
 832
 833            if (!string.IsNullOrEmpty(searchInfo.SearchProviderName))
 834            {
 835                providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.O
 836            }
 837
 838            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
 839            {
 840                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
 841            }
 842
 843            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
 844            {
 845                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
 846            }
 847
 848            var resultList = new List<RemoteSearchResult>();
 849
 850            foreach (var provider in providers)
 851            {
 852                try
 853                {
 854                    var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwa
 855
 856                    foreach (var result in results)
 857                    {
 858                        result.SearchProviderName = provider.Name;
 859
 860                        var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.G
 861
 862                        if (existingMatch is null)
 863                        {
 864                            resultList.Add(result);
 865                        }
 866                        else
 867                        {
 868                            foreach (var providerId in result.ProviderIds)
 869                            {
 870                                existingMatch.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 871                            }
 872
 873                            if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
 874                            {
 875                                existingMatch.ImageUrl = result.ImageUrl;
 876                            }
 877                        }
 878                    }
 879                }
 880#pragma warning disable CA1031 // do not catch general exception types
 881                catch (Exception ex)
 882#pragma warning restore CA1031 // do not catch general exception types
 883                {
 884                    _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
 885                }
 886            }
 887
 888            return resultList;
 889        }
 890
 891        private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
 892        {
 0893            return _externalIds.Where(i =>
 0894            {
 0895                try
 0896                {
 0897                    return i.Supports(item);
 0898                }
 0899                catch (Exception ex)
 0900                {
 0901                    _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name);
 0902                    return false;
 0903                }
 0904            });
 905        }
 906
 907        /// <inheritdoc/>
 908        public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
 909        {
 6910            return _externalUrlProviders
 6911                .SelectMany(p => p
 6912                    .GetExternalUrls(item)
 6913                    .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
 914        }
 915
 916        /// <inheritdoc/>
 917        public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
 918        {
 0919            return GetExternalIds(item)
 0920                .Select(i => new ExternalIdInfo(
 0921                    name: i.ProviderName,
 0922                    key: i.Key,
 0923                    type: i.Type));
 924        }
 925
 926        /// <inheritdoc/>
 927        public HashSet<Guid> GetRefreshQueue()
 1928        {
 929            lock (_refreshQueueLock)
 930            {
 1931                return _refreshQueue.UnorderedItems.Select(x => x.Element.ItemId).ToHashSet();
 932            }
 1933        }
 934
 935        /// <inheritdoc/>
 936        public void OnRefreshStart(BaseItem item)
 937        {
 11938            _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
 11939            _activeRefreshes[item.Id] = 0;
 940            try
 941            {
 11942                RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 11943            }
 0944            catch (Exception ex)
 945            {
 946                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0947                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
 0948            }
 11949        }
 950
 951        /// <inheritdoc/>
 952        public void OnRefreshComplete(BaseItem item)
 953        {
 11954            _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
 11955            _activeRefreshes.TryRemove(item.Id, out _);
 956
 957            try
 958            {
 11959                RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 11960            }
 0961            catch (Exception ex)
 962            {
 963                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0964                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
 0965            }
 11966        }
 967
 968        /// <inheritdoc/>
 969        public double? GetRefreshProgress(Guid id)
 970        {
 0971            if (_activeRefreshes.TryGetValue(id, out double value))
 972            {
 0973                return value;
 974            }
 975
 0976            return null;
 977        }
 978
 979        /// <inheritdoc/>
 980        public void OnRefreshProgress(BaseItem item, double progress)
 981        {
 45982            var id = item.Id;
 45983            _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
 984
 45985            if (!_activeRefreshes.TryGetValue(id, out var current)
 45986                || progress <= current
 45987                || !_activeRefreshes.TryUpdate(id, progress, current))
 988            {
 989                // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
 17990                return;
 991            }
 992
 993            try
 994            {
 28995                RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(
 28996            }
 0997            catch (Exception ex)
 998            {
 999                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01000                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
 01001            }
 281002        }
 1003
 1004        /// <inheritdoc/>
 1005        public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
 1006        {
 01007            if (itemId.IsEmpty())
 1008            {
 01009                throw new ArgumentException("Guid can't be empty", nameof(itemId));
 1010            }
 1011
 01012            if (_disposed)
 1013            {
 01014                return;
 1015            }
 1016
 01017            _refreshQueue.Enqueue((itemId, options), priority);
 1018
 1019            lock (_refreshQueueLock)
 1020            {
 01021                if (!_isProcessingRefreshQueue)
 1022                {
 01023                    _isProcessingRefreshQueue = true;
 01024                    Task.Run(StartProcessingRefreshQueue);
 1025                }
 01026            }
 01027        }
 1028
 1029        private async Task StartProcessingRefreshQueue()
 1030        {
 1031            var libraryManager = _libraryManager;
 1032
 1033            if (_disposed)
 1034            {
 1035                return;
 1036            }
 1037
 1038            var cancellationToken = _disposeCancellationTokenSource.Token;
 1039
 1040            while (_refreshQueue.TryDequeue(out var refreshItem, out _))
 1041            {
 1042                if (_disposed)
 1043                {
 1044                    return;
 1045                }
 1046
 1047                try
 1048                {
 1049                    var item = libraryManager.GetItemById(refreshItem.ItemId);
 1050                    if (item is null)
 1051                    {
 1052                        continue;
 1053                    }
 1054
 1055                    var task = item is MusicArtist artist
 1056                        ? RefreshArtist(artist, refreshItem.RefreshOptions, cancellationToken)
 1057                        : RefreshItem(item, refreshItem.RefreshOptions, cancellationToken);
 1058
 1059                    await task.ConfigureAwait(false);
 1060                }
 1061                catch (OperationCanceledException)
 1062                {
 1063                    break;
 1064                }
 1065                catch (Exception ex)
 1066                {
 1067                    _logger.LogError(ex, "Error refreshing item");
 1068                }
 1069            }
 1070
 1071            lock (_refreshQueueLock)
 1072            {
 1073                _isProcessingRefreshQueue = false;
 1074            }
 1075        }
 1076
 1077        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToke
 1078        {
 1079            await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1080
 1081            // Collection folders don't validate their children so we'll have to simulate that here
 1082            switch (item)
 1083            {
 1084                case CollectionFolder collectionFolder:
 1085                    await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(f
 1086                    break;
 1087                case Folder folder:
 1088                    await folder.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken)
 1089                    break;
 1090            }
 1091        }
 1092
 1093        private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFo
 1094        {
 1095            foreach (var child in collectionFolder.GetPhysicalFolders())
 1096            {
 1097                await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1098
 1099                await child.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken).Conf
 1100            }
 1101        }
 1102
 1103        private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellatio
 1104        {
 1105            var albums = _libraryManager
 1106                .GetItemList(new InternalItemsQuery
 1107                {
 1108                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 1109                    ArtistIds = new[] { item.Id },
 1110                    DtoOptions = new DtoOptions(false)
 1111                    {
 1112                        EnableImages = false
 1113                    }
 1114                })
 1115                .OfType<MusicAlbum>();
 1116
 1117            var musicArtists = albums
 1118                .Select(i => i.MusicArtist)
 1119                .Where(i => i is not null)
 1120                .Distinct();
 1121
 1122            var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, t
 1123
 1124            await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
 1125
 1126            try
 1127            {
 1128                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1129            }
 1130            catch (Exception ex)
 1131            {
 1132                _logger.LogError(ex, "Error refreshing library");
 1133            }
 1134        }
 1135
 1136        /// <inheritdoc/>
 1137        public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 1138        {
 01139            return RefreshItem(item, options, cancellationToken);
 1140        }
 1141
 1142        /// <inheritdoc/>
 1143        public void Dispose()
 1144        {
 911145            Dispose(true);
 911146            GC.SuppressFinalize(this);
 911147        }
 1148
 1149        /// <summary>
 1150        /// Releases unmanaged and optionally managed resources.
 1151        /// </summary>
 1152        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 1153        protected virtual void Dispose(bool disposing)
 1154        {
 911155            if (_disposed)
 1156            {
 01157                return;
 1158            }
 1159
 911160            if (disposing)
 1161            {
 911162                if (!_disposeCancellationTokenSource.IsCancellationRequested)
 1163                {
 911164                    _disposeCancellationTokenSource.Cancel();
 1165                }
 1166
 911167                _disposeCancellationTokenSource.Dispose();
 911168                _imageSaveLock.Dispose();
 1169            }
 1170
 911171            _disposed = true;
 911172        }
 1173    }
 1174}

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)