< 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: 235
Uncovered lines: 110
Coverable lines: 345
Total lines: 1291
Line coverage: 68.1%
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.Persistence;
 18using MediaBrowser.Controller.Providers;
 19using MediaBrowser.Model.Configuration;
 20using MediaBrowser.Model.Entities;
 21using MediaBrowser.Model.IO;
 22using MediaBrowser.Model.Providers;
 23using Microsoft.Extensions.Logging;
 24
 25namespace MediaBrowser.Providers.Manager
 26{
 27    public abstract class MetadataService<TItemType, TIdType> : IMetadataService
 28        where TItemType : BaseItem, IHasLookupInfo<TIdType>, new()
 29        where TIdType : ItemLookupInfo, new()
 30    {
 31        protected MetadataService(
 32            IServerConfigurationManager serverConfigurationManager,
 33            ILogger<MetadataService<TItemType, TIdType>> logger,
 34            IProviderManager providerManager,
 35            IFileSystem fileSystem,
 36            ILibraryManager libraryManager,
 37            IExternalDataManager externalDataManager,
 38            IItemRepository itemRepository)
 39        {
 54640            ServerConfigurationManager = serverConfigurationManager;
 54641            Logger = logger;
 54642            ProviderManager = providerManager;
 54643            FileSystem = fileSystem;
 54644            LibraryManager = libraryManager;
 54645            ExternalDataManager = externalDataManager;
 54646            ItemRepository = itemRepository;
 54647            ImageProvider = new ItemImageProvider(Logger, ProviderManager, FileSystem);
 54648        }
 49
 50        protected ItemImageProvider ImageProvider { get; }
 51
 52        protected IServerConfigurationManager ServerConfigurationManager { get; }
 53
 54        protected ILogger<MetadataService<TItemType, TIdType>> Logger { get; }
 55
 56        protected IProviderManager ProviderManager { get; }
 57
 58        protected IFileSystem FileSystem { get; }
 59
 60        protected ILibraryManager LibraryManager { get; }
 61
 62        protected IExternalDataManager ExternalDataManager { get; }
 63
 64        protected IItemRepository ItemRepository { get; }
 65
 3566        protected virtual bool EnableUpdatingPremiereDateFromChildren => false;
 67
 3568        protected virtual bool EnableUpdatingGenresFromChildren => false;
 69
 3570        protected virtual bool EnableUpdatingStudiosFromChildren => false;
 71
 3572        protected virtual bool EnableUpdatingOfficialRatingFromChildren => false;
 73
 50474        public virtual int Order => 0;
 75
 76        private FileSystemMetadata TryGetFileSystemMetadata(string path, IDirectoryService directoryService)
 77        {
 78            try
 79            {
 7080                return directoryService.GetFileSystemEntry(path);
 81            }
 082            catch (Exception ex)
 83            {
 084                Logger.LogError(ex, "Error getting file {Path}", path);
 085                return null;
 86            }
 7087        }
 88
 89        public virtual async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, 
 90        {
 91            var itemOfType = (TItemType)item;
 92            var updateType = ItemUpdateType.None;
 93
 94            var libraryOptions = LibraryManager.GetLibraryOptions(item);
 95            var isFirstRefresh = item.DateLastRefreshed == DateTime.MinValue;
 96            var hasRefreshedMetadata = true;
 97            var hasRefreshedImages = true;
 98
 99            var requiresRefresh = libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRef
 100
 101            if (!requiresRefresh && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
 102            {
 103                // TODO: If this returns true, should we instead just change metadata refresh mode to Full?
 104                requiresRefresh = item.RequiresRefresh();
 105
 106                if (requiresRefresh)
 107                {
 108                    Logger.LogDebug("Refreshing {Type} {Item} because item.RequiresRefresh() returned true", typeof(TIte
 109                }
 110            }
 111
 112            if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages)
 113            {
 114                if (ImageProvider.RemoveImages(item))
 115                {
 116                    updateType |= ItemUpdateType.ImageUpdate;
 117                }
 118            }
 119
 120            var localImagesFailed = false;
 121            var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
 122
 123            // Only validate already registered images if we are replacing and saving locally
 124            if (item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages)
 125            {
 126                item.ValidateImages();
 127            }
 128            else
 129            {
 130                // Run full image validation and register new local images
 131                try
 132                {
 133                    if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptio
 134                    {
 135                        updateType |= ItemUpdateType.ImageUpdate;
 136                    }
 137                }
 138                catch (Exception ex)
 139                {
 140                    localImagesFailed = true;
 141                    Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
 142                }
 143            }
 144
 145            var metadataResult = new MetadataResult<TItemType>
 146            {
 147                Item = itemOfType
 148            };
 149
 150            var beforeSaveResult = await BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || r
 151                .ConfigureAwait(false);
 152            updateType |= beforeSaveResult;
 153
 154            updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataR
 155
 156            // Next run metadata providers
 157            if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
 158            {
 159                var providers = GetProviders(item, libraryOptions, refreshOptions, isFirstRefresh, requiresRefresh)
 160                    .ToList();
 161
 162                if (providers.Count > 0 || isFirstRefresh || requiresRefresh)
 163                {
 164                    if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata))
 165                    {
 166                        updateType |= ItemUpdateType.MetadataImport;
 167                    }
 168                }
 169
 170                if (providers.Count > 0)
 171                {
 172                    var id = itemOfType.GetLookupInfo();
 173
 174                    if (refreshOptions.SearchResult is not null)
 175                    {
 176                        ApplySearchResult(id, refreshOptions.SearchResult);
 177                    }
 178
 179                    id.IsAutomated = refreshOptions.IsAutomated;
 180
 181                    var hasMetadataSavers = ProviderManager.GetMetadataSavers(item, libraryOptions).Any();
 182                    var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider
 183
 184                    updateType |= result.UpdateType;
 185                    if (result.Failures > 0)
 186                    {
 187                        hasRefreshedMetadata = false;
 188                    }
 189                }
 190            }
 191
 192            // Next run remote image providers, but only if local image providers didn't throw an exception
 193            if (!localImagesFailed && refreshOptions.ImageRefreshMode > MetadataRefreshMode.ValidationOnly)
 194            {
 195                var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList();
 196
 197                if (providers.Count > 0)
 198                {
 199                    var result = await ImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions
 200
 201                    updateType |= result.UpdateType;
 202                    if (result.Failures > 0)
 203                    {
 204                        hasRefreshedImages = false;
 205                    }
 206                }
 207            }
 208
 209            if (hasRefreshedMetadata && hasRefreshedImages)
 210            {
 211                item.DateLastRefreshed = DateTime.UtcNow;
 212                updateType |= item.OnMetadataChanged();
 213            }
 214
 215            updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataR
 216
 217            await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
 218
 219            return updateType;
 220
 221            async Task<ItemUpdateType> SaveInternal(BaseItem item, MetadataRefreshOptions refreshOptions, ItemUpdateType
 222            {
 223                // Save if changes were made, or it's never been saved before
 224                if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.Rep
 225                {
 226                    if (item.IsFileProtocol)
 227                    {
 228                        var file = TryGetFileSystemMetadata(item.Path, refreshOptions.DirectoryService);
 229                        if (file is not null)
 230                        {
 231                            item.DateModified = file.LastWriteTimeUtc;
 232                        }
 233                    }
 234
 235                    // If any of these properties are set then make sure the updateType is not None, just to force every
 236                    if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
 237                    {
 238                        updateType |= ItemUpdateType.MetadataDownload;
 239                    }
 240
 241                    // Save to database
 242                    await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
 243                }
 244
 245                return updateType;
 246            }
 247        }
 248
 249        private void ApplySearchResult(ItemLookupInfo lookupInfo, RemoteSearchResult result)
 250        {
 251            // Episode and Season do not support Identify, so the search results are the Series'
 252            switch (lookupInfo)
 253            {
 254                case EpisodeInfo episodeInfo:
 0255                    episodeInfo.SeriesProviderIds = result.ProviderIds;
 0256                    episodeInfo.ProviderIds.Clear();
 0257                    break;
 258                case SeasonInfo seasonInfo:
 0259                    seasonInfo.SeriesProviderIds = result.ProviderIds;
 0260                    seasonInfo.ProviderIds.Clear();
 0261                    break;
 262                default:
 0263                    lookupInfo.ProviderIds = result.ProviderIds;
 0264                    lookupInfo.Name = result.Name;
 0265                    lookupInfo.Year = result.ProductionYear;
 266                    break;
 267            }
 0268        }
 269
 270        protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken ca
 271        {
 272            await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false);
 273            if (result.Item.SupportsPeople && result.People is not null)
 274            {
 275                var baseItem = result.Item;
 276
 277                await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false)
 278            }
 279        }
 280
 281        protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationT
 282        {
 55283            item.AfterMetadataRefresh();
 55284            return Task.CompletedTask;
 285        }
 286
 287        /// <summary>
 288        /// Before the save.
 289        /// </summary>
 290        /// <param name="item">The item.</param>
 291        /// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param>
 292        /// <param name="currentUpdateType">Type of the current update.</param>
 293        /// <returns>ItemUpdateType.</returns>
 294        private async Task<ItemUpdateType> BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateTy
 295        {
 296            var updateType = BeforeSaveInternal(item, isFullRefresh, currentUpdateType);
 297
 298            updateType |= item.OnMetadataChanged();
 299
 300            if (updateType == ItemUpdateType.None)
 301            {
 302                if (!await ItemRepository.ItemExistsAsync(item.Id).ConfigureAwait(false))
 303                {
 304                    return ItemUpdateType.MetadataImport;
 305                }
 306            }
 307
 308            return updateType;
 309        }
 310
 311        protected virtual ItemUpdateType BeforeSaveInternal(TItemType item, bool isFullRefresh, ItemUpdateType updateTyp
 312        {
 57313            if (EnableUpdateMetadataFromChildren(item, isFullRefresh, updateType))
 314            {
 0315                if (isFullRefresh || updateType > ItemUpdateType.None)
 316                {
 0317                    var children = GetChildrenForMetadataUpdates(item);
 318
 0319                    updateType = UpdateMetadataFromChildren(item, children, isFullRefresh, updateType);
 320                }
 321            }
 322
 57323            var presentationUniqueKey = item.CreatePresentationUniqueKey();
 57324            if (!string.Equals(item.PresentationUniqueKey, presentationUniqueKey, StringComparison.Ordinal))
 325            {
 30326                item.PresentationUniqueKey = presentationUniqueKey;
 30327                updateType |= ItemUpdateType.MetadataImport;
 328            }
 329
 330            // Cleanup extracted files if source file was modified
 57331            var itemPath = item.Path;
 57332            if (!string.IsNullOrEmpty(itemPath))
 333            {
 57334                var info = FileSystem.GetFileSystemInfo(itemPath);
 57335                var modificationDate = info.LastWriteTimeUtc;
 57336                var itemLastModifiedFileSystem = item.DateModified;
 57337                if (info.Exists && itemLastModifiedFileSystem != modificationDate)
 338                {
 3339                    Logger.LogDebug("File modification time changed from {Then} to {Now}: {Path}", itemLastModifiedFileS
 340
 3341                    item.DateModified = modificationDate;
 3342                    if (ServerConfigurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded)
 343                    {
 3344                        item.DateCreated = info.CreationTimeUtc;
 345                    }
 346
 3347                    if (item is Video video)
 348                    {
 0349                        Logger.LogInformation("File changed, pruning extracted data: {Path}", item.Path);
 0350                        ExternalDataManager.DeleteExternalItemDataAsync(video, CancellationToken.None).GetAwaiter().GetR
 351                    }
 352
 3353                    updateType |= ItemUpdateType.MetadataImport;
 354                }
 355            }
 356
 57357            return updateType;
 358        }
 359
 360        protected virtual bool EnableUpdateMetadataFromChildren(TItemType item, bool isFullRefresh, ItemUpdateType curre
 361        {
 57362            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
 363            {
 35364                if (EnableUpdatingPremiereDateFromChildren || EnableUpdatingGenresFromChildren || EnableUpdatingStudiosF
 365                {
 0366                    return true;
 367                }
 368
 35369                if (item is Folder folder)
 370                {
 35371                    return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks;
 372                }
 373            }
 374
 22375            return false;
 376        }
 377
 378        protected virtual IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(TItemType item)
 379        {
 0380            if (item is Folder folder)
 381            {
 0382                return folder.GetRecursiveChildren();
 383            }
 384
 0385            return [];
 386        }
 387
 388        protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IReadOnlyList<BaseItem> children, bo
 389        {
 0390            var updateType = ItemUpdateType.None;
 391
 0392            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
 393            {
 0394                updateType |= UpdateCumulativeRunTimeTicks(item, children);
 0395                updateType |= UpdateDateLastMediaAdded(item, children);
 396
 397                // don't update user-changeable metadata for locked items
 0398                if (item.IsLocked)
 399                {
 0400                    return updateType;
 401                }
 402
 0403                if (EnableUpdatingPremiereDateFromChildren)
 404                {
 0405                    updateType |= UpdatePremiereDate(item, children);
 406                }
 407
 0408                if (EnableUpdatingGenresFromChildren)
 409                {
 0410                    updateType |= UpdateGenres(item, children);
 411                }
 412
 0413                if (EnableUpdatingStudiosFromChildren)
 414                {
 0415                    updateType |= UpdateStudios(item, children);
 416                }
 417
 0418                if (EnableUpdatingOfficialRatingFromChildren)
 419                {
 0420                    updateType |= UpdateOfficialRating(item, children);
 421                }
 422            }
 423
 0424            return updateType;
 425        }
 426
 427        private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IReadOnlyList<BaseItem> children)
 428        {
 0429            if (item is Folder folder && folder.SupportsCumulativeRunTimeTicks)
 430            {
 0431                long ticks = 0;
 432
 0433                foreach (var child in children)
 434                {
 0435                    if (!child.IsFolder)
 436                    {
 0437                        ticks += child.RunTimeTicks ?? 0;
 438                    }
 439                }
 440
 0441                if (!folder.RunTimeTicks.HasValue || folder.RunTimeTicks.Value != ticks)
 442                {
 0443                    folder.RunTimeTicks = ticks;
 0444                    return ItemUpdateType.MetadataImport;
 445                }
 446            }
 447
 0448            return ItemUpdateType.None;
 449        }
 450
 451        private ItemUpdateType UpdateDateLastMediaAdded(TItemType item, IReadOnlyList<BaseItem> children)
 452        {
 0453            var updateType = ItemUpdateType.None;
 454
 0455            if (item is Folder folder && folder.SupportsDateLastMediaAdded)
 456            {
 0457                var dateLastMediaAdded = DateTime.MinValue;
 0458                var any = false;
 459
 0460                foreach (var child in children)
 461                {
 462                    // Exclude any folders and virtual items since they are only placeholders
 0463                    if (!child.IsFolder && !child.IsVirtualItem)
 464                    {
 0465                        var childDateCreated = child.DateCreated;
 0466                        if (childDateCreated > dateLastMediaAdded)
 467                        {
 0468                            dateLastMediaAdded = childDateCreated;
 469                        }
 470
 0471                        any = true;
 472                    }
 473                }
 474
 0475                if ((!folder.DateLastMediaAdded.HasValue && any) || folder.DateLastMediaAdded != dateLastMediaAdded)
 476                {
 0477                    folder.DateLastMediaAdded = dateLastMediaAdded;
 0478                    updateType = ItemUpdateType.MetadataImport;
 479                }
 480            }
 481
 0482            return updateType;
 483        }
 484
 485        private ItemUpdateType UpdatePremiereDate(TItemType item, IReadOnlyList<BaseItem> children)
 486        {
 0487            var updateType = ItemUpdateType.None;
 488
 0489            if (children.Count == 0)
 490            {
 0491                return updateType;
 492            }
 493
 0494            var date = children.Select(i => i.PremiereDate ?? DateTime.MaxValue).Min();
 495
 0496            var originalPremiereDate = item.PremiereDate;
 0497            var originalProductionYear = item.ProductionYear;
 498
 0499            if (date > DateTime.MinValue && date < DateTime.MaxValue)
 500            {
 0501                item.PremiereDate = date;
 0502                item.ProductionYear = date.Year;
 503            }
 504            else
 505            {
 0506                var year = children.Select(i => i.ProductionYear ?? 0).Min();
 507
 0508                if (year > 0)
 509                {
 0510                    item.ProductionYear = year;
 511                }
 512            }
 513
 0514            if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue)
 0515                || (originalProductionYear ?? -1) != (item.ProductionYear ?? -1))
 516            {
 0517                updateType |= ItemUpdateType.MetadataEdit;
 518            }
 519
 0520            return updateType;
 521        }
 522
 523        private ItemUpdateType UpdateGenres(TItemType item, IReadOnlyList<BaseItem> children)
 524        {
 0525            var updateType = ItemUpdateType.None;
 526
 0527            if (!item.LockedFields.Contains(MetadataField.Genres))
 528            {
 0529                var currentList = item.Genres;
 530
 0531                item.Genres = children.SelectMany(i => i.Genres)
 0532                    .Distinct(StringComparer.OrdinalIgnoreCase)
 0533                    .ToArray();
 534
 0535                if (currentList.Length != item.Genres.Length || !currentList.Order().SequenceEqual(item.Genres.Order(), 
 536                {
 0537                    updateType |= ItemUpdateType.MetadataEdit;
 538                }
 539            }
 540
 0541            return updateType;
 542        }
 543
 544        private ItemUpdateType UpdateStudios(TItemType item, IReadOnlyList<BaseItem> children)
 545        {
 0546            var updateType = ItemUpdateType.None;
 547
 0548            if (!item.LockedFields.Contains(MetadataField.Studios))
 549            {
 0550                var currentList = item.Studios;
 551
 0552                item.Studios = children.SelectMany(i => i.Studios)
 0553                    .Distinct(StringComparer.OrdinalIgnoreCase)
 0554                    .ToArray();
 555
 0556                if (currentList.Length != item.Studios.Length || !currentList.Order().SequenceEqual(item.Studios.Order()
 557                {
 0558                    updateType |= ItemUpdateType.MetadataEdit;
 559                }
 560            }
 561
 0562            return updateType;
 563        }
 564
 565        private ItemUpdateType UpdateOfficialRating(TItemType item, IReadOnlyList<BaseItem> children)
 566        {
 0567            var updateType = ItemUpdateType.None;
 568
 0569            if (!item.LockedFields.Contains(MetadataField.OfficialRating))
 570            {
 0571                if (item.UpdateRatingToItems(children))
 572                {
 0573                    updateType |= ItemUpdateType.MetadataEdit;
 574                }
 575            }
 576
 0577            return updateType;
 578        }
 579
 580        /// <summary>
 581        /// Gets the providers.
 582        /// </summary>
 583        /// <param name="item">A media item.</param>
 584        /// <param name="libraryOptions">The LibraryOptions to use.</param>
 585        /// <param name="options">The MetadataRefreshOptions to use.</param>
 586        /// <param name="isFirstRefresh">Specifies first refresh mode.</param>
 587        /// <param name="requiresRefresh">Specifies refresh mode.</param>
 588        /// <returns>IEnumerable{`0}.</returns>
 589        protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefr
 590        {
 591            // Get providers to refresh
 57592            var providers = ProviderManager.GetMetadataProviders<TItemType>(item, libraryOptions).ToList();
 593
 57594            var metadataRefreshMode = options.MetadataRefreshMode;
 595
 596            // Run all if either of these flags are true
 57597            var runAllProviders = options.ReplaceAllMetadata ||
 57598                metadataRefreshMode == MetadataRefreshMode.FullRefresh ||
 57599                (isFirstRefresh && metadataRefreshMode >= MetadataRefreshMode.Default) ||
 57600                (requiresRefresh && metadataRefreshMode >= MetadataRefreshMode.Default);
 601
 57602            if (!runAllProviders)
 603            {
 22604                var providersWithChanges = providers
 22605                    .Where(i =>
 22606                    {
 22607                        if (i is IHasItemChangeMonitor hasFileChangeMonitor)
 22608                        {
 22609                            return HasChanged(item, hasFileChangeMonitor, options.DirectoryService);
 22610                        }
 22611
 22612                        return false;
 22613                    })
 22614                    .ToList();
 615
 22616                if (providersWithChanges.Count == 0)
 617                {
 0618                    providers = new List<IMetadataProvider<TItemType>>();
 619                }
 620                else
 621                {
 22622                    var anyRemoteProvidersChanged = providersWithChanges.OfType<IRemoteMetadataProvider>()
 22623                        .Any();
 624
 22625                    var anyLocalProvidersChanged = providersWithChanges.OfType<ILocalMetadataProvider>()
 22626                        .Any();
 627
 22628                    var anyLocalPreRefreshProvidersChanged = providersWithChanges.OfType<IPreRefreshProvider>()
 22629                        .Any();
 630
 22631                    providers = providers.Where(i =>
 22632                    {
 22633                        // If any provider reports a change, always run local ones as well
 22634                        if (i is ILocalMetadataProvider)
 22635                        {
 22636                            return anyRemoteProvidersChanged || anyLocalProvidersChanged || anyLocalPreRefreshProvidersC
 22637                        }
 22638
 22639                        // If any remote providers changed, run them all so that priorities can be honored
 22640                        if (i is IRemoteMetadataProvider)
 22641                        {
 22642                            if (options.MetadataRefreshMode == MetadataRefreshMode.ValidationOnly)
 22643                            {
 22644                                return false;
 22645                            }
 22646
 22647                            return anyRemoteProvidersChanged;
 22648                        }
 22649
 22650                        // Run custom refresh providers if they report a change or any remote providers change
 22651                        return anyRemoteProvidersChanged || providersWithChanges.Contains(i);
 22652                    }).ToList();
 653                }
 654            }
 655
 57656            return providers;
 657        }
 658
 659        protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(BaseItem item, IEnumerable<IImageProvide
 660        {
 661            // Get providers to refresh
 57662            var providers = allImageProviders.Where(i => i is not ILocalImageProvider);
 663
 57664            var dateLastImageRefresh = item.DateLastRefreshed;
 665
 666            // Run all if either of these flags are true
 57667            var runAllProviders = options.ImageRefreshMode == MetadataRefreshMode.FullRefresh || dateLastImageRefresh.Da
 668
 57669            if (!runAllProviders)
 670            {
 27671                providers = providers
 27672                    .Where(i =>
 27673                    {
 27674                        if (i is IHasItemChangeMonitor hasFileChangeMonitor)
 27675                        {
 27676                            return HasChanged(item, hasFileChangeMonitor, options.DirectoryService);
 27677                        }
 27678
 27679                        return false;
 27680                    });
 681            }
 682
 57683            return providers;
 684        }
 685
 686        public bool CanRefresh(BaseItem item)
 687        {
 1040688            return item is TItemType;
 689        }
 690
 691        public bool CanRefreshPrimary(Type type)
 692        {
 1363693            return type == typeof(TItemType);
 694        }
 695
 696        protected virtual async Task<RefreshResult> RefreshWithProviders(
 697            MetadataResult<TItemType> metadata,
 698            TIdType id,
 699            MetadataRefreshOptions options,
 700            ICollection<IMetadataProvider> providers,
 701            ItemImageProvider imageService,
 702            bool isSavingMetadata,
 703            CancellationToken cancellationToken)
 704        {
 705            var refreshResult = new RefreshResult
 706            {
 707                UpdateType = ItemUpdateType.None
 708            };
 709
 710            var item = metadata.Item;
 711
 712            var customProviders = providers.OfType<ICustomMetadataProvider<TItemType>>().ToList();
 713            var logName = !item.IsFileProtocol ? item.Name ?? item.Path : item.Path ?? item.Name;
 714
 715            foreach (var provider in customProviders.Where(i => i is IPreRefreshProvider))
 716            {
 717                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwa
 718            }
 719
 720            if (item.IsLocked)
 721            {
 722                return refreshResult;
 723            }
 724
 725            var temp = new MetadataResult<TItemType>
 726            {
 727                Item = CreateNew()
 728            };
 729            temp.Item.Path = item.Path;
 730            temp.Item.Id = item.Id;
 731            temp.Item.ParentIndexNumber = item.ParentIndexNumber;
 732            temp.Item.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 733            temp.Item.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 734
 735            var foundImageTypes = new List<ImageType>();
 736
 737            // Do not execute local providers if we are identifying or replacing with local metadata saving enabled
 738            if (options.SearchResult is null && !(isSavingMetadata && options.ReplaceAllMetadata))
 739            {
 740                foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
 741                {
 742                    var providerName = provider.GetType().Name;
 743                    Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
 744
 745                    var itemInfo = new ItemInfo(item);
 746
 747                    try
 748                    {
 749                        var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken
 750
 751                        if (localItem.HasMetadata)
 752                        {
 753                            foreach (var remoteImage in localItem.RemoteImages)
 754                            {
 755                                try
 756                                {
 757                                    if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
 758                                        && !options.IsReplacingImage(remoteImage.Type))
 759                                    {
 760                                        continue;
 761                                    }
 762
 763                                    await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cance
 764                                    refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
 765
 766                                    // remember imagetype that has just been downloaded
 767                                    foundImageTypes.Add(remoteImage.Type);
 768                                }
 769                                catch (HttpRequestException ex)
 770                                {
 771                                    Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteIm
 772                                }
 773                            }
 774
 775                            if (foundImageTypes.Count > 0)
 776                            {
 777                                imageService.UpdateReplaceImages(options, foundImageTypes);
 778                            }
 779
 780                            if (imageService.MergeImages(item, localItem.Images, options))
 781                            {
 782                                refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
 783                            }
 784
 785                            MergeData(localItem, temp, [], false, true);
 786                            refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
 787
 788                            break;
 789                        }
 790
 791                        Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
 792                    }
 793                    catch (OperationCanceledException)
 794                    {
 795                        throw;
 796                    }
 797                    catch (Exception ex)
 798                    {
 799                        Logger.LogError(ex, "Error in {Provider}", provider.Name);
 800
 801                        // If a local provider fails, consider that a failure
 802                        refreshResult.ErrorMessage = ex.Message;
 803                    }
 804                }
 805            }
 806
 807            var isLocalLocked = temp.Item.IsLocked;
 808            if (!isLocalLocked && (options.ReplaceAllMetadata || options.MetadataRefreshMode > MetadataRefreshMode.Valid
 809            {
 810                var remoteResult = await ExecuteRemoteProviders(temp, logName, false, id, providers.OfType<IRemoteMetada
 811                    .ConfigureAwait(false);
 812
 813                refreshResult.UpdateType |= remoteResult.UpdateType;
 814                refreshResult.ErrorMessage = remoteResult.ErrorMessage;
 815                refreshResult.Failures += remoteResult.Failures;
 816            }
 817
 818            if (providers.Any(i => i is not ICustomMetadataProvider))
 819            {
 820                if (refreshResult.UpdateType > ItemUpdateType.None)
 821                {
 822                    if (!options.RemoveOldMetadata)
 823                    {
 824                        // Add existing metadata to provider result if it does not exist there
 825                        MergeData(metadata, temp, [], false, false);
 826                    }
 827
 828                    if (isLocalLocked)
 829                    {
 830                        MergeData(temp, metadata, item.LockedFields, true, true);
 831                    }
 832                    else
 833                    {
 834                        var shouldReplace = (options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly && options
 835                            // Case for Scan for new and updated files
 836                            || (options.MetadataRefreshMode == MetadataRefreshMode.Default && !options.ReplaceAllMetadat
 837                        MergeData(temp, metadata, item.LockedFields, shouldReplace, true);
 838                    }
 839                }
 840            }
 841
 842            foreach (var provider in customProviders.Where(i => i is not IPreRefreshProvider))
 843            {
 844                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwa
 845            }
 846
 847            return refreshResult;
 848        }
 849
 850        private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, string logName
 851        {
 852            Logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, logName);
 853
 854            try
 855            {
 856                refreshResult.UpdateType |= await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(f
 857            }
 858            catch (OperationCanceledException)
 859            {
 860                throw;
 861            }
 862            catch (Exception ex)
 863            {
 864                refreshResult.ErrorMessage = ex.Message;
 865                Logger.LogError(ex, "Error in {Provider}", provider.Name);
 866            }
 867        }
 868
 869        protected virtual TItemType CreateNew()
 870        {
 57871            return new TItemType();
 872        }
 873
 874        private async Task<RefreshResult> ExecuteRemoteProviders(MetadataResult<TItemType> temp, string logName, bool re
 875        {
 876            var refreshResult = new RefreshResult();
 877
 878            if (id is not null)
 879            {
 880                MergeNewData(temp.Item, id);
 881            }
 882
 883            foreach (var provider in providers)
 884            {
 885                var providerName = provider.GetType().Name;
 886                Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
 887
 888                try
 889                {
 890                    var result = await provider.GetMetadata(id, cancellationToken).ConfigureAwait(false);
 891
 892                    if (result.HasMetadata)
 893                    {
 894                        result.Provider = provider.Name;
 895
 896                        MergeData(result, temp, [], replaceData, false);
 897                        MergeNewData(temp.Item, id);
 898
 899                        refreshResult.UpdateType |= ItemUpdateType.MetadataDownload;
 900                    }
 901                    else
 902                    {
 903                        Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
 904                    }
 905                }
 906                catch (OperationCanceledException)
 907                {
 908                    throw;
 909                }
 910                catch (Exception ex)
 911                {
 912                    refreshResult.Failures++;
 913                    refreshResult.ErrorMessage = ex.Message;
 914                    Logger.LogError(ex, "Error in {Provider}", provider.Name);
 915                }
 916            }
 917
 918            return refreshResult;
 919        }
 920
 921        private void MergeNewData(TItemType source, TIdType lookupInfo)
 922        {
 923            // Copy new provider id's that may have been obtained
 114924            foreach (var providerId in source.ProviderIds)
 925            {
 0926                var key = providerId.Key;
 927
 928                // Don't replace existing Id's.
 0929                lookupInfo.ProviderIds.TryAdd(key, providerId.Value);
 930            }
 57931        }
 932
 933        private bool HasChanged(BaseItem item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService)
 934        {
 935            try
 936            {
 22937                var hasChanged = changeMonitor.HasChanged(item, directoryService);
 938
 22939                if (hasChanged)
 940                {
 22941                    Logger.LogDebug("{Monitor} reports change to {Item}", changeMonitor.GetType().Name, item.Path ?? ite
 942                }
 943
 22944                return hasChanged;
 945            }
 0946            catch (Exception ex)
 947            {
 0948                Logger.LogError(ex, "Error in {Monitor}.HasChanged", changeMonitor.GetType().Name);
 0949                return false;
 950            }
 22951        }
 952
 953        /// <summary>
 954        /// Merges metadata from source into target.
 955        /// </summary>
 956        /// <param name="source">The source for new metadata.</param>
 957        /// <param name="target">The target to insert new metadata into.</param>
 958        /// <param name="lockedFields">The fields that are locked and should not be updated.</param>
 959        /// <param name="replaceData"><c>true</c> if existing data should be replaced.</param>
 960        /// <param name="mergeMetadataSettings"><c>true</c> if the metadata settings in target should be updated to matc
 961        /// <exception cref="ArgumentException">Thrown if source or target are null.</exception>
 962        protected virtual void MergeData(
 963            MetadataResult<TItemType> source,
 964            MetadataResult<TItemType> target,
 965            MetadataField[] lockedFields,
 966            bool replaceData,
 967            bool mergeMetadataSettings)
 968        {
 0969            MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
 0970        }
 971
 972        internal static void MergeBaseItemData(
 973            MetadataResult<TItemType> sourceResult,
 974            MetadataResult<TItemType> targetResult,
 975            MetadataField[] lockedFields,
 976            bool replaceData,
 977            bool mergeMetadataSettings)
 978        {
 129979            var source = sourceResult.Item;
 129980            var target = targetResult.Item;
 981
 129982            ArgumentNullException.ThrowIfNull(sourceResult);
 129983            ArgumentNullException.ThrowIfNull(targetResult);
 984
 129985            if (!lockedFields.Contains(MetadataField.Name))
 986            {
 126987                if (replaceData || string.IsNullOrEmpty(target.Name))
 988                {
 989                    // Safeguard against incoming data having an empty name
 125990                    if (!string.IsNullOrWhiteSpace(source.Name))
 991                    {
 3992                        target.Name = source.Name;
 993                    }
 994                }
 995            }
 996
 129997            if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
 998            {
 128999                target.OriginalTitle = source.OriginalTitle;
 1000            }
 1001
 1291002            if (replaceData || !target.CommunityRating.HasValue)
 1003            {
 1281004                target.CommunityRating = source.CommunityRating;
 1005            }
 1006
 1291007            if (replaceData || !target.EndDate.HasValue)
 1008            {
 1281009                target.EndDate = source.EndDate;
 1010            }
 1011
 1291012            if (!lockedFields.Contains(MetadataField.Genres))
 1013            {
 1271014                if (replaceData || target.Genres.Length == 0)
 1015                {
 1261016                    target.Genres = source.Genres;
 1017                }
 1018            }
 1019
 1291020            if (replaceData || !target.IndexNumber.HasValue)
 1021            {
 1281022                target.IndexNumber = source.IndexNumber;
 1023            }
 1024
 1291025            if (!lockedFields.Contains(MetadataField.OfficialRating))
 1026            {
 1261027                if (replaceData || string.IsNullOrEmpty(target.OfficialRating))
 1028                {
 1251029                    target.OfficialRating = source.OfficialRating;
 1030                }
 1031            }
 1032
 1291033            if (replaceData || string.IsNullOrEmpty(target.CustomRating))
 1034            {
 1281035                target.CustomRating = source.CustomRating;
 1036            }
 1037
 1291038            if (replaceData || string.IsNullOrEmpty(target.Tagline))
 1039            {
 1281040                target.Tagline = source.Tagline;
 1041            }
 1042
 1291043            if (!lockedFields.Contains(MetadataField.Overview))
 1044            {
 1261045                if (replaceData || string.IsNullOrEmpty(target.Overview))
 1046                {
 1251047                    target.Overview = source.Overview;
 1048                }
 1049            }
 1050
 1291051            if (replaceData || !target.ParentIndexNumber.HasValue)
 1052            {
 1281053                target.ParentIndexNumber = source.ParentIndexNumber;
 1054            }
 1055
 1291056            if (!lockedFields.Contains(MetadataField.Cast))
 1057            {
 1281058                if (replaceData || targetResult.People is null || targetResult.People.Count == 0)
 1059                {
 1241060                    targetResult.People = sourceResult.People;
 1061                }
 41062                else if (sourceResult.People is not null && sourceResult.People.Count > 0)
 1063                {
 41064                    MergePeople(sourceResult.People, targetResult.People);
 1065                }
 1066            }
 1067
 1291068            if (replaceData || !target.PremiereDate.HasValue)
 1069            {
 1281070                target.PremiereDate = source.PremiereDate;
 1071            }
 1072
 1291073            if (replaceData || !target.ProductionYear.HasValue)
 1074            {
 1281075                target.ProductionYear = source.ProductionYear;
 1076            }
 1077
 1291078            if (!lockedFields.Contains(MetadataField.Runtime))
 1079            {
 1291080                if (replaceData || !target.RunTimeTicks.HasValue)
 1081                {
 1291082                    if (target is not Audio && target is not Video)
 1083                    {
 491084                        target.RunTimeTicks = source.RunTimeTicks;
 1085                    }
 1086                }
 1087            }
 1088
 1291089            if (!lockedFields.Contains(MetadataField.Studios))
 1090            {
 1271091                if (replaceData || target.Studios.Length == 0)
 1092                {
 1261093                    target.Studios = source.Studios;
 1094                }
 1095                else
 1096                {
 11097                    target.Studios = target.Studios.Concat(source.Studios).Distinct(StringComparer.OrdinalIgnoreCase).To
 1098                }
 1099            }
 1100
 1291101            if (!lockedFields.Contains(MetadataField.Tags))
 1102            {
 1271103                if (replaceData || target.Tags.Length == 0)
 1104                {
 1261105                    target.Tags = source.Tags;
 1106                }
 1107                else
 1108                {
 11109                    target.Tags = target.Tags.Concat(source.Tags).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
 1110                }
 1111            }
 1112
 1291113            if (!lockedFields.Contains(MetadataField.ProductionLocations))
 1114            {
 1271115                if (replaceData || target.ProductionLocations.Length == 0)
 1116                {
 1261117                    target.ProductionLocations = source.ProductionLocations;
 1118                }
 1119                else
 1120                {
 11121                    target.ProductionLocations = target.ProductionLocations.Concat(source.ProductionLocations).Distinct(
 1122                }
 1123            }
 1124
 2661125            foreach (var id in source.ProviderIds)
 1126            {
 41127                var key = id.Key;
 1128
 1129                // Don't replace existing Id's.
 41130                if (replaceData)
 1131                {
 11132                    target.ProviderIds[key] = id.Value;
 1133                }
 1134                else
 1135                {
 31136                    target.ProviderIds.TryAdd(key, id.Value);
 1137                }
 1138            }
 1139
 1291140            if (replaceData || !target.CriticRating.HasValue)
 1141            {
 1281142                target.CriticRating = source.CriticRating;
 1143            }
 1144
 1291145            if (replaceData || target.RemoteTrailers.Count == 0)
 1146            {
 1281147                target.RemoteTrailers = source.RemoteTrailers;
 1148            }
 1149            else
 1150            {
 11151                target.RemoteTrailers = target.RemoteTrailers.Concat(source.RemoteTrailers).DistinctBy(t => t.Url).ToArr
 1152            }
 1153
 1291154            MergeAlbumArtist(source, target, replaceData);
 1291155            MergeVideoInfo(source, target, replaceData);
 1291156            MergeDisplayOrder(source, target, replaceData);
 1157
 1291158            if (replaceData || string.IsNullOrEmpty(target.ForcedSortName))
 1159            {
 1281160                var forcedSortName = source.ForcedSortName;
 1281161                if (!string.IsNullOrEmpty(forcedSortName))
 1162                {
 31163                    target.ForcedSortName = forcedSortName;
 1164                }
 1165            }
 1166
 1291167            if (mergeMetadataSettings)
 1168            {
 21169                if (replaceData || !target.IsLocked)
 1170                {
 21171                    target.IsLocked = target.IsLocked || source.IsLocked;
 1172                }
 1173
 21174                if (target.LockedFields.Length == 0)
 1175                {
 01176                    target.LockedFields = source.LockedFields;
 1177                }
 1178                else
 1179                {
 21180                    target.LockedFields = target.LockedFields.Concat(source.LockedFields).Distinct().ToArray();
 1181                }
 1182
 21183                if (source.DateCreated != DateTime.MinValue)
 1184                {
 11185                    target.DateCreated = source.DateCreated;
 1186                }
 1187
 21188                if (replaceData || source.DateModified != DateTime.MinValue)
 1189                {
 21190                    target.DateModified = source.DateModified;
 1191                }
 1192
 21193                if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataCountryCode))
 1194                {
 21195                    target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
 1196                }
 1197
 21198                if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataLanguage))
 1199                {
 21200                    target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
 1201                }
 1202            }
 1291203        }
 1204
 1205        private static void MergePeople(IReadOnlyList<PersonInfo> source, IReadOnlyList<PersonInfo> target)
 1206        {
 41207            var sourceByName = source.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase);
 41208            var targetByName = target.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase);
 1209
 161210            foreach (var name in targetByName.Select(g => g.Key))
 1211            {
 41212                var targetPeople = targetByName[name].ToArray();
 41213                var sourcePeople = sourceByName[name].ToArray();
 1214
 41215                if (sourcePeople.Length == 0)
 1216                {
 1217                    continue;
 1218                }
 1219
 121220                for (int i = 0; i < targetPeople.Length; i++)
 1221                {
 31222                    var person = targetPeople[i];
 31223                    var personInSource = i < sourcePeople.Length ? sourcePeople[i] : sourcePeople[0];
 1224
 101225                    foreach (var providerId in personInSource.ProviderIds)
 1226                    {
 21227                        person.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 1228                    }
 1229
 31230                    if (string.IsNullOrWhiteSpace(person.ImageUrl))
 1231                    {
 21232                        person.ImageUrl = personInSource.ImageUrl;
 1233                    }
 1234
 31235                    if (!string.IsNullOrWhiteSpace(personInSource.Role) && string.IsNullOrWhiteSpace(person.Role))
 1236                    {
 01237                        person.Role = personInSource.Role;
 1238                    }
 1239
 31240                    if (personInSource.SortOrder.HasValue && !person.SortOrder.HasValue)
 1241                    {
 01242                        person.SortOrder = personInSource.SortOrder;
 1243                    }
 1244                }
 1245            }
 41246        }
 1247
 1248        private static void MergeDisplayOrder(BaseItem source, BaseItem target, bool replaceData)
 1249        {
 1291250            if (source is IHasDisplayOrder sourceHasDisplayOrder
 1291251                && target is IHasDisplayOrder targetHasDisplayOrder)
 1252            {
 491253                if (replaceData || string.IsNullOrEmpty(targetHasDisplayOrder.DisplayOrder))
 1254                {
 481255                    var displayOrder = sourceHasDisplayOrder.DisplayOrder;
 481256                    if (!string.IsNullOrWhiteSpace(displayOrder))
 1257                    {
 31258                        targetHasDisplayOrder.DisplayOrder = displayOrder;
 1259                    }
 1260                }
 1261            }
 1291262        }
 1263
 1264        private static void MergeAlbumArtist(BaseItem source, BaseItem target, bool replaceData)
 1265        {
 1291266            if (source is IHasAlbumArtist sourceHasAlbumArtist
 1291267                && target is IHasAlbumArtist targetHasAlbumArtist)
 1268            {
 281269                if (replaceData || targetHasAlbumArtist.AlbumArtists.Count == 0)
 1270                {
 271271                    targetHasAlbumArtist.AlbumArtists = sourceHasAlbumArtist.AlbumArtists;
 1272                }
 11273                else if (sourceHasAlbumArtist.AlbumArtists.Count > 0)
 1274                {
 11275                    targetHasAlbumArtist.AlbumArtists = targetHasAlbumArtist.AlbumArtists.Concat(sourceHasAlbumArtist.Al
 1276                }
 1277            }
 1021278        }
 1279
 1280        private static void MergeVideoInfo(BaseItem source, BaseItem target, bool replaceData)
 1281        {
 1291282            if (source is Video sourceCast && target is Video targetCast)
 1283            {
 521284                if (replaceData || !targetCast.Video3DFormat.HasValue)
 1285                {
 511286                    targetCast.Video3DFormat = sourceCast.Video3DFormat;
 1287                }
 1288            }
 1291289        }
 1290    }
 1291}

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,MediaBrowser.Controller.Persistence.IItemRepository)
get_EnableUpdatingPremiereDateFromChildren()
get_EnableUpdatingGenresFromChildren()
get_EnableUpdatingStudiosFromChildren()
get_EnableUpdatingOfficialRatingFromChildren()
get_Order()
TryGetFileSystemMetadata(System.String,MediaBrowser.Controller.Providers.IDirectoryService)
ApplySearchResult(MediaBrowser.Controller.Providers.ItemLookupInfo,MediaBrowser.Model.Providers.RemoteSearchResult)
AfterMetadataRefresh(TItemType,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
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)