< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.ProviderManager
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/ProviderManager.cs
Line coverage
51%
Covered lines: 169
Uncovered lines: 161
Coverable lines: 330
Total lines: 1187
Line coverage: 51.2%
Branch coverage
51%
Covered branches: 59
Total branches: 114
Branch coverage: 51.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Manager/ProviderManager.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Globalization;
 5using System.IO;
 6using System.Linq;
 7using System.Net;
 8using System.Net.Http;
 9using System.Net.Mime;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using AsyncKeyedLock;
 13using Jellyfin.Data.Enums;
 14using Jellyfin.Data.Events;
 15using Jellyfin.Extensions;
 16using MediaBrowser.Common.Net;
 17using MediaBrowser.Controller;
 18using MediaBrowser.Controller.BaseItemManager;
 19using MediaBrowser.Controller.Configuration;
 20using MediaBrowser.Controller.Dto;
 21using MediaBrowser.Controller.Entities;
 22using MediaBrowser.Controller.Entities.Audio;
 23using MediaBrowser.Controller.Entities.Movies;
 24using MediaBrowser.Controller.Library;
 25using MediaBrowser.Controller.Lyrics;
 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    {
 9250        private readonly object _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;
 9261        private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new();
 9262        private readonly CancellationTokenSource _disposeCancellationTokenSource = new();
 9263        private readonly PriorityQueue<(Guid ItemId, MetadataRefreshOptions RefreshOptions), RefreshPriority> _refreshQu
 64        private readonly IMemoryCache _memoryCache;
 65        private readonly IMediaSegmentManager _mediaSegmentManager;
 9266        private readonly AsyncKeyedLocker<string> _imageSaveLock = new(o =>
 9267        {
 9268            o.PoolSize = 20;
 9269            o.PoolInitialFill = 1;
 9270        });
 71
 9272        private IImageProvider[] _imageProviders = [];
 9273        private IMetadataService[] _metadataServices = [];
 9274        private IMetadataProvider[] _metadataProviders = [];
 9275        private IMetadataSaver[] _savers = [];
 9276        private IExternalId[] _externalIds = [];
 9277        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        {
 92110            _logger = logger;
 92111            _httpClientFactory = httpClientFactory;
 92112            _configurationManager = configurationManager;
 92113            _libraryMonitor = libraryMonitor;
 92114            _fileSystem = fileSystem;
 92115            _appPaths = appPaths;
 92116            _libraryManager = libraryManager;
 92117            _subtitleManager = subtitleManager;
 92118            _baseItemManager = baseItemManager;
 92119            _lyricManager = lyricManager;
 92120            _memoryCache = memoryCache;
 92121            _mediaSegmentManager = mediaSegmentManager;
 92122        }
 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        {
 92142            _imageProviders = imageProviders.ToArray();
 92143            _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
 92144            _metadataProviders = metadataProviders.ToArray();
 92145            _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
 92146            _externalUrlProviders = externalUrlProviders.OrderBy(i => i.Name).ToArray();
 147
 92148            _savers = metadataSavers.ToArray();
 92149        }
 150
 151        /// <inheritdoc/>
 152        public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken c
 153        {
 52154            var type = item.GetType();
 155
 52156            var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type))
 52157                ?? _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
 158
 52159            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
 51165            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                    if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase))
 203                    {
 204                        contentType = MediaTypeNames.Image.Png;
 205                    }
 206                    else
 207                    {
 208                        throw new HttpRequestException("Invalid image received: contentType not set.", null, response.St
 209                    }
 210                }
 211
 212                // TVDb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons...
 213                if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase))
 214                {
 215                    throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound);
 216                }
 217
 218                // some iptv/epg providers don't correctly report media type, extract from url if no extension found
 219                if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType)))
 220                {
 221                    // Strip query parameters from url to get actual path.
 222                    contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
 223                }
 224
 225                if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
 226                {
 227                    throw new HttpRequestException($"Request returned {contentType} instead of an image type", null, Htt
 228                }
 229
 230                var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false)
 231                var stream = new MemoryStream(responseBytes, 0, responseBytes.Length, false);
 232                await using (stream.ConfigureAwait(false))
 233                {
 234                    _memoryCache.Set(url, (contentType, responseBytes), TimeSpan.FromSeconds(10));
 235
 236                    await SaveImage(
 237                        item,
 238                        stream,
 239                        contentType,
 240                        type,
 241                        imageIndex,
 242                        cancellationToken).ConfigureAwait(false);
 243                }
 244            }
 245        }
 246
 247        /// <inheritdoc/>
 248        public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, Cancellati
 249        {
 0250            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, 
 251        }
 252
 253        /// <inheritdoc/>
 254        public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? save
 255        {
 0256            if (string.IsNullOrWhiteSpace(source))
 257            {
 0258                throw new ArgumentNullException(nameof(source));
 259            }
 260
 0261            var fileStream = AsyncFile.OpenRead(source);
 0262            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStre
 263        }
 264
 265        /// <inheritdoc/>
 266        public Task SaveImage(Stream source, string mimeType, string path)
 267        {
 0268            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 0269                .SaveImage(source, path);
 270        }
 271
 272        /// <inheritdoc/>
 273        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, 
 274        {
 275            var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
 276
 277            if (!string.IsNullOrEmpty(query.ProviderName))
 278            {
 279                var providerName = query.ProviderName;
 280
 281                providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)
 282            }
 283
 284            if (query.ImageType is not null)
 285            {
 286                providers = providers.Where(i => i.GetSupportedImages(item).Contains(query.ImageType.Value));
 287            }
 288
 289            var preferredLanguage = item.GetPreferredMetadataLanguage();
 290
 291            var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellat
 292
 293            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
 294
 295            return results.SelectMany(i => i);
 296        }
 297
 298        /// <summary>
 299        /// Gets the images.
 300        /// </summary>
 301        /// <param name="item">The item.</param>
 302        /// <param name="provider">The provider.</param>
 303        /// <param name="preferredLanguage">The preferred language.</param>
 304        /// <param name="includeAllLanguages">Whether to include all languages in results.</param>
 305        /// <param name="cancellationToken">The cancellation token.</param>
 306        /// <param name="type">The type.</param>
 307        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
 308        private async Task<IEnumerable<RemoteImageInfo>> GetImages(
 309            BaseItem item,
 310            IRemoteImageProvider provider,
 311            string preferredLanguage,
 312            bool includeAllLanguages,
 313            CancellationToken cancellationToken,
 314            ImageType? type = null)
 315        {
 316            bool hasPreferredLanguage = !string.IsNullOrWhiteSpace(preferredLanguage);
 317
 318            try
 319            {
 320                var result = await provider.GetImages(item, cancellationToken).ConfigureAwait(false);
 321
 322                if (type.HasValue)
 323                {
 324                    result = result.Where(i => i.Type == type.Value);
 325                }
 326
 327                if (!includeAllLanguages && hasPreferredLanguage)
 328                {
 329                    // Filter out languages that do not match the preferred languages.
 330                    //
 331                    // TODO: should exception case of "en" (English) eventually be removed?
 332                    result = result.Where(i => string.IsNullOrWhiteSpace(i.Language) ||
 333                                               string.Equals(preferredLanguage, i.Language, StringComparison.OrdinalIgno
 334                                               string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
 335                }
 336
 337                return result.OrderByLanguageDescending(preferredLanguage);
 338            }
 339            catch (OperationCanceledException)
 340            {
 341                return Enumerable.Empty<RemoteImageInfo>();
 342            }
 343            catch (Exception ex)
 344            {
 345                _logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provide
 346                return Enumerable.Empty<RemoteImageInfo>();
 347            }
 348        }
 349
 350        /// <inheritdoc/>
 351        public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
 352        {
 0353            return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(it
 354        }
 355
 356        private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(BaseItem item, bool includeDisabled)
 357        {
 0358            var options = GetMetadataOptions(item);
 0359            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 360
 0361            return GetImageProvidersInternal(
 0362                item,
 0363                libraryOptions,
 0364                options,
 0365                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0366                includeDisabled).OfType<IRemoteImageProvider>();
 367        }
 368
 369        /// <inheritdoc/>
 370        public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
 371        {
 70372            return GetImageProvidersInternal(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), re
 373        }
 374
 375        private IEnumerable<IImageProvider> GetImageProvidersInternal(BaseItem item, LibraryOptions libraryOptions, Meta
 376        {
 70377            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 70378            var fetcherOrder = typeOptions?.ImageFetcherOrder ?? options.ImageFetcherOrder;
 379
 70380            return _imageProviders.Where(i => CanRefreshImages(i, item, typeOptions, refreshOptions, includeDisabled))
 70381                .OrderBy(i => GetConfiguredOrder(fetcherOrder, i.Name))
 70382                .ThenBy(GetDefaultOrder);
 383        }
 384
 385        private bool CanRefreshImages(
 386            IImageProvider provider,
 387            BaseItem item,
 388            TypeOptions? libraryTypeOptions,
 389            ImageRefreshOptions refreshOptions,
 390            bool includeDisabled)
 391        {
 392            try
 393            {
 876394                if (!provider.Supports(item))
 395                {
 737396                    return false;
 397                }
 138398            }
 1399            catch (Exception ex)
 400            {
 1401                _logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.Get
 1402                return false;
 403            }
 404
 138405            if (includeDisabled || provider is ILocalImageProvider)
 406            {
 131407                return true;
 408            }
 409
 7410            if (item.IsLocked && refreshOptions.ImageRefreshMode != MetadataRefreshMode.FullRefresh)
 411            {
 1412                return false;
 413            }
 414
 6415            return _baseItemManager.IsImageFetcherEnabled(item, libraryTypeOptions, provider.Name);
 738416        }
 417
 418        /// <inheritdoc />
 419        public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
 420            where T : BaseItem
 421        {
 86422            var globalMetadataOptions = GetMetadataOptions(item);
 423
 86424            return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false);
 425        }
 426
 427        /// <inheritdoc />
 428        public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
 429        {
 46430            return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false)
 431        }
 432
 433        private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryO
 434            where T : BaseItem
 435        {
 86436            var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadat
 86437            var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
 86438            var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
 439
 86440            return _metadataProviders.OfType<IMetadataProvider<T>>()
 86441                .Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInternetMetadata))
 86442                .OrderBy(i =>
 86443                    // local and remote providers will be interleaved in the final order
 86444                    // only relative order within a type matters: consumers of the list filter to one or the other
 86445                    i switch
 86446                    {
 86447                        ILocalMetadataProvider => GetConfiguredOrder(localMetadataReaderOrder, i.Name),
 86448                        IRemoteMetadataProvider => GetConfiguredOrder(metadataFetcherOrder, i.Name),
 86449                        // Default to end
 86450                        _ => int.MaxValue
 86451                    })
 86452                .ThenBy(GetDefaultOrder);
 453        }
 454
 455        private bool CanRefreshMetadata(
 456            IMetadataProvider provider,
 457            BaseItem item,
 458            TypeOptions? libraryTypeOptions,
 459            bool includeDisabled,
 460            bool forceEnableInternetMetadata)
 461        {
 153462            if (!item.SupportsLocalMetadata && provider is ILocalMetadataProvider)
 463            {
 1464                return false;
 465            }
 466
 152467            if (includeDisabled)
 468            {
 0469                return true;
 470            }
 471
 472            // If locked only allow local providers
 152473            if (item.IsLocked && provider is not ILocalMetadataProvider && provider is not IForcedProvider)
 474            {
 3475                return false;
 476            }
 477
 149478            if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider)
 479            {
 102480                return true;
 481            }
 482
 47483            return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
 484        }
 485
 486        private static int GetConfiguredOrder(string[] order, string providerName)
 487        {
 232488            var index = Array.IndexOf(order, providerName);
 489
 232490            if (index != -1)
 491            {
 54492                return index;
 493            }
 494
 495            // default to end
 178496            return int.MaxValue;
 497        }
 498
 499        private static int GetDefaultOrder(object provider)
 500        {
 283501            if (provider is IHasOrder hasOrder)
 502            {
 163503                return hasOrder.Order;
 504            }
 505
 506            // after items that want to be first (~0) but before items that want to be last (~100)
 120507            return 50;
 508        }
 509
 510        /// <inheritdoc/>
 511        public MetadataPluginSummary[] GetAllMetadataPlugins()
 512        {
 0513            return new[]
 0514            {
 0515                GetPluginSummary<Movie>(),
 0516                GetPluginSummary<BoxSet>(),
 0517                GetPluginSummary<Book>(),
 0518                GetPluginSummary<Series>(),
 0519                GetPluginSummary<Season>(),
 0520                GetPluginSummary<Episode>(),
 0521                GetPluginSummary<MusicAlbum>(),
 0522                GetPluginSummary<MusicArtist>(),
 0523                GetPluginSummary<Audio>(),
 0524                GetPluginSummary<AudioBook>(),
 0525                GetPluginSummary<Studio>(),
 0526                GetPluginSummary<MusicVideo>(),
 0527                GetPluginSummary<Video>()
 0528            };
 529        }
 530
 531        private MetadataPluginSummary GetPluginSummary<T>()
 532            where T : BaseItem, new()
 533        {
 534            // Give it a dummy path just so that it looks like a file system item
 0535            var dummy = new T
 0536            {
 0537                Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 0538                ParentId = Guid.NewGuid()
 0539            };
 540
 0541            var options = GetMetadataOptions(dummy);
 542
 0543            var summary = new MetadataPluginSummary
 0544            {
 0545                ItemType = typeof(T).Name
 0546            };
 547
 0548            var libraryOptions = new LibraryOptions();
 549
 0550            var imageProviders = GetImageProvidersInternal(
 0551                dummy,
 0552                libraryOptions,
 0553                options,
 0554                new ImageRefreshOptions(new DirectoryService(_fileSystem)),
 0555                true).ToList();
 556
 0557            var pluginList = summary.Plugins.ToList();
 558
 0559            AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
 0560            AddImagePlugins(pluginList, imageProviders);
 561
 562            // Subtitle fetchers
 0563            var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
 0564            pluginList.AddRange(subtitleProviders.Select(i => new MetadataPlugin
 0565            {
 0566                Name = i.Name,
 0567                Type = MetadataPluginType.SubtitleFetcher
 0568            }));
 569
 570            // Lyric fetchers
 0571            var lyricProviders = _lyricManager.GetSupportedProviders(dummy);
 0572            pluginList.AddRange(lyricProviders.Select(i => new MetadataPlugin
 0573            {
 0574                Name = i.Name,
 0575                Type = MetadataPluginType.LyricFetcher
 0576            }));
 577
 578            // Media segment providers
 0579            var mediaSegmentProviders = _mediaSegmentManager.GetSupportedProviders(dummy);
 0580            pluginList.AddRange(mediaSegmentProviders.Select(i => new MetadataPlugin
 0581            {
 0582                Name = i.Name,
 0583                Type = MetadataPluginType.MediaSegmentProvider
 0584            }));
 585
 0586            summary.Plugins = pluginList.ToArray();
 587
 0588            var supportedImageTypes = imageProviders.OfType<IRemoteImageProvider>()
 0589                .SelectMany(i => i.GetSupportedImages(dummy))
 0590                .ToList();
 591
 0592            supportedImageTypes.AddRange(imageProviders.OfType<IDynamicImageProvider>()
 0593                .SelectMany(i => i.GetSupportedImages(dummy)));
 594
 0595            summary.SupportedImageTypes = supportedImageTypes.Distinct().ToArray();
 596
 0597            return summary;
 598        }
 599
 600        private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, LibraryOptions libraryOptions, MetadataOpt
 601            where T : BaseItem
 602        {
 0603            var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true).ToList();
 604
 605            // Locals
 0606            list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin
 0607            {
 0608                Name = i.Name,
 0609                Type = MetadataPluginType.LocalMetadataProvider
 0610            }));
 611
 612            // Fetchers
 0613            list.AddRange(providers.Where(i => i is IRemoteMetadataProvider).Select(i => new MetadataPlugin
 0614            {
 0615                Name = i.Name,
 0616                Type = MetadataPluginType.MetadataFetcher
 0617            }));
 618
 619            // Savers
 0620            list.AddRange(_savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit,
 0621            {
 0622                Name = i.Name,
 0623                Type = MetadataPluginType.MetadataSaver
 0624            }));
 0625        }
 626
 627        private void AddImagePlugins(List<MetadataPlugin> list, List<IImageProvider> imageProviders)
 628        {
 629            // Locals
 0630            list.AddRange(imageProviders.Where(i => i is ILocalImageProvider).Select(i => new MetadataPlugin
 0631            {
 0632                Name = i.Name,
 0633                Type = MetadataPluginType.LocalImageProvider
 0634            }));
 635
 636            // Fetchers
 0637            list.AddRange(imageProviders.Where(i => i is IDynamicImageProvider || (i is IRemoteImageProvider)).Select(i 
 0638            {
 0639                Name = i.Name,
 0640                Type = MetadataPluginType.ImageFetcher
 0641            }));
 0642        }
 643
 644        /// <inheritdoc/>
 645        public MetadataOptions GetMetadataOptions(BaseItem item)
 988646            => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
 647
 648        /// <inheritdoc/>
 649        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
 58650            => SaveMetadataAsync(item, updateType, _savers);
 651
 652        /// <inheritdoc/>
 653        public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
 0654            => SaveMetadataAsync(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparison.OrdinalIg
 655
 656        /// <summary>
 657        /// Saves the metadata.
 658        /// </summary>
 659        /// <param name="item">The item.</param>
 660        /// <param name="updateType">Type of the update.</param>
 661        /// <param name="savers">The savers.</param>
 662        private async Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> saver
 663        {
 664            var libraryOptions = _libraryManager.GetLibraryOptions(item);
 665
 666            foreach (var saver in savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, updateType, false)))
 667            {
 668                _logger.LogDebug("Saving {Item} to {Saver}", item.Path ?? item.Name, saver.Name);
 669
 670                if (saver is IMetadataFileSaver fileSaver)
 671                {
 672                    string path;
 673
 674                    try
 675                    {
 676                        path = fileSaver.GetSavePath(item);
 677                    }
 678                    catch (Exception ex)
 679                    {
 680                        _logger.LogError(ex, "Error in {Saver} GetSavePath", saver.Name);
 681                        continue;
 682                    }
 683
 684                    try
 685                    {
 686                        _libraryMonitor.ReportFileSystemChangeBeginning(path);
 687                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 688                    }
 689                    catch (Exception ex)
 690                    {
 691                        _logger.LogError(ex, "Error in metadata saver");
 692                    }
 693                    finally
 694                    {
 695                        _libraryMonitor.ReportFileSystemChangeComplete(path, false);
 696                    }
 697                }
 698                else
 699                {
 700                    try
 701                    {
 702                        await saver.SaveAsync(item, CancellationToken.None).ConfigureAwait(false);
 703                    }
 704                    catch (Exception ex)
 705                    {
 706                        _logger.LogError(ex, "Error in metadata saver");
 707                    }
 708                }
 709            }
 710        }
 711
 712        /// <summary>
 713        /// Determines whether [is saver enabled for item] [the specified saver].
 714        /// </summary>
 715        private bool IsSaverEnabledForItem(IMetadataSaver saver, BaseItem item, LibraryOptions libraryOptions, ItemUpdat
 716        {
 832717            var options = GetMetadataOptions(item);
 718
 719            try
 720            {
 832721                if (!saver.IsEnabledFor(item, updateType))
 722                {
 832723                    return false;
 724                }
 725
 0726                if (!includeDisabled)
 727                {
 0728                    if (libraryOptions.MetadataSavers is null)
 729                    {
 0730                        if (options.DisabledMetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 731                        {
 0732                            return false;
 733                        }
 734
 0735                        if (!item.IsSaveLocalMetadataEnabled())
 736                        {
 0737                            if (updateType >= ItemUpdateType.MetadataEdit)
 738                            {
 739                                // Manual edit occurred
 740                                // Even if save local is off, save locally anyway if the metadata file already exists
 0741                                if (saver is not IMetadataFileSaver fileSaver || !File.Exists(fileSaver.GetSavePath(item
 742                                {
 0743                                    return false;
 744                                }
 745                            }
 746                            else
 747                            {
 748                                // Manual edit did not occur
 749                                // Since local metadata saving is disabled, consider it disabled
 0750                                return false;
 751                            }
 752                        }
 753                    }
 754                    else
 755                    {
 0756                        if (!libraryOptions.MetadataSavers.Contains(saver.Name, StringComparison.OrdinalIgnoreCase))
 757                        {
 0758                            return false;
 759                        }
 760                    }
 761                }
 762
 0763                return true;
 764            }
 0765            catch (Exception ex)
 766            {
 0767                _logger.LogError(ex, "Error in {Saver}.IsEnabledFor", saver.Name);
 0768                return false;
 769            }
 832770        }
 771
 772        /// <inheritdoc/>
 773        public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TL
 774            where TItemType : BaseItem, new()
 775            where TLookupType : ItemLookupInfo
 776        {
 0777            BaseItem? referenceItem = null;
 778
 0779            if (!searchInfo.ItemId.IsEmpty())
 780            {
 0781                referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
 782            }
 783
 0784            return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
 785        }
 786
 787        private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQ
 788            where TItemType : BaseItem, new()
 789            where TLookupType : ItemLookupInfo
 790        {
 791            LibraryOptions libraryOptions;
 792
 793            if (referenceItem is null)
 794            {
 795                // Give it a dummy path just so that it looks like a file system item
 796                var dummy = new TItemType
 797                {
 798                    Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
 799                    ParentId = Guid.NewGuid()
 800                };
 801
 802                dummy.SetParent(new Folder());
 803
 804                referenceItem = dummy;
 805                libraryOptions = new LibraryOptions();
 806            }
 807            else
 808            {
 809                libraryOptions = _libraryManager.GetLibraryOptions(referenceItem);
 810            }
 811
 812            var options = GetMetadataOptions(referenceItem);
 813
 814            var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.I
 815                .OfType<IRemoteSearchProvider<TLookupType>>();
 816
 817            if (!string.IsNullOrEmpty(searchInfo.SearchProviderName))
 818            {
 819                providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.O
 820            }
 821
 822            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
 823            {
 824                searchInfo.SearchInfo.MetadataLanguage = _configurationManager.Configuration.PreferredMetadataLanguage;
 825            }
 826
 827            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
 828            {
 829                searchInfo.SearchInfo.MetadataCountryCode = _configurationManager.Configuration.MetadataCountryCode;
 830            }
 831
 832            var resultList = new List<RemoteSearchResult>();
 833
 834            foreach (var provider in providers)
 835            {
 836                try
 837                {
 838                    var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwa
 839
 840                    foreach (var result in results)
 841                    {
 842                        result.SearchProviderName = provider.Name;
 843
 844                        var existingMatch = resultList.FirstOrDefault(i => i.ProviderIds.Any(p => string.Equals(result.G
 845
 846                        if (existingMatch is null)
 847                        {
 848                            resultList.Add(result);
 849                        }
 850                        else
 851                        {
 852                            foreach (var providerId in result.ProviderIds)
 853                            {
 854                                existingMatch.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 855                            }
 856
 857                            if (string.IsNullOrWhiteSpace(existingMatch.ImageUrl))
 858                            {
 859                                existingMatch.ImageUrl = result.ImageUrl;
 860                            }
 861                        }
 862                    }
 863                }
 864#pragma warning disable CA1031 // do not catch general exception types
 865                catch (Exception ex)
 866#pragma warning restore CA1031 // do not catch general exception types
 867                {
 868                    _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
 869                }
 870            }
 871
 872            return resultList;
 873        }
 874
 875        private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
 876        {
 11877            return _externalIds.Where(i =>
 11878            {
 11879                try
 11880                {
 11881                    return i.Supports(item);
 11882                }
 11883                catch (Exception ex)
 11884                {
 11885                    _logger.LogError(ex, "Error in {Type}.Supports", i.GetType().Name);
 11886                    return false;
 11887                }
 11888            });
 889        }
 890
 891        /// <inheritdoc/>
 892        public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
 893        {
 894#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11
 11895            var legacyExternalIdUrls = GetExternalIds(item)
 11896                .Select(i =>
 11897                {
 11898                    var urlFormatString = i.UrlFormatString;
 11899                    if (string.IsNullOrEmpty(urlFormatString)
 11900                        || !item.TryGetProviderId(i.Key, out var providerId))
 11901                    {
 11902                        return null;
 11903                    }
 11904
 11905                    return new ExternalUrl
 11906                    {
 11907                        Name = i.ProviderName,
 11908                        Url = string.Format(
 11909                            CultureInfo.InvariantCulture,
 11910                            urlFormatString,
 11911                            providerId)
 11912                    };
 11913                })
 11914                .OfType<ExternalUrl>();
 915#pragma warning restore CS0618 // Type or member is obsolete
 916
 11917            var externalUrls = _externalUrlProviders
 11918                .SelectMany(p => p
 11919                    .GetExternalUrls(item)
 11920                    .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
 921
 11922            return legacyExternalIdUrls.Concat(externalUrls).OrderBy(u => u.Name);
 923        }
 924
 925        /// <inheritdoc/>
 926        public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
 927        {
 0928            return GetExternalIds(item)
 0929                .Select(i => new ExternalIdInfo(
 0930                    name: i.ProviderName,
 0931                    key: i.Key,
 0932                    type: i.Type,
 0933#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11
 0934                    urlFormatString: i.UrlFormatString));
 935#pragma warning restore CS0618 // Type or member is obsolete
 936        }
 937
 938        /// <inheritdoc/>
 939        public HashSet<Guid> GetRefreshQueue()
 940        {
 1941            lock (_refreshQueueLock)
 942            {
 1943                return _refreshQueue.UnorderedItems.Select(x => x.Element.ItemId).ToHashSet();
 944            }
 1945        }
 946
 947        /// <inheritdoc/>
 948        public void OnRefreshStart(BaseItem item)
 949        {
 19950            _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
 19951            _activeRefreshes[item.Id] = 0;
 952            try
 953            {
 19954                RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 19955            }
 0956            catch (Exception ex)
 957            {
 958                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0959                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
 0960            }
 19961        }
 962
 963        /// <inheritdoc/>
 964        public void OnRefreshComplete(BaseItem item)
 965        {
 19966            _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
 19967            _activeRefreshes.TryRemove(item.Id, out _);
 968
 969            try
 970            {
 19971                RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
 19972            }
 0973            catch (Exception ex)
 974            {
 975                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 0976                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
 0977            }
 19978        }
 979
 980        /// <inheritdoc/>
 981        public double? GetRefreshProgress(Guid id)
 982        {
 0983            if (_activeRefreshes.TryGetValue(id, out double value))
 984            {
 0985                return value;
 986            }
 987
 0988            return null;
 989        }
 990
 991        /// <inheritdoc/>
 992        public void OnRefreshProgress(BaseItem item, double progress)
 993        {
 57994            var id = item.Id;
 57995            _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
 996
 57997            if (!_activeRefreshes.TryGetValue(id, out var current)
 57998                || progress <= current
 57999                || !_activeRefreshes.TryUpdate(id, progress, current))
 1000            {
 1001                // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
 01002                return;
 1003            }
 1004
 1005            try
 1006            {
 571007                RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(
 571008            }
 01009            catch (Exception ex)
 1010            {
 1011                // EventHandlers should never propagate exceptions, but we have little control over plugins...
 01012                _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
 01013            }
 571014        }
 1015
 1016        /// <inheritdoc/>
 1017        public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
 1018        {
 01019            ArgumentNullException.ThrowIfNull(itemId);
 01020            if (itemId.IsEmpty())
 1021            {
 01022                throw new ArgumentException("Guid can't be empty", nameof(itemId));
 1023            }
 1024
 01025            if (_disposed)
 1026            {
 01027                return;
 1028            }
 1029
 01030            _refreshQueue.Enqueue((itemId, options), priority);
 1031
 01032            lock (_refreshQueueLock)
 1033            {
 01034                if (!_isProcessingRefreshQueue)
 1035                {
 01036                    _isProcessingRefreshQueue = true;
 01037                    Task.Run(StartProcessingRefreshQueue);
 1038                }
 01039            }
 01040        }
 1041
 1042        private async Task StartProcessingRefreshQueue()
 1043        {
 1044            var libraryManager = _libraryManager;
 1045
 1046            if (_disposed)
 1047            {
 1048                return;
 1049            }
 1050
 1051            var cancellationToken = _disposeCancellationTokenSource.Token;
 1052
 1053            while (_refreshQueue.TryDequeue(out var refreshItem, out _))
 1054            {
 1055                if (_disposed)
 1056                {
 1057                    return;
 1058                }
 1059
 1060                try
 1061                {
 1062                    var item = libraryManager.GetItemById(refreshItem.ItemId);
 1063                    if (item is null)
 1064                    {
 1065                        continue;
 1066                    }
 1067
 1068                    var task = item is MusicArtist artist
 1069                        ? RefreshArtist(artist, refreshItem.RefreshOptions, cancellationToken)
 1070                        : RefreshItem(item, refreshItem.RefreshOptions, cancellationToken);
 1071
 1072                    await task.ConfigureAwait(false);
 1073                }
 1074                catch (OperationCanceledException)
 1075                {
 1076                    break;
 1077                }
 1078                catch (Exception ex)
 1079                {
 1080                    _logger.LogError(ex, "Error refreshing item");
 1081                }
 1082            }
 1083
 1084            lock (_refreshQueueLock)
 1085            {
 1086                _isProcessingRefreshQueue = false;
 1087            }
 1088        }
 1089
 1090        private async Task RefreshItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToke
 1091        {
 1092            await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1093
 1094            // Collection folders don't validate their children so we'll have to simulate that here
 1095            switch (item)
 1096            {
 1097                case CollectionFolder collectionFolder:
 1098                    await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(f
 1099                    break;
 1100                case Folder folder:
 1101                    await folder.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken)
 1102                    break;
 1103            }
 1104        }
 1105
 1106        private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFo
 1107        {
 1108            foreach (var child in collectionFolder.GetPhysicalFolders())
 1109            {
 1110                await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1111
 1112                await child.ValidateChildren(new Progress<double>(), options, cancellationToken: cancellationToken).Conf
 1113            }
 1114        }
 1115
 1116        private async Task RefreshArtist(MusicArtist item, MetadataRefreshOptions options, CancellationToken cancellatio
 1117        {
 1118            var albums = _libraryManager
 1119                .GetItemList(new InternalItemsQuery
 1120                {
 1121                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 1122                    ArtistIds = new[] { item.Id },
 1123                    DtoOptions = new DtoOptions(false)
 1124                    {
 1125                        EnableImages = false
 1126                    }
 1127                })
 1128                .OfType<MusicAlbum>();
 1129
 1130            var musicArtists = albums
 1131                .Select(i => i.MusicArtist)
 1132                .Where(i => i is not null)
 1133                .Distinct();
 1134
 1135            var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, t
 1136
 1137            await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
 1138
 1139            try
 1140            {
 1141                await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
 1142            }
 1143            catch (Exception ex)
 1144            {
 1145                _logger.LogError(ex, "Error refreshing library");
 1146            }
 1147        }
 1148
 1149        /// <inheritdoc/>
 1150        public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
 1151        {
 01152            return RefreshItem(item, options, cancellationToken);
 1153        }
 1154
 1155        /// <inheritdoc/>
 1156        public void Dispose()
 1157        {
 921158            Dispose(true);
 921159            GC.SuppressFinalize(this);
 921160        }
 1161
 1162        /// <summary>
 1163        /// Releases unmanaged and optionally managed resources.
 1164        /// </summary>
 1165        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
 1166        protected virtual void Dispose(bool disposing)
 1167        {
 921168            if (_disposed)
 1169            {
 01170                return;
 1171            }
 1172
 921173            if (disposing)
 1174            {
 921175                if (!_disposeCancellationTokenSource.IsCancellationRequested)
 1176                {
 921177                    _disposeCancellationTokenSource.Cancel();
 1178                }
 1179
 921180                _disposeCancellationTokenSource.Dispose();
 921181                _imageSaveLock.Dispose();
 1182            }
 1183
 921184            _disposed = true;
 921185        }
 1186    }
 1187}

Methods/Properties

.ctor(System.Net.Http.IHttpClientFactory,MediaBrowser.Controller.Subtitles.ISubtitleManager,MediaBrowser.Controller.Configuration.IServerConfigurationManager,MediaBrowser.Controller.Library.ILibraryMonitor,Microsoft.Extensions.Logging.ILogger`1<MediaBrowser.Providers.Manager.ProviderManager>,MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.IServerApplicationPaths,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.BaseItemManager.IBaseItemManager,MediaBrowser.Controller.Lyrics.ILyricManager,Microsoft.Extensions.Caching.Memory.IMemoryCache,MediaBrowser.Controller.IMediaSegmentManager)
AddParts(System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IImageProvider>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IMetadataService>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IMetadataProvider>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Library.IMetadataSaver>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IExternalId>,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IExternalUrlProvider>)
RefreshSingleItem(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
SaveImage(MediaBrowser.Controller.Entities.BaseItem,System.IO.Stream,System.String,MediaBrowser.Model.Entities.ImageType,System.Nullable`1<System.Int32>,System.Threading.CancellationToken)
SaveImage(MediaBrowser.Controller.Entities.BaseItem,System.String,System.String,MediaBrowser.Model.Entities.ImageType,System.Nullable`1<System.Int32>,System.Nullable`1<System.Boolean>,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)