< 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: 1172
Line coverage: 45.5%
Branch coverage
58%
Covered branches: 65
Total branches: 112
Branch coverage: 58%
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)
 1258653            => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
 654
 655        /// <inheritdoc/>
 656        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
 83657            => 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                    }
 701                    catch (Exception ex)
 702                    {
 703                        _logger.LogError(ex, "Error in metadata saver");
 704                    }
 705                    finally
 706                    {
 707                        _libraryMonitor.ReportFileSystemChangeComplete(path, false);
 708                    }
 709                }
 710                else
 711                {
 712                    try
 713                    {
 714                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 715                    }
 716                    catch (Exception ex)
 717                    {
 718                        _logger.LogError(ex, "Error in metadata saver");
 719                    }
 720                }
 721            }
 722
 723            _libraryManager.CreateItem(item, null);
 724        }
 725
 726        /// <summary>
 727        /// Determines whether [is saver enabled for item] [the specified saver].
 728        /// </summary>
 729        private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdat
 730        {
 1088731            var options = GetMetadataOptions(item);
 732
 733            try
 734            {
 1088735                if (!saver.IsEnabledFor(item, updateType))
 736                {
 1088737                    return false;
 738                }
 739
 0740                if (!includeDisabled)
 741                {
 0742                    if (libraryOptions.MetadataSavers is null)
 743                    {
 0744                        if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 745                        {
 0746                            return false;
 747                        }
 748
 0749                        if (!item.IsSaveLocalMetadataEnabled())
 750                        {
 0751                            if (updateType >= ItemUpdateType.MetadataEdit)
 752                            {
 753                                // Manual edit occurred
 754                                // Even if save local is off, save locally anyway if the metadata file already exists
 0755                                if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item
 756                                {
 0757                                    return false;
 758                                }
 759                            }
 760                            else
 761                            {
 762                                // Manual edit did not occur
 763                                // Since local metadata saving is disabled, consider it disabled
 0764                                return false;
 765                            }
 766                        }
 767                    }
 768                    else
 769                    {
 0770                        if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 771                        {
 0772                            return false;
 773                        }
 774                    }
 775                }
 776
 0777                return true;
 778            }
 0779            catch (Exception ex)
 780            {
 0781                _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name);
 0782                return false;
 783            }
 1088784        }
 785
 786        /// <inheritdoc/>
 787        public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TL
 788            where TItemType : BaseItem, new()
 789            where TLookupType : ItemLookupInfo
 790        {
 0791            BaseItem? referenceItem = null;
 792
 0793            if (!searchInfo.ItemId.IsEmpty())
 794            {
 0795                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
 796            }
 797
 0798            return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
 799        }
 800
 801        private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQ
 802            where TItemType : BaseItem, new()
 803            where TLookupType : ItemLookupInfo
 804        {
 805            LibraryOptions libraryOptions;
 806
 807            if (referenceItem is null)
 808            {
 809                // Give it a dummy path just so that it looks like a file system item
 810                var dummy = new TItemType
 811                {
 812                    Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 813                    ParentId = Guid.NewGuid()
 814                };
 815
 816                dummy.SetParent(new Folder());
 817
 818                referenceItem = dummy;
 819                libraryOptions = new LibraryOptions();
 820            }
 821            else
 822            {
 823                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
 824            }
 825
 826            var options = GetMetadataOptions(referenceItem);
 827
 828            var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.I
 829                .OfType<IRemoteSearchProvider<TLookupType>>();
 830
 831            if (!string.IsNullOrEmpty(searchInfo.SearchProviderName))
 832            {
 833                providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.O
 834            }
 835
 836            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
 837            {
 838                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
 839            }
 840
 841            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
 842            {
 843                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
 844            }
 845
 846            var resultList = new List<RemoteSearchResult>();
 847
 848            foreach (var provider in providers)
 849            {
 850                try
 851                {
 852                    var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwa
 853
 854                    foreach (var result in results)
 855                    {
 856                        result.SearchProviderName = provider.Name;
 857
 858                        var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.G
 859
 860                        if (existingMatch is null)
 861                        {
 862                            resultList.Add(result);
 863                        }
 864                        else
 865                        {
 866                            foreach (var providerId in result.ProviderIds)
 867                            {
 868                                existingMatch.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 869                            }
 870
 871                            if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
 872                            {
 873                                existingMatch.ImageUrl = result.ImageUrl;
 874                            }
 875                        }
 876                    }
 877                }
 878#pragma warning disable CA1031 // do not catch general exception types
 879                catch (Exception ex)
 880#pragma warning restore CA1031 // do not catch general exception types
 881                {
 882                    _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
 883                }
 884            }
 885
 886            return resultList;
 887        }
 888
 889        private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
 890        {
 0891            return _externalIds.Where(i =>
 0892            {
 0893                try
 0894                {
 0895                    return i.Supports(item);
 0896                }
 0897                catch (Exception ex)
 0898                {
 0899                    _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name);
 0900                    return false;
 0901                }
 0902            });
 903        }
 904
 905        /// <inheritdoc/>
 906        public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
 907        {
 6908            return _externalUrlProviders
 6909                .SelectMany(p => p
 6910                    .GetExternalUrls(item)
 6911                    .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
 912        }
 913
 914        /// <inheritdoc/>
 915        public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
 916        {
 0917            return GetExternalIds(item)
 0918                .Select(i => new ExternalIdInfo(
 0919                    name: i.ProviderName,
 0920                    key: i.Key,
 0921                    type: i.Type));
 922        }
 923
 924        /// <inheritdoc/>
 925        public HashSet<Guid> GetRefreshQueue()
 1926        {
 927            lock (_refreshQueueLock)
 928            {
 1929                return _refreshQueue.UnorderedItems.Select(x => x.Element.ItemId).ToHashSet();
 930            }
 1931        }
 932
 933        /// <inheritdoc/>
 934        public void OnRefreshStart(BaseItem item)
 935        {
 13936            _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
 13937            _activeRefreshes[item.Id] = 0;
 938            try
 939            {
 13940                RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 13941            }
 0942            catch (Exception ex)
 943            {
 944                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0945                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
 0946            }
 13947        }
 948
 949        /// <inheritdoc/>
 950        public void OnRefreshComplete(BaseItem item)
 951        {
 13952            _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
 13953            _activeRefreshes.TryRemove(item.Id, out _);
 954
 955            try
 956            {
 13957                RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 13958            }
 0959            catch (Exception ex)
 960            {
 961                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0962                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
 0963            }
 13964        }
 965
 966        /// <inheritdoc/>
 967        public double? GetRefreshProgress(Guid id)
 968        {
 0969            if (_activeRefreshes.TryGetValue(id, out double value))
 970            {
 0971                return value;
 972            }
 973
 0974            return null;
 975        }
 976
 977        /// <inheritdoc/>
 978        public void OnRefreshProgress(BaseItem item, double progress)
 979        {
 59980            var id = item.Id;
 59981            _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
 982
 59983            if (!_activeRefreshes.TryGetValue(id, out var current)
 59984                || progress <= current
 59985                || !_activeRefreshes.TryUpdate(id, progress, current))
 986            {
 987                // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
 19988                return;
 989            }
 990
 991            try
 992            {
 40993                RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(
 40994            }
 0995            catch (Exception ex)
 996            {
 997                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0998                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
 0999            }
 401000        }
 1001
 1002        /// <inheritdoc/>
 1003        public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
 1004        {
 01005            if (itemId.IsEmpty())
 1006            {
 01007                throw new ArgumentException("Guid can't be empty", nameof(itemId));
 1008            }
 1009
 01010            if (_disposed)
 1011            {
 01012                return;
 1013            }
 1014
 01015            _refreshQueue.Enqueue((itemId, options), priority);
 1016
 1017            lock (_refreshQueueLock)
 1018            {
 01019                if (!_isProcessingRefreshQueue)
 1020                {
 01021                    _isProcessingRefreshQueue = true;
 01022                    Task.Run(StartProcessingRefreshQueue);
 1023                }
 01024            }
 01025        }
 1026
 1027        private async Task StartProcessingRefreshQueue()
 1028        {
 1029            var libraryManager = _libraryManager;
 1030
 1031            if (_disposed)
 1032            {
 1033                return;
 1034            }
 1035
 1036            var cancellationToken = _disposeCancellationTokenSource.Token;
 1037
 1038            while (_refreshQueue.TryDequeue(out var refreshItem, out _))
 1039            {
 1040                if (_disposed)
 1041                {
 1042                    return;
 1043                }
 1044
 1045                try
 1046                {
 1047                    var item = libraryManager.GetItemById(refreshItem.ItemId);
 1048                    if (item is null)
 1049                    {
 1050                        continue;
 1051                    }
 1052
 1053                    var task = item is MusicArtist artist
 1054                        ? RefreshArtist(artist, refreshItem.RefreshOptions, cancellationToken)
 1055                        : RefreshItem(item, refreshItem.RefreshOptions, cancellationToken);
 1056
 1057                    await task.ConfigureAwait(false);
 1058                }
 1059                catch (OperationCanceledException)
 1060                {
 1061                    break;
 1062                }
 1063                catch (Exception ex)
 1064                {
 1065                    _logger.LogError(ex, "Error refreshing item");
 1066                }
 1067            }
 1068
 1069            lock (_refreshQueueLock)
 1070            {
 1071                _isProcessingRefreshQueue = false;
 1072            }
 1073        }
 1074
 1075        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToke
 1076        {
 1077            await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1078
 1079            // Collection folders don't validate their children so we'll have to simulate that here
 1080            switch (item)
 1081            {
 1082                case CollectionFolder collectionFolder:
 1083                    await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(f
 1084                    break;
 1085                case Folder folder:
 1086                    await folder.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken)
 1087                    break;
 1088            }
 1089        }
 1090
 1091        private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFo
 1092        {
 1093            foreach (var child in collectionFolder.GetPhysicalFolders())
 1094            {
 1095                await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1096
 1097                await child.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken).Conf
 1098            }
 1099        }
 1100
 1101        private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellatio
 1102        {
 1103            var albums = _libraryManager
 1104                .GetItemList(new InternalItemsQuery
 1105                {
 1106                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 1107                    ArtistIds = new[] { item.Id },
 1108                    DtoOptions = new DtoOptions(false)
 1109                    {
 1110                        EnableImages = false
 1111                    }
 1112                })
 1113                .OfType<MusicAlbum>();
 1114
 1115            var musicArtists = albums
 1116                .Select(i => i.MusicArtist)
 1117                .Where(i => i is not null)
 1118                .Distinct();
 1119
 1120            var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, t
 1121
 1122            await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
 1123
 1124            try
 1125            {
 1126                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1127            }
 1128            catch (Exception ex)
 1129            {
 1130                _logger.LogError(ex, "Error refreshing library");
 1131            }
 1132        }
 1133
 1134        /// <inheritdoc/>
 1135        public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 1136        {
 01137            return RefreshItem(item, options, cancellationToken);
 1138        }
 1139
 1140        /// <inheritdoc/>
 1141        public void Dispose()
 1142        {
 911143            Dispose(true);
 911144            GC.SuppressFinalize(this);
 911145        }
 1146
 1147        /// <summary>
 1148        /// Releases unmanaged and optionally managed resources.
 1149        /// </summary>
 1150        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 1151        protected virtual void Dispose(bool disposing)
 1152        {
 911153            if (_disposed)
 1154            {
 01155                return;
 1156            }
 1157
 911158            if (disposing)
 1159            {
 911160                if (!_disposeCancellationTokenSource.IsCancellationRequested)
 1161                {
 911162                    _disposeCancellationTokenSource.Cancel();
 1163                }
 1164
 911165                _disposeCancellationTokenSource.Dispose();
 911166                _imageSaveLock.Dispose();
 1167            }
 1168
 911169            _disposed = true;
 911170        }
 1171    }
 1172}

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)