< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.MetadataService<T1, T2>
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/MetadataService.cs
Line coverage
68%
Covered lines: 237
Uncovered lines: 110
Coverable lines: 347
Total lines: 1275
Line coverage: 68.2%
Branch coverage
67%
Covered branches: 235
Total branches: 350
Branch coverage: 67.1%
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/MetadataService.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Linq;
 8using System.Net.Http;
 9using System.Threading;
 10using System.Threading.Tasks;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Controller.Configuration;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Entities.Audio;
 15using MediaBrowser.Controller.IO;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Controller.Providers;
 18using MediaBrowser.Model.Configuration;
 19using MediaBrowser.Model.Entities;
 20using MediaBrowser.Model.IO;
 21using MediaBrowser.Model.Providers;
 22using Microsoft.Extensions.Logging;
 23
 24namespace MediaBrowser.Providers.Manager
 25{
 26    public abstract class MetadataService<TItemType, TIdType> : IMetadataService
 27        where TItemType : BaseItem, IHasLookupInfo<TIdType>, new()
 28        where TIdType : ItemLookupInfo, new()
 29    {
 30        protected MetadataService(
 31            IServerConfigurationManager serverConfigurationManager,
 32            ILogger<MetadataService<TItemType, TIdType>> logger,
 33            IProviderManager providerManager,
 34            IFileSystem fileSystem,
 35            ILibraryManager libraryManager,
 36            IExternalDataManager externalDataManager)
 37        {
 54638            ServerConfigurationManager = serverConfigurationManager;
 54639            Logger = logger;
 54640            ProviderManager = providerManager;
 54641            FileSystem = fileSystem;
 54642            LibraryManager = libraryManager;
 54643            ExternalDataManager = externalDataManager;
 54644            ImageProvider = new ItemImageProvider(Logger, ProviderManager, FileSystem);
 54645        }
 46
 47        protected ItemImageProvider ImageProvider { get; }
 48
 49        protected IServerConfigurationManager ServerConfigurationManager { get; }
 50
 51        protected ILogger<MetadataService<TItemType, TIdType>> Logger { get; }
 52
 53        protected IProviderManager ProviderManager { get; }
 54
 55        protected IFileSystem FileSystem { get; }
 56
 57        protected ILibraryManager LibraryManager { get; }
 58
 59        protected IExternalDataManager ExternalDataManager { get; }
 60
 3361        protected virtual bool EnableUpdatingPremiereDateFromChildren => false;
 62
 3363        protected virtual bool EnableUpdatingGenresFromChildren => false;
 64
 3365        protected virtual bool EnableUpdatingStudiosFromChildren => false;
 66
 3367        protected virtual bool EnableUpdatingOfficialRatingFromChildren => false;
 68
 50469        public virtual int Order => 0;
 70
 71        private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService)
 72        {
 73            try
 74            {
 6675                return directoryService.GetFile(path);
 76            }
 077            catch (Exception ex)
 78            {
 079                Logger.LogError(ex, "Error getting file {Path}", path);
 080                return null;
 81            }
 6682        }
 83
 84        public virtual async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, 
 85        {
 86            var itemOfType = (TItemType)item;
 87            var updateType = ItemUpdateType.None;
 88            var libraryOptions = LibraryManager.GetLibraryOptions(item);
 89            var isFirstRefresh = item.DateLastRefreshed == default;
 90            var hasRefreshedMetadata = true;
 91            var hasRefreshedImages = true;
 92
 93            var requiresRefresh = libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRef
 94
 95            if (!requiresRefresh && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
 96            {
 97                // TODO: If this returns true, should we instead just change metadata refresh mode to Full?
 98                requiresRefresh = item.RequiresRefresh();
 99
 100                if (requiresRefresh)
 101                {
 102                    Logger.LogDebug("Refreshing {Type} {Item} because item.RequiresRefresh() returned true", typeof(TIte
 103                }
 104            }
 105
 106            if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages)
 107            {
 108                if (ImageProvider.RemoveImages(item))
 109                {
 110                    updateType |= ItemUpdateType.ImageUpdate;
 111                }
 112            }
 113
 114            var localImagesFailed = false;
 115            var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
 116
 117            // Only validate already registered images if we are replacing and saving locally
 118            if (item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages)
 119            {
 120                item.ValidateImages();
 121            }
 122            else
 123            {
 124                // Run full image validation and register new local images
 125                try
 126                {
 127                    if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptio
 128                    {
 129                        updateType |= ItemUpdateType.ImageUpdate;
 130                    }
 131                }
 132                catch (Exception ex)
 133                {
 134                    localImagesFailed = true;
 135                    Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
 136                }
 137            }
 138
 139            var metadataResult = new MetadataResult<TItemType>
 140            {
 141                Item = itemOfType
 142            };
 143
 144            var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refresh
 145            updateType |= beforeSaveResult;
 146
 147            updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataR
 148
 149            // Next run metadata providers
 150            if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
 151            {
 152                var providers = GetProviders(item, libraryOptions, refreshOptions, isFirstRefresh, requiresRefresh)
 153                    .ToList();
 154
 155                if (providers.Count > 0 || isFirstRefresh || requiresRefresh)
 156                {
 157                    if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata))
 158                    {
 159                        updateType |= ItemUpdateType.MetadataImport;
 160                    }
 161                }
 162
 163                if (providers.Count > 0)
 164                {
 165                    var id = itemOfType.GetLookupInfo();
 166
 167                    if (refreshOptions.SearchResult is not null)
 168                    {
 169                        ApplySearchResult(id, refreshOptions.SearchResult);
 170                    }
 171
 172                    id.IsAutomated = refreshOptions.IsAutomated;
 173
 174                    var hasMetadataSavers = ProviderManager.GetMetadataSavers(item, libraryOptions).Any();
 175                    var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider
 176
 177                    updateType |= result.UpdateType;
 178                    if (result.Failures > 0)
 179                    {
 180                        hasRefreshedMetadata = false;
 181                    }
 182                }
 183            }
 184
 185            // Next run remote image providers, but only if local image providers didn't throw an exception
 186            if (!localImagesFailed && refreshOptions.ImageRefreshMode > MetadataRefreshMode.ValidationOnly)
 187            {
 188                var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList();
 189
 190                if (providers.Count > 0)
 191                {
 192                    var result = await ImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions
 193
 194                    updateType |= result.UpdateType;
 195                    if (result.Failures > 0)
 196                    {
 197                        hasRefreshedImages = false;
 198                    }
 199                }
 200            }
 201
 202            if (hasRefreshedMetadata && hasRefreshedImages)
 203            {
 204                item.DateLastRefreshed = DateTime.UtcNow;
 205                updateType |= item.OnMetadataChanged();
 206            }
 207
 208            updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataR
 209
 210            await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
 211
 212            return updateType;
 213
 214            async Task<ItemUpdateType> SaveInternal(BaseItem item, MetadataRefreshOptions refreshOptions, ItemUpdateType
 215            {
 216                // Save if changes were made, or it's never been saved before
 217                if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.Rep
 218                {
 219                    if (item.IsFileProtocol)
 220                    {
 221                        var file = TryGetFile(item.Path, refreshOptions.DirectoryService);
 222                        if (file is not null)
 223                        {
 224                            item.DateModified = file.LastWriteTimeUtc;
 225                        }
 226                    }
 227
 228                    // If any of these properties are set then make sure the updateType is not None, just to force every
 229                    if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
 230                    {
 231                        updateType |= ItemUpdateType.MetadataDownload;
 232                    }
 233
 234                    // Save to database
 235                    await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
 236                }
 237
 238                return updateType;
 239            }
 240        }
 241
 242        private void ApplySearchResult(ItemLookupInfo lookupInfo, RemoteSearchResult result)
 243        {
 244            // Episode and Season do not support Identify, so the search results are the Series'
 245            switch (lookupInfo)
 246            {
 247                case EpisodeInfo episodeInfo:
 0248                    episodeInfo.SeriesProviderIds = result.ProviderIds;
 0249                    episodeInfo.ProviderIds.Clear();
 0250                    break;
 251                case SeasonInfo seasonInfo:
 0252                    seasonInfo.SeriesProviderIds = result.ProviderIds;
 0253                    seasonInfo.ProviderIds.Clear();
 0254                    break;
 255                default:
 0256                    lookupInfo.ProviderIds = result.ProviderIds;
 0257                    lookupInfo.Name = result.Name;
 0258                    lookupInfo.Year = result.ProductionYear;
 259                    break;
 260            }
 0261        }
 262
 263        protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken ca
 264        {
 265            if (result.Item.SupportsPeople && result.People is not null)
 266            {
 267                var baseItem = result.Item;
 268
 269                await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false)
 270            }
 271
 272            await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false);
 273        }
 274
 275        protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationT
 276        {
 49277            item.AfterMetadataRefresh();
 49278            return Task.CompletedTask;
 279        }
 280
 281        /// <summary>
 282        /// Before the save.
 283        /// </summary>
 284        /// <param name="item">The item.</param>
 285        /// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param>
 286        /// <param name="currentUpdateType">Type of the current update.</param>
 287        /// <returns>ItemUpdateType.</returns>
 288        private ItemUpdateType BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
 289        {
 49290            var updateType = BeforeSaveInternal(item, isFullRefresh, currentUpdateType);
 291
 49292            updateType |= item.OnMetadataChanged();
 293
 49294            return updateType;
 295        }
 296
 297        protected virtual ItemUpdateType BeforeSaveInternal(TItemType item, bool isFullRefresh, ItemUpdateType updateTyp
 298        {
 49299            if (EnableUpdateMetadataFromChildren(item, isFullRefresh, updateType))
 300            {
 0301                if (isFullRefresh || updateType > ItemUpdateType.None)
 302                {
 0303                    var children = GetChildrenForMetadataUpdates(item);
 304
 0305                    updateType = UpdateMetadataFromChildren(item, children, isFullRefresh, updateType);
 306                }
 307            }
 308
 49309            var presentationUniqueKey = item.CreatePresentationUniqueKey();
 49310            if (!string.Equals(item.PresentationUniqueKey, presentationUniqueKey, StringComparison.Ordinal))
 311            {
 28312                item.PresentationUniqueKey = presentationUniqueKey;
 28313                updateType |= ItemUpdateType.MetadataImport;
 314            }
 315
 316            // Cleanup extracted files if source file was modified
 49317            var itemPath = item.Path;
 49318            if (!string.IsNullOrEmpty(itemPath))
 319            {
 49320                var info = FileSystem.GetFileSystemInfo(itemPath);
 49321                var modificationDate = info.LastWriteTimeUtc;
 49322                var itemLastModifiedFileSystem = item.DateModified;
 49323                if (info.Exists && itemLastModifiedFileSystem != modificationDate)
 324                {
 3325                    Logger.LogDebug("File modification time changed from {Then} to {Now}: {Path}", itemLastModifiedFileS
 326
 3327                    item.DateModified = modificationDate;
 3328                    if (ServerConfigurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded)
 329                    {
 3330                        item.DateCreated = info.CreationTimeUtc;
 331                    }
 332
 3333                    if (item is Video video)
 334                    {
 0335                        Logger.LogInformation("File changed, pruning extracted data: {Path}", item.Path);
 0336                        ExternalDataManager.DeleteExternalItemDataAsync(video, CancellationToken.None).GetAwaiter().GetR
 337                    }
 338
 3339                    updateType |= ItemUpdateType.MetadataImport;
 340                }
 341            }
 342
 49343            return updateType;
 344        }
 345
 346        protected virtual bool EnableUpdateMetadataFromChildren(TItemType item, bool isFullRefresh, ItemUpdateType curre
 347        {
 49348            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
 349            {
 33350                if (EnableUpdatingPremiereDateFromChildren || EnableUpdatingGenresFromChildren || EnableUpdatingStudiosF
 351                {
 0352                    return true;
 353                }
 354
 33355                if (item is Folder folder)
 356                {
 33357                    return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks;
 358                }
 359            }
 360
 16361            return false;
 362        }
 363
 364        protected virtual IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(TItemType item)
 365        {
 0366            if (item is Folder folder)
 367            {
 0368                return folder.GetRecursiveChildren();
 369            }
 370
 0371            return [];
 372        }
 373
 374        protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IReadOnlyList<BaseItem> children, bo
 375        {
 0376            var updateType = ItemUpdateType.None;
 377
 0378            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
 379            {
 0380                updateType |= UpdateCumulativeRunTimeTicks(item, children);
 0381                updateType |= UpdateDateLastMediaAdded(item, children);
 382
 383                // don't update user-changeable metadata for locked items
 0384                if (item.IsLocked)
 385                {
 0386                    return updateType;
 387                }
 388
 0389                if (EnableUpdatingPremiereDateFromChildren)
 390                {
 0391                    updateType |= UpdatePremiereDate(item, children);
 392                }
 393
 0394                if (EnableUpdatingGenresFromChildren)
 395                {
 0396                    updateType |= UpdateGenres(item, children);
 397                }
 398
 0399                if (EnableUpdatingStudiosFromChildren)
 400                {
 0401                    updateType |= UpdateStudios(item, children);
 402                }
 403
 0404                if (EnableUpdatingOfficialRatingFromChildren)
 405                {
 0406                    updateType |= UpdateOfficialRating(item, children);
 407                }
 408            }
 409
 0410            return updateType;
 411        }
 412
 413        private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IReadOnlyList<BaseItem> children)
 414        {
 0415            if (item is Folder folder && folder.SupportsCumulativeRunTimeTicks)
 416            {
 0417                long ticks = 0;
 418
 0419                foreach (var child in children)
 420                {
 0421                    if (!child.IsFolder)
 422                    {
 0423                        ticks += child.RunTimeTicks ?? 0;
 424                    }
 425                }
 426
 0427                if (!folder.RunTimeTicks.HasValue || folder.RunTimeTicks.Value != ticks)
 428                {
 0429                    folder.RunTimeTicks = ticks;
 0430                    return ItemUpdateType.MetadataImport;
 431                }
 432            }
 433
 0434            return ItemUpdateType.None;
 435        }
 436
 437        private ItemUpdateType UpdateDateLastMediaAdded(TItemType item, IReadOnlyList<BaseItem> children)
 438        {
 0439            var updateType = ItemUpdateType.None;
 440
 0441            if (item is Folder folder && folder.SupportsDateLastMediaAdded)
 442            {
 0443                var dateLastMediaAdded = DateTime.MinValue;
 0444                var any = false;
 445
 0446                foreach (var child in children)
 447                {
 448                    // Exclude any folders and virtual items since they are only placeholders
 0449                    if (!child.IsFolder && !child.IsVirtualItem)
 450                    {
 0451                        var childDateCreated = child.DateCreated;
 0452                        if (childDateCreated > dateLastMediaAdded)
 453                        {
 0454                            dateLastMediaAdded = childDateCreated;
 455                        }
 456
 0457                        any = true;
 458                    }
 459                }
 460
 0461                if ((!folder.DateLastMediaAdded.HasValue && any) || folder.DateLastMediaAdded != dateLastMediaAdded)
 462                {
 0463                    folder.DateLastMediaAdded = dateLastMediaAdded;
 0464                    updateType = ItemUpdateType.MetadataImport;
 465                }
 466            }
 467
 0468            return updateType;
 469        }
 470
 471        private ItemUpdateType UpdatePremiereDate(TItemType item, IReadOnlyList<BaseItem> children)
 472        {
 0473            var updateType = ItemUpdateType.None;
 474
 0475            if (children.Count == 0)
 476            {
 0477                return updateType;
 478            }
 479
 0480            var date = children.Select(i => i.PremiereDate ?? DateTime.MaxValue).Min();
 481
 0482            var originalPremiereDate = item.PremiereDate;
 0483            var originalProductionYear = item.ProductionYear;
 484
 0485            if (date > DateTime.MinValue && date < DateTime.MaxValue)
 486            {
 0487                item.PremiereDate = date;
 0488                item.ProductionYear = date.Year;
 489            }
 490            else
 491            {
 0492                var year = children.Select(i => i.ProductionYear ?? 0).Min();
 493
 0494                if (year > 0)
 495                {
 0496                    item.ProductionYear = year;
 497                }
 498            }
 499
 0500            if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue)
 0501                || (originalProductionYear ?? -1) != (item.ProductionYear ?? -1))
 502            {
 0503                updateType |= ItemUpdateType.MetadataEdit;
 504            }
 505
 0506            return updateType;
 507        }
 508
 509        private ItemUpdateType UpdateGenres(TItemType item, IReadOnlyList<BaseItem> children)
 510        {
 0511            var updateType = ItemUpdateType.None;
 512
 0513            if (!item.LockedFields.Contains(MetadataField.Genres))
 514            {
 0515                var currentList = item.Genres;
 516
 0517                item.Genres = children.SelectMany(i => i.Genres)
 0518                    .Distinct(StringComparer.OrdinalIgnoreCase)
 0519                    .ToArray();
 520
 0521                if (currentList.Length != item.Genres.Length || !currentList.Order().SequenceEqual(item.Genres.Order(), 
 522                {
 0523                    updateType |= ItemUpdateType.MetadataEdit;
 524                }
 525            }
 526
 0527            return updateType;
 528        }
 529
 530        private ItemUpdateType UpdateStudios(TItemType item, IReadOnlyList<BaseItem> children)
 531        {
 0532            var updateType = ItemUpdateType.None;
 533
 0534            if (!item.LockedFields.Contains(MetadataField.Studios))
 535            {
 0536                var currentList = item.Studios;
 537
 0538                item.Studios = children.SelectMany(i => i.Studios)
 0539                    .Distinct(StringComparer.OrdinalIgnoreCase)
 0540                    .ToArray();
 541
 0542                if (currentList.Length != item.Studios.Length || !currentList.Order().SequenceEqual(item.Studios.Order()
 543                {
 0544                    updateType |= ItemUpdateType.MetadataEdit;
 545                }
 546            }
 547
 0548            return updateType;
 549        }
 550
 551        private ItemUpdateType UpdateOfficialRating(TItemType item, IReadOnlyList<BaseItem> children)
 552        {
 0553            var updateType = ItemUpdateType.None;
 554
 0555            if (!item.LockedFields.Contains(MetadataField.OfficialRating))
 556            {
 0557                if (item.UpdateRatingToItems(children))
 558                {
 0559                    updateType |= ItemUpdateType.MetadataEdit;
 560                }
 561            }
 562
 0563            return updateType;
 564        }
 565
 566        /// <summary>
 567        /// Gets the providers.
 568        /// </summary>
 569        /// <param name="item">A media item.</param>
 570        /// <param name="libraryOptions">The LibraryOptions to use.</param>
 571        /// <param name="options">The MetadataRefreshOptions to use.</param>
 572        /// <param name="isFirstRefresh">Specifies first refresh mode.</param>
 573        /// <param name="requiresRefresh">Specifies refresh mode.</param>
 574        /// <returns>IEnumerable{`0}.</returns>
 575        protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefr
 576        {
 577            // Get providers to refresh
 49578            var providers = ProviderManager.GetMetadataProviders<TItemType>(item, libraryOptions).ToList();
 579
 49580            var metadataRefreshMode = options.MetadataRefreshMode;
 581
 582            // Run all if either of these flags are true
 49583            var runAllProviders = options.ReplaceAllMetadata ||
 49584                metadataRefreshMode == MetadataRefreshMode.FullRefresh ||
 49585                (isFirstRefresh && metadataRefreshMode >= MetadataRefreshMode.Default) ||
 49586                (requiresRefresh && metadataRefreshMode >= MetadataRefreshMode.Default);
 587
 49588            if (!runAllProviders)
 589            {
 16590                var providersWithChanges = providers
 16591                    .Where(i =>
 16592                    {
 16593                        if (i is IHasItemChangeMonitor hasFileChangeMonitor)
 16594                        {
 16595                            return HasChanged(item, hasFileChangeMonitor, options.DirectoryService);
 16596                        }
 16597
 16598                        return false;
 16599                    })
 16600                    .ToList();
 601
 16602                if (providersWithChanges.Count == 0)
 603                {
 0604                    providers = new List<IMetadataProvider<TItemType>>();
 605                }
 606                else
 607                {
 16608                    var anyRemoteProvidersChanged = providersWithChanges.OfType<IRemoteMetadataProvider>()
 16609                        .Any();
 610
 16611                    var anyLocalProvidersChanged = providersWithChanges.OfType<ILocalMetadataProvider>()
 16612                        .Any();
 613
 16614                    var anyLocalPreRefreshProvidersChanged = providersWithChanges.OfType<IPreRefreshProvider>()
 16615                        .Any();
 616
 16617                    providers = providers.Where(i =>
 16618                    {
 16619                        // If any provider reports a change, always run local ones as well
 16620                        if (i is ILocalMetadataProvider)
 16621                        {
 16622                            return anyRemoteProvidersChanged || anyLocalProvidersChanged || anyLocalPreRefreshProvidersC
 16623                        }
 16624
 16625                        // If any remote providers changed, run them all so that priorities can be honored
 16626                        if (i is IRemoteMetadataProvider)
 16627                        {
 16628                            if (options.MetadataRefreshMode == MetadataRefreshMode.ValidationOnly)
 16629                            {
 16630                                return false;
 16631                            }
 16632
 16633                            return anyRemoteProvidersChanged;
 16634                        }
 16635
 16636                        // Run custom refresh providers if they report a change or any remote providers change
 16637                        return anyRemoteProvidersChanged || providersWithChanges.Contains(i);
 16638                    }).ToList();
 639                }
 640            }
 641
 49642            return providers;
 643        }
 644
 645        protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(BaseItem item, IEnumerable<IImageProvide
 646        {
 647            // Get providers to refresh
 49648            var providers = allImageProviders.Where(i => i is not ILocalImageProvider);
 649
 49650            var dateLastImageRefresh = item.DateLastRefreshed;
 651
 652            // Run all if either of these flags are true
 49653            var runAllProviders = options.ImageRefreshMode == MetadataRefreshMode.FullRefresh || dateLastImageRefresh ==
 654
 49655            if (!runAllProviders)
 656            {
 20657                providers = providers
 20658                    .Where(i =>
 20659                    {
 20660                        if (i is IHasItemChangeMonitor hasFileChangeMonitor)
 20661                        {
 20662                            return HasChanged(item, hasFileChangeMonitor, options.DirectoryService);
 20663                        }
 20664
 20665                        return false;
 20666                    });
 667            }
 668
 49669            return providers;
 670        }
 671
 672        public bool CanRefresh(BaseItem item)
 673        {
 936674            return item is TItemType;
 675        }
 676
 677        public bool CanRefreshPrimary(Type type)
 678        {
 1183679            return type == typeof(TItemType);
 680        }
 681
 682        protected virtual async Task<RefreshResult> RefreshWithProviders(
 683            MetadataResult<TItemType> metadata,
 684            TIdType id,
 685            MetadataRefreshOptions options,
 686            ICollection<IMetadataProvider> providers,
 687            ItemImageProvider imageService,
 688            bool isSavingMetadata,
 689            CancellationToken cancellationToken)
 690        {
 691            var refreshResult = new RefreshResult
 692            {
 693                UpdateType = ItemUpdateType.None
 694            };
 695
 696            var item = metadata.Item;
 697
 698            var customProviders = providers.OfType<ICustomMetadataProvider<TItemType>>().ToList();
 699            var logName = !item.IsFileProtocol ? item.Name ?? item.Path : item.Path ?? item.Name;
 700
 701            foreach (var provider in customProviders.Where(i => i is IPreRefreshProvider))
 702            {
 703                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwa
 704            }
 705
 706            if (item.IsLocked)
 707            {
 708                return refreshResult;
 709            }
 710
 711            var temp = new MetadataResult<TItemType>
 712            {
 713                Item = CreateNew()
 714            };
 715            temp.Item.Path = item.Path;
 716            temp.Item.Id = item.Id;
 717            temp.Item.ParentIndexNumber = item.ParentIndexNumber;
 718            temp.Item.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 719            temp.Item.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 720
 721            var foundImageTypes = new List<ImageType>();
 722
 723            // Do not execute local providers if we are identifying or replacing with local metadata saving enabled
 724            if (options.SearchResult is null && !(isSavingMetadata && options.ReplaceAllMetadata))
 725            {
 726                foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
 727                {
 728                    var providerName = provider.GetType().Name;
 729                    Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
 730
 731                    var itemInfo = new ItemInfo(item);
 732
 733                    try
 734                    {
 735                        var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken
 736
 737                        if (localItem.HasMetadata)
 738                        {
 739                            foreach (var remoteImage in localItem.RemoteImages)
 740                            {
 741                                try
 742                                {
 743                                    if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
 744                                        && !options.IsReplacingImage(remoteImage.Type))
 745                                    {
 746                                        continue;
 747                                    }
 748
 749                                    await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cance
 750                                    refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
 751
 752                                    // remember imagetype that has just been downloaded
 753                                    foundImageTypes.Add(remoteImage.Type);
 754                                }
 755                                catch (HttpRequestException ex)
 756                                {
 757                                    Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteIm
 758                                }
 759                            }
 760
 761                            if (foundImageTypes.Count > 0)
 762                            {
 763                                imageService.UpdateReplaceImages(options, foundImageTypes);
 764                            }
 765
 766                            if (imageService.MergeImages(item, localItem.Images, options))
 767                            {
 768                                refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
 769                            }
 770
 771                            MergeData(localItem, temp, [], false, true);
 772                            refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
 773
 774                            break;
 775                        }
 776
 777                        Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
 778                    }
 779                    catch (OperationCanceledException)
 780                    {
 781                        throw;
 782                    }
 783                    catch (Exception ex)
 784                    {
 785                        Logger.LogError(ex, "Error in {Provider}", provider.Name);
 786
 787                        // If a local provider fails, consider that a failure
 788                        refreshResult.ErrorMessage = ex.Message;
 789                    }
 790                }
 791            }
 792
 793            var isLocalLocked = temp.Item.IsLocked;
 794            if (!isLocalLocked && (options.ReplaceAllMetadata || options.MetadataRefreshMode > MetadataRefreshMode.Valid
 795            {
 796                var remoteResult = await ExecuteRemoteProviders(temp, logName, false, id, providers.OfType<IRemoteMetada
 797                    .ConfigureAwait(false);
 798
 799                refreshResult.UpdateType |= remoteResult.UpdateType;
 800                refreshResult.ErrorMessage = remoteResult.ErrorMessage;
 801                refreshResult.Failures += remoteResult.Failures;
 802            }
 803
 804            if (providers.Any(i => i is not ICustomMetadataProvider))
 805            {
 806                if (refreshResult.UpdateType > ItemUpdateType.None)
 807                {
 808                    if (!options.RemoveOldMetadata)
 809                    {
 810                        // Add existing metadata to provider result if it does not exist there
 811                        MergeData(metadata, temp, [], false, false);
 812                    }
 813
 814                    if (isLocalLocked)
 815                    {
 816                        MergeData(temp, metadata, item.LockedFields, true, true);
 817                    }
 818                    else
 819                    {
 820                        var shouldReplace = options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly || options.
 821                        MergeData(temp, metadata, item.LockedFields, shouldReplace, true);
 822                    }
 823                }
 824            }
 825
 826            foreach (var provider in customProviders.Where(i => i is not IPreRefreshProvider))
 827            {
 828                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwa
 829            }
 830
 831            return refreshResult;
 832        }
 833
 834        private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, string logName
 835        {
 836            Logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, logName);
 837
 838            try
 839            {
 840                refreshResult.UpdateType |= await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(f
 841            }
 842            catch (OperationCanceledException)
 843            {
 844                throw;
 845            }
 846            catch (Exception ex)
 847            {
 848                refreshResult.ErrorMessage = ex.Message;
 849                Logger.LogError(ex, "Error in {Provider}", provider.Name);
 850            }
 851        }
 852
 853        protected virtual TItemType CreateNew()
 854        {
 49855            return new TItemType();
 856        }
 857
 858        private async Task<RefreshResult> ExecuteRemoteProviders(MetadataResult<TItemType> temp, string logName, bool re
 859        {
 860            var refreshResult = new RefreshResult();
 861
 862            if (id is not null)
 863            {
 864                MergeNewData(temp.Item, id);
 865            }
 866
 867            foreach (var provider in providers)
 868            {
 869                var providerName = provider.GetType().Name;
 870                Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
 871
 872                try
 873                {
 874                    var result = await provider.GetMetadata(id, cancellationToken).ConfigureAwait(false);
 875
 876                    if (result.HasMetadata)
 877                    {
 878                        result.Provider = provider.Name;
 879
 880                        MergeData(result, temp, [], replaceData, false);
 881                        MergeNewData(temp.Item, id);
 882
 883                        refreshResult.UpdateType |= ItemUpdateType.MetadataDownload;
 884                    }
 885                    else
 886                    {
 887                        Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
 888                    }
 889                }
 890                catch (OperationCanceledException)
 891                {
 892                    throw;
 893                }
 894                catch (Exception ex)
 895                {
 896                    refreshResult.Failures++;
 897                    refreshResult.ErrorMessage = ex.Message;
 898                    Logger.LogError(ex, "Error in {Provider}", provider.Name);
 899                }
 900            }
 901
 902            return refreshResult;
 903        }
 904
 905        private void MergeNewData(TItemType source, TIdType lookupInfo)
 906        {
 907            // Copy new provider id's that may have been obtained
 98908            foreach (var providerId in source.ProviderIds)
 909            {
 0910                var key = providerId.Key;
 911
 912                // Don't replace existing Id's.
 0913                lookupInfo.ProviderIds.TryAdd(key, providerId.Value);
 914            }
 49915        }
 916
 917        private bool HasChanged(BaseItem item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService)
 918        {
 919            try
 920            {
 16921                var hasChanged = changeMonitor.HasChanged(item, directoryService);
 922
 16923                if (hasChanged)
 924                {
 16925                    Logger.LogDebug("{Monitor} reports change to {Item}", changeMonitor.GetType().Name, item.Path ?? ite
 926                }
 927
 16928                return hasChanged;
 929            }
 0930            catch (Exception ex)
 931            {
 0932                Logger.LogError(ex, "Error in {Monitor}.HasChanged", changeMonitor.GetType().Name);
 0933                return false;
 934            }
 16935        }
 936
 937        /// <summary>
 938        /// Merges metadata from source into target.
 939        /// </summary>
 940        /// <param name="source">The source for new metadata.</param>
 941        /// <param name="target">The target to insert new metadata into.</param>
 942        /// <param name="lockedFields">The fields that are locked and should not be updated.</param>
 943        /// <param name="replaceData"><c>true</c> if existing data should be replaced.</param>
 944        /// <param name="mergeMetadataSettings"><c>true</c> if the metadata settings in target should be updated to matc
 945        /// <exception cref="ArgumentException">Thrown if source or target are null.</exception>
 946        protected virtual void MergeData(
 947            MetadataResult<TItemType> source,
 948            MetadataResult<TItemType> target,
 949            MetadataField[] lockedFields,
 950            bool replaceData,
 951            bool mergeMetadataSettings)
 952        {
 0953            MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
 0954        }
 955
 956        internal static void MergeBaseItemData(
 957            MetadataResult<TItemType> sourceResult,
 958            MetadataResult<TItemType> targetResult,
 959            MetadataField[] lockedFields,
 960            bool replaceData,
 961            bool mergeMetadataSettings)
 962        {
 129963            var source = sourceResult.Item;
 129964            var target = targetResult.Item;
 965
 129966            ArgumentNullException.ThrowIfNull(sourceResult);
 129967            ArgumentNullException.ThrowIfNull(targetResult);
 968
 129969            if (!lockedFields.Contains(MetadataField.Name))
 970            {
 126971                if (replaceData || string.IsNullOrEmpty(target.Name))
 972                {
 973                    // Safeguard against incoming data having an empty name
 125974                    if (!string.IsNullOrWhiteSpace(source.Name))
 975                    {
 3976                        target.Name = source.Name;
 977                    }
 978                }
 979            }
 980
 129981            if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
 982            {
 128983                target.OriginalTitle = source.OriginalTitle;
 984            }
 985
 129986            if (replaceData || !target.CommunityRating.HasValue)
 987            {
 128988                target.CommunityRating = source.CommunityRating;
 989            }
 990
 129991            if (replaceData || !target.EndDate.HasValue)
 992            {
 128993                target.EndDate = source.EndDate;
 994            }
 995
 129996            if (!lockedFields.Contains(MetadataField.Genres))
 997            {
 127998                if (replaceData || target.Genres.Length == 0)
 999                {
 1261000                    target.Genres = source.Genres;
 1001                }
 1002            }
 1003
 1291004            if (replaceData || !target.IndexNumber.HasValue)
 1005            {
 1281006                target.IndexNumber = source.IndexNumber;
 1007            }
 1008
 1291009            if (!lockedFields.Contains(MetadataField.OfficialRating))
 1010            {
 1261011                if (replaceData || string.IsNullOrEmpty(target.OfficialRating))
 1012                {
 1251013                    target.OfficialRating = source.OfficialRating;
 1014                }
 1015            }
 1016
 1291017            if (replaceData || string.IsNullOrEmpty(target.CustomRating))
 1018            {
 1281019                target.CustomRating = source.CustomRating;
 1020            }
 1021
 1291022            if (replaceData || string.IsNullOrEmpty(target.Tagline))
 1023            {
 1281024                target.Tagline = source.Tagline;
 1025            }
 1026
 1291027            if (!lockedFields.Contains(MetadataField.Overview))
 1028            {
 1261029                if (replaceData || string.IsNullOrEmpty(target.Overview))
 1030                {
 1251031                    target.Overview = source.Overview;
 1032                }
 1033            }
 1034
 1291035            if (replaceData || !target.ParentIndexNumber.HasValue)
 1036            {
 1281037                target.ParentIndexNumber = source.ParentIndexNumber;
 1038            }
 1039
 1291040            if (!lockedFields.Contains(MetadataField.Cast))
 1041            {
 1281042                if (replaceData || targetResult.People is null || targetResult.People.Count == 0)
 1043                {
 1241044                    targetResult.People = sourceResult.People;
 1045                }
 41046                else if (sourceResult.People is not null && sourceResult.People.Count > 0)
 1047                {
 41048                    MergePeople(sourceResult.People, targetResult.People);
 1049                }
 1050            }
 1051
 1291052            if (replaceData || !target.PremiereDate.HasValue)
 1053            {
 1281054                target.PremiereDate = source.PremiereDate;
 1055            }
 1056
 1291057            if (replaceData || !target.ProductionYear.HasValue)
 1058            {
 1281059                target.ProductionYear = source.ProductionYear;
 1060            }
 1061
 1291062            if (!lockedFields.Contains(MetadataField.Runtime))
 1063            {
 1291064                if (replaceData || !target.RunTimeTicks.HasValue)
 1065                {
 1291066                    if (target is not Audio && target is not Video)
 1067                    {
 491068                        target.RunTimeTicks = source.RunTimeTicks;
 1069                    }
 1070                }
 1071            }
 1072
 1291073            if (!lockedFields.Contains(MetadataField.Studios))
 1074            {
 1271075                if (replaceData || target.Studios.Length == 0)
 1076                {
 1261077                    target.Studios = source.Studios;
 1078                }
 1079                else
 1080                {
 11081                    target.Studios = target.Studios.Concat(source.Studios).Distinct(StringComparer.OrdinalIgnoreCase).To
 1082                }
 1083            }
 1084
 1291085            if (!lockedFields.Contains(MetadataField.Tags))
 1086            {
 1271087                if (replaceData || target.Tags.Length == 0)
 1088                {
 1261089                    target.Tags = source.Tags;
 1090                }
 1091                else
 1092                {
 11093                    target.Tags = target.Tags.Concat(source.Tags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
 1094                }
 1095            }
 1096
 1291097            if (!lockedFields.Contains(MetadataField.ProductionLocations))
 1098            {
 1271099                if (replaceData || target.ProductionLocations.Length == 0)
 1100                {
 1261101                    target.ProductionLocations = source.ProductionLocations;
 1102                }
 1103                else
 1104                {
 11105                    target.ProductionLocations = target.ProductionLocations.Concat(source.ProductionLocations).Distinct(
 1106                }
 1107            }
 1108
 2661109            foreach (var id in source.ProviderIds)
 1110            {
 41111                var key = id.Key;
 1112
 1113                // Don't replace existing Id's.
 41114                if (replaceData)
 1115                {
 11116                    target.ProviderIds[key] = id.Value;
 1117                }
 1118                else
 1119                {
 31120                    target.ProviderIds.TryAdd(key, id.Value);
 1121                }
 1122            }
 1123
 1291124            if (replaceData || !target.CriticRating.HasValue)
 1125            {
 1281126                target.CriticRating = source.CriticRating;
 1127            }
 1128
 1291129            if (replaceData || target.RemoteTrailers.Count == 0)
 1130            {
 1281131                target.RemoteTrailers = source.RemoteTrailers;
 1132            }
 1133            else
 1134            {
 11135                target.RemoteTrailers = target.RemoteTrailers.Concat(source.RemoteTrailers).DistinctBy(t => t.Url).ToArr
 1136            }
 1137
 1291138            MergeAlbumArtist(source, target, replaceData);
 1291139            MergeVideoInfo(source, target, replaceData);
 1291140            MergeDisplayOrder(source, target, replaceData);
 1141
 1291142            if (replaceData || string.IsNullOrEmpty(target.ForcedSortName))
 1143            {
 1281144                var forcedSortName = source.ForcedSortName;
 1281145                if (!string.IsNullOrEmpty(forcedSortName))
 1146                {
 31147                    target.ForcedSortName = forcedSortName;
 1148                }
 1149            }
 1150
 1291151            if (mergeMetadataSettings)
 1152            {
 21153                if (replaceData || !target.IsLocked)
 1154                {
 21155                    target.IsLocked = target.IsLocked || source.IsLocked;
 1156                }
 1157
 21158                if (target.LockedFields.Length == 0)
 1159                {
 01160                    target.LockedFields = source.LockedFields;
 1161                }
 1162                else
 1163                {
 21164                    target.LockedFields = target.LockedFields.Concat(source.LockedFields).Distinct().ToArray();
 1165                }
 1166
 21167                if (source.DateCreated != default)
 1168                {
 11169                    target.DateCreated = source.DateCreated;
 1170                }
 1171
 21172                if (replaceData || source.DateModified != default)
 1173                {
 21174                    target.DateModified = source.DateModified;
 1175                }
 1176
 21177                if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataCountryCode))
 1178                {
 21179                    target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
 1180                }
 1181
 21182                if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataLanguage))
 1183                {
 21184                    target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
 1185                }
 1186            }
 1291187        }
 1188
 1189        private static void MergePeople(IReadOnlyList<PersonInfo> source, IReadOnlyList<PersonInfo> target)
 1190        {
 41191            var sourceByName = source.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase);
 41192            var targetByName = target.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase);
 1193
 161194            foreach (var name in targetByName.Select(g => g.Key))
 1195            {
 41196                var targetPeople = targetByName[name].ToArray();
 41197                var sourcePeople = sourceByName[name].ToArray();
 1198
 41199                if (sourcePeople.Length == 0)
 1200                {
 1201                    continue;
 1202                }
 1203
 121204                for (int i = 0; i < targetPeople.Length; i++)
 1205                {
 31206                    var person = targetPeople[i];
 31207                    var personInSource = i < sourcePeople.Length ? sourcePeople[i] : sourcePeople[0];
 1208
 101209                    foreach (var providerId in personInSource.ProviderIds)
 1210                    {
 21211                        person.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 1212                    }
 1213
 31214                    if (string.IsNullOrWhiteSpace(person.ImageUrl))
 1215                    {
 21216                        person.ImageUrl = personInSource.ImageUrl;
 1217                    }
 1218
 31219                    if (!string.IsNullOrWhiteSpace(personInSource.Role) && string.IsNullOrWhiteSpace(person.Role))
 1220                    {
 01221                        person.Role = personInSource.Role;
 1222                    }
 1223
 31224                    if (personInSource.SortOrder.HasValue && !person.SortOrder.HasValue)
 1225                    {
 01226                        person.SortOrder = personInSource.SortOrder;
 1227                    }
 1228                }
 1229            }
 41230        }
 1231
 1232        private static void MergeDisplayOrder(BaseItem source, BaseItem target, bool replaceData)
 1233        {
 1291234            if (source is IHasDisplayOrder sourceHasDisplayOrder
 1291235                && target is IHasDisplayOrder targetHasDisplayOrder)
 1236            {
 491237                if (replaceData || string.IsNullOrEmpty(targetHasDisplayOrder.DisplayOrder))
 1238                {
 481239                    var displayOrder = sourceHasDisplayOrder.DisplayOrder;
 481240                    if (!string.IsNullOrWhiteSpace(displayOrder))
 1241                    {
 31242                        targetHasDisplayOrder.DisplayOrder = displayOrder;
 1243                    }
 1244                }
 1245            }
 1291246        }
 1247
 1248        private static void MergeAlbumArtist(BaseItem source, BaseItem target, bool replaceData)
 1249        {
 1291250            if (source is IHasAlbumArtist sourceHasAlbumArtist
 1291251                && target is IHasAlbumArtist targetHasAlbumArtist)
 1252            {
 281253                if (replaceData || targetHasAlbumArtist.AlbumArtists.Count == 0)
 1254                {
 271255                    targetHasAlbumArtist.AlbumArtists = sourceHasAlbumArtist.AlbumArtists;
 1256                }
 11257                else if (sourceHasAlbumArtist.AlbumArtists.Count > 0)
 1258                {
 11259                    targetHasAlbumArtist.AlbumArtists = targetHasAlbumArtist.AlbumArtists.Concat(sourceHasAlbumArtist.Al
 1260                }
 1261            }
 1021262        }
 1263
 1264        private static void MergeVideoInfo(BaseItem source, BaseItem target, bool replaceData)
 1265        {
 1291266            if (source is Video sourceCast && target is Video targetCast)
 1267            {
 521268                if (replaceData || !targetCast.Video3DFormat.HasValue)
 1269                {
 511270                    targetCast.Video3DFormat = sourceCast.Video3DFormat;
 1271                }
 1272            }
 1291273        }
 1274    }
 1275}

Methods/Properties

.ctor(MediaBrowser.Controller.Configuration.IServerConfigurationManager,Microsoft.Extensions.Logging.ILogger`1<MediaBrowser.Providers.Manager.MetadataService`2<TItemType,TIdType>>,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.IO.IExternalDataManager)
get_EnableUpdatingPremiereDateFromChildren()
get_EnableUpdatingGenresFromChildren()
get_EnableUpdatingStudiosFromChildren()
get_EnableUpdatingOfficialRatingFromChildren()
get_Order()
TryGetFile(System.String,MediaBrowser.Controller.Providers.IDirectoryService)
ApplySearchResult(MediaBrowser.Controller.Providers.ItemLookupInfo,MediaBrowser.Model.Providers.RemoteSearchResult)
AfterMetadataRefresh(TItemType,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
BeforeSave(TItemType,System.Boolean,MediaBrowser.Controller.Library.ItemUpdateType)
BeforeSaveInternal(TItemType,System.Boolean,MediaBrowser.Controller.Library.ItemUpdateType)
EnableUpdateMetadataFromChildren(TItemType,System.Boolean,MediaBrowser.Controller.Library.ItemUpdateType)
GetChildrenForMetadataUpdates(TItemType)
UpdateMetadataFromChildren(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>,System.Boolean,MediaBrowser.Controller.Library.ItemUpdateType)
UpdateCumulativeRunTimeTicks(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdateDateLastMediaAdded(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdatePremiereDate(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdateGenres(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdateStudios(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdateOfficialRating(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
GetProviders(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.LibraryOptions,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Boolean,System.Boolean)
GetNonLocalImageProviders(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IImageProvider>,MediaBrowser.Controller.Providers.ImageRefreshOptions)
CanRefresh(MediaBrowser.Controller.Entities.BaseItem)
CanRefreshPrimary(System.Type)
CreateNew()
MergeNewData(TItemType,TIdType)
HasChanged(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IHasItemChangeMonitor,MediaBrowser.Controller.Providers.IDirectoryService)
MergeData(MediaBrowser.Controller.Providers.MetadataResult`1<TItemType>,MediaBrowser.Controller.Providers.MetadataResult`1<TItemType>,MediaBrowser.Model.Entities.MetadataField[],System.Boolean,System.Boolean)
MergeBaseItemData(MediaBrowser.Controller.Providers.MetadataResult`1<TItemType>,MediaBrowser.Controller.Providers.MetadataResult`1<TItemType>,MediaBrowser.Model.Entities.MetadataField[],System.Boolean,System.Boolean)
MergePeople(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.PersonInfo>,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.PersonInfo>)
MergeDisplayOrder(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem,System.Boolean)
MergeAlbumArtist(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem,System.Boolean)
MergeVideoInfo(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem,System.Boolean)