mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-02 18:47:18 -05:00 
			
		
		
		
	Embed ProviderUtils into MetadataService
This commit is contained in:
		
							parent
							
								
									19164378f2
								
							
						
					
					
						commit
						1dfbeae045
					
				@ -973,7 +973,7 @@ namespace Emby.Server.Implementations
 | 
			
		||||
            yield return typeof(IServerApplicationHost).Assembly;
 | 
			
		||||
 | 
			
		||||
            // Include composable parts in the Providers assembly
 | 
			
		||||
            yield return typeof(ProviderUtils).Assembly;
 | 
			
		||||
            yield return typeof(ProviderManager).Assembly;
 | 
			
		||||
 | 
			
		||||
            // Include composable parts in the Photos assembly
 | 
			
		||||
            yield return typeof(PhotoProvider).Assembly;
 | 
			
		||||
 | 
			
		||||
@ -8,8 +8,10 @@ using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Diacritics.Extensions;
 | 
			
		||||
using MediaBrowser.Controller.Configuration;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Entities.Audio;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Controller.Providers;
 | 
			
		||||
using MediaBrowser.Model.Configuration;
 | 
			
		||||
@ -875,16 +877,6 @@ namespace MediaBrowser.Providers.Manager
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected virtual void MergeData(
 | 
			
		||||
            MetadataResult<TItemType> source,
 | 
			
		||||
            MetadataResult<TItemType> target,
 | 
			
		||||
            MetadataField[] lockedFields,
 | 
			
		||||
            bool replaceData,
 | 
			
		||||
            bool mergeMetadataSettings)
 | 
			
		||||
        {
 | 
			
		||||
            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool HasChanged(BaseItem item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
@ -904,5 +896,300 @@ namespace MediaBrowser.Providers.Manager
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Merges metadata from source into target.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="source">The source for new metadata.</param>
 | 
			
		||||
        /// <param name="target">The target to insert new metadata into.</param>
 | 
			
		||||
        /// <param name="lockedFields">The fields that are locked and should not be updated.</param>
 | 
			
		||||
        /// <param name="replaceData"><c>true</c> if existing data should be replaced.</param>
 | 
			
		||||
        /// <param name="mergeMetadataSettings"><c>true</c> if the metadata settings in target should be updated to match source.</param>
 | 
			
		||||
        /// <exception cref="ArgumentException">Thrown if source or target are null.</exception>
 | 
			
		||||
        protected virtual void MergeData(
 | 
			
		||||
            MetadataResult<TItemType> source,
 | 
			
		||||
            MetadataResult<TItemType> target,
 | 
			
		||||
            MetadataField[] lockedFields,
 | 
			
		||||
            bool replaceData,
 | 
			
		||||
            bool mergeMetadataSettings)
 | 
			
		||||
        {
 | 
			
		||||
            MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        internal static void MergeBaseItemData(
 | 
			
		||||
            MetadataResult<TItemType> sourceResult,
 | 
			
		||||
            MetadataResult<TItemType> targetResult,
 | 
			
		||||
            MetadataField[] lockedFields,
 | 
			
		||||
            bool replaceData,
 | 
			
		||||
            bool mergeMetadataSettings)
 | 
			
		||||
        {
 | 
			
		||||
            var source = sourceResult.Item;
 | 
			
		||||
            var target = targetResult.Item;
 | 
			
		||||
 | 
			
		||||
            if (source == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentException("Item cannot be null.", nameof(sourceResult));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (target == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentException("Item cannot be null.", nameof(targetResult));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Name))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || string.IsNullOrEmpty(target.Name))
 | 
			
		||||
                {
 | 
			
		||||
                    // Safeguard against incoming data having an empty name
 | 
			
		||||
                    if (!string.IsNullOrWhiteSpace(source.Name))
 | 
			
		||||
                    {
 | 
			
		||||
                        target.Name = source.Name;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
 | 
			
		||||
            {
 | 
			
		||||
                // Safeguard against incoming data having an empty name
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(source.OriginalTitle))
 | 
			
		||||
                {
 | 
			
		||||
                    target.OriginalTitle = source.OriginalTitle;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.CommunityRating.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.CommunityRating = source.CommunityRating;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.EndDate.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.EndDate = source.EndDate;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Genres))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || target.Genres.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    target.Genres = source.Genres;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.IndexNumber.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.IndexNumber = source.IndexNumber;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.OfficialRating))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || string.IsNullOrEmpty(target.OfficialRating))
 | 
			
		||||
                {
 | 
			
		||||
                    target.OfficialRating = source.OfficialRating;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || string.IsNullOrEmpty(target.CustomRating))
 | 
			
		||||
            {
 | 
			
		||||
                target.CustomRating = source.CustomRating;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || string.IsNullOrEmpty(target.Tagline))
 | 
			
		||||
            {
 | 
			
		||||
                target.Tagline = source.Tagline;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Overview))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || string.IsNullOrEmpty(target.Overview))
 | 
			
		||||
                {
 | 
			
		||||
                    target.Overview = source.Overview;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.ParentIndexNumber.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.ParentIndexNumber = source.ParentIndexNumber;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Cast))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || targetResult.People == null || targetResult.People.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    targetResult.People = sourceResult.People;
 | 
			
		||||
                }
 | 
			
		||||
                else if (targetResult.People != null && sourceResult.People != null)
 | 
			
		||||
                {
 | 
			
		||||
                    MergePeople(sourceResult.People, targetResult.People);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.PremiereDate.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.PremiereDate = source.PremiereDate;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.ProductionYear.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.ProductionYear = source.ProductionYear;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Runtime))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || !target.RunTimeTicks.HasValue)
 | 
			
		||||
                {
 | 
			
		||||
                    if (target is not Audio && target is not Video)
 | 
			
		||||
                    {
 | 
			
		||||
                        target.RunTimeTicks = source.RunTimeTicks;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Studios))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || target.Studios.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    target.Studios = source.Studios;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Tags))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || target.Tags.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    target.Tags = source.Tags;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.ProductionLocations))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || target.ProductionLocations.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    target.ProductionLocations = source.ProductionLocations;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach (var id in source.ProviderIds)
 | 
			
		||||
            {
 | 
			
		||||
                var key = id.Key;
 | 
			
		||||
 | 
			
		||||
                // Don't replace existing Id's.
 | 
			
		||||
                if (replaceData || !target.ProviderIds.ContainsKey(key))
 | 
			
		||||
                {
 | 
			
		||||
                    target.ProviderIds[key] = id.Value;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            MergeAlbumArtist(source, target, replaceData);
 | 
			
		||||
            MergeCriticRating(source, target, replaceData);
 | 
			
		||||
            MergeTrailers(source, target, replaceData);
 | 
			
		||||
            MergeVideoInfo(source, target, replaceData);
 | 
			
		||||
            MergeDisplayOrder(source, target, replaceData);
 | 
			
		||||
 | 
			
		||||
            if (replaceData || string.IsNullOrEmpty(target.ForcedSortName))
 | 
			
		||||
            {
 | 
			
		||||
                var forcedSortName = source.ForcedSortName;
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(forcedSortName))
 | 
			
		||||
                {
 | 
			
		||||
                    target.ForcedSortName = forcedSortName;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (mergeMetadataSettings)
 | 
			
		||||
            {
 | 
			
		||||
                target.LockedFields = source.LockedFields;
 | 
			
		||||
                target.IsLocked = source.IsLocked;
 | 
			
		||||
 | 
			
		||||
                // Grab the value if it's there, but if not then don't overwrite with the default
 | 
			
		||||
                if (source.DateCreated != default)
 | 
			
		||||
                {
 | 
			
		||||
                    target.DateCreated = source.DateCreated;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
 | 
			
		||||
                target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergePeople(List<PersonInfo> source, List<PersonInfo> target)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var person in target)
 | 
			
		||||
            {
 | 
			
		||||
                var normalizedName = person.Name.RemoveDiacritics();
 | 
			
		||||
                var personInSource = source.FirstOrDefault(i => string.Equals(i.Name.RemoveDiacritics(), normalizedName, StringComparison.OrdinalIgnoreCase));
 | 
			
		||||
 | 
			
		||||
                if (personInSource != null)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var providerId in personInSource.ProviderIds)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!person.ProviderIds.ContainsKey(providerId.Key))
 | 
			
		||||
                        {
 | 
			
		||||
                            person.ProviderIds[providerId.Key] = providerId.Value;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (string.IsNullOrWhiteSpace(person.ImageUrl))
 | 
			
		||||
                    {
 | 
			
		||||
                        person.ImageUrl = personInSource.ImageUrl;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeDisplayOrder(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (source is IHasDisplayOrder sourceHasDisplayOrder
 | 
			
		||||
                && target is IHasDisplayOrder targetHasDisplayOrder)
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || string.IsNullOrEmpty(targetHasDisplayOrder.DisplayOrder))
 | 
			
		||||
                {
 | 
			
		||||
                    var displayOrder = sourceHasDisplayOrder.DisplayOrder;
 | 
			
		||||
 | 
			
		||||
                    if (!string.IsNullOrWhiteSpace(displayOrder))
 | 
			
		||||
                    {
 | 
			
		||||
                        targetHasDisplayOrder.DisplayOrder = displayOrder;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeAlbumArtist(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (source is IHasAlbumArtist sourceHasAlbumArtist
 | 
			
		||||
                && target is IHasAlbumArtist targetHasAlbumArtist)
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || targetHasAlbumArtist.AlbumArtists.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    targetHasAlbumArtist.AlbumArtists = sourceHasAlbumArtist.AlbumArtists;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeCriticRating(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (replaceData || !target.CriticRating.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.CriticRating = source.CriticRating;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeTrailers(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (replaceData || target.RemoteTrailers.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                target.RemoteTrailers = source.RemoteTrailers;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeVideoInfo(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (source is Video sourceCast && target is Video targetCast)
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || targetCast.Video3DFormat == null)
 | 
			
		||||
                {
 | 
			
		||||
                    targetCast.Video3DFormat = sourceCast.Video3DFormat;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,306 +0,0 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using Diacritics.Extensions;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Entities.Audio;
 | 
			
		||||
using MediaBrowser.Controller.Providers;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
 | 
			
		||||
namespace MediaBrowser.Providers.Manager
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Class ProviderUtils.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static class ProviderUtils
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Merges metadata from source into target.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="sourceResult">The source for new metadata.</param>
 | 
			
		||||
        /// <param name="targetResult">The target to insert new metadata into.</param>
 | 
			
		||||
        /// <param name="lockedFields">The fields that are locked and should not be updated.</param>
 | 
			
		||||
        /// <param name="replaceData"><c>true</c> if existing data should be replaced.</param>
 | 
			
		||||
        /// <param name="mergeMetadataSettings"><c>true</c> if the metadata settings in target should be updated to match source.</param>
 | 
			
		||||
        /// <typeparam name="T">The type being acted upon.</typeparam>
 | 
			
		||||
        /// <exception cref="ArgumentException">Thrown if source or target are null.</exception>
 | 
			
		||||
        public static void MergeBaseItemData<T>(
 | 
			
		||||
            MetadataResult<T> sourceResult,
 | 
			
		||||
            MetadataResult<T> targetResult,
 | 
			
		||||
            MetadataField[] lockedFields,
 | 
			
		||||
            bool replaceData,
 | 
			
		||||
            bool mergeMetadataSettings)
 | 
			
		||||
            where T : BaseItem
 | 
			
		||||
        {
 | 
			
		||||
            var source = sourceResult.Item;
 | 
			
		||||
            var target = targetResult.Item;
 | 
			
		||||
 | 
			
		||||
            if (source == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentException("Item cannot be null.", nameof(sourceResult));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (target == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentException("Item cannot be null.", nameof(targetResult));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Name))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || string.IsNullOrEmpty(target.Name))
 | 
			
		||||
                {
 | 
			
		||||
                    // Safeguard against incoming data having an empty name
 | 
			
		||||
                    if (!string.IsNullOrWhiteSpace(source.Name))
 | 
			
		||||
                    {
 | 
			
		||||
                        target.Name = source.Name;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
 | 
			
		||||
            {
 | 
			
		||||
                // Safeguard against incoming data having an empty name
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(source.OriginalTitle))
 | 
			
		||||
                {
 | 
			
		||||
                    target.OriginalTitle = source.OriginalTitle;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.CommunityRating.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.CommunityRating = source.CommunityRating;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.EndDate.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.EndDate = source.EndDate;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Genres))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || target.Genres.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    target.Genres = source.Genres;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.IndexNumber.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.IndexNumber = source.IndexNumber;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.OfficialRating))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || string.IsNullOrEmpty(target.OfficialRating))
 | 
			
		||||
                {
 | 
			
		||||
                    target.OfficialRating = source.OfficialRating;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || string.IsNullOrEmpty(target.CustomRating))
 | 
			
		||||
            {
 | 
			
		||||
                target.CustomRating = source.CustomRating;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || string.IsNullOrEmpty(target.Tagline))
 | 
			
		||||
            {
 | 
			
		||||
                target.Tagline = source.Tagline;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Overview))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || string.IsNullOrEmpty(target.Overview))
 | 
			
		||||
                {
 | 
			
		||||
                    target.Overview = source.Overview;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.ParentIndexNumber.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.ParentIndexNumber = source.ParentIndexNumber;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Cast))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || targetResult.People == null || targetResult.People.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    targetResult.People = sourceResult.People;
 | 
			
		||||
                }
 | 
			
		||||
                else if (targetResult.People != null && sourceResult.People != null)
 | 
			
		||||
                {
 | 
			
		||||
                    MergePeople(sourceResult.People, targetResult.People);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.PremiereDate.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.PremiereDate = source.PremiereDate;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (replaceData || !target.ProductionYear.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.ProductionYear = source.ProductionYear;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Runtime))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || !target.RunTimeTicks.HasValue)
 | 
			
		||||
                {
 | 
			
		||||
                    if (target is not Audio && target is not Video)
 | 
			
		||||
                    {
 | 
			
		||||
                        target.RunTimeTicks = source.RunTimeTicks;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Studios))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || target.Studios.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    target.Studios = source.Studios;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.Tags))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || target.Tags.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    target.Tags = source.Tags;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!lockedFields.Contains(MetadataField.ProductionLocations))
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || target.ProductionLocations.Length == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    target.ProductionLocations = source.ProductionLocations;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach (var id in source.ProviderIds)
 | 
			
		||||
            {
 | 
			
		||||
                var key = id.Key;
 | 
			
		||||
 | 
			
		||||
                // Don't replace existing Id's.
 | 
			
		||||
                if (replaceData || !target.ProviderIds.ContainsKey(key))
 | 
			
		||||
                {
 | 
			
		||||
                    target.ProviderIds[key] = id.Value;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            MergeAlbumArtist(source, target, replaceData);
 | 
			
		||||
            MergeCriticRating(source, target, replaceData);
 | 
			
		||||
            MergeTrailers(source, target, replaceData);
 | 
			
		||||
            MergeVideoInfo(source, target, replaceData);
 | 
			
		||||
            MergeDisplayOrder(source, target, replaceData);
 | 
			
		||||
 | 
			
		||||
            if (replaceData || string.IsNullOrEmpty(target.ForcedSortName))
 | 
			
		||||
            {
 | 
			
		||||
                var forcedSortName = source.ForcedSortName;
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(forcedSortName))
 | 
			
		||||
                {
 | 
			
		||||
                    target.ForcedSortName = forcedSortName;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (mergeMetadataSettings)
 | 
			
		||||
            {
 | 
			
		||||
                target.LockedFields = source.LockedFields;
 | 
			
		||||
                target.IsLocked = source.IsLocked;
 | 
			
		||||
 | 
			
		||||
                // Grab the value if it's there, but if not then don't overwrite with the default
 | 
			
		||||
                if (source.DateCreated != default)
 | 
			
		||||
                {
 | 
			
		||||
                    target.DateCreated = source.DateCreated;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
 | 
			
		||||
                target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergePeople(List<PersonInfo> source, List<PersonInfo> target)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var person in target)
 | 
			
		||||
            {
 | 
			
		||||
                var normalizedName = person.Name.RemoveDiacritics();
 | 
			
		||||
                var personInSource = source.FirstOrDefault(i => string.Equals(i.Name.RemoveDiacritics(), normalizedName, StringComparison.OrdinalIgnoreCase));
 | 
			
		||||
 | 
			
		||||
                if (personInSource != null)
 | 
			
		||||
                {
 | 
			
		||||
                    foreach (var providerId in personInSource.ProviderIds)
 | 
			
		||||
                    {
 | 
			
		||||
                        if (!person.ProviderIds.ContainsKey(providerId.Key))
 | 
			
		||||
                        {
 | 
			
		||||
                            person.ProviderIds[providerId.Key] = providerId.Value;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (string.IsNullOrWhiteSpace(person.ImageUrl))
 | 
			
		||||
                    {
 | 
			
		||||
                        person.ImageUrl = personInSource.ImageUrl;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeDisplayOrder(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (source is IHasDisplayOrder sourceHasDisplayOrder
 | 
			
		||||
                && target is IHasDisplayOrder targetHasDisplayOrder)
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || string.IsNullOrEmpty(targetHasDisplayOrder.DisplayOrder))
 | 
			
		||||
                {
 | 
			
		||||
                    var displayOrder = sourceHasDisplayOrder.DisplayOrder;
 | 
			
		||||
 | 
			
		||||
                    if (!string.IsNullOrWhiteSpace(displayOrder))
 | 
			
		||||
                    {
 | 
			
		||||
                        targetHasDisplayOrder.DisplayOrder = displayOrder;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeAlbumArtist(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (source is IHasAlbumArtist sourceHasAlbumArtist
 | 
			
		||||
                && target is IHasAlbumArtist targetHasAlbumArtist)
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || targetHasAlbumArtist.AlbumArtists.Count == 0)
 | 
			
		||||
                {
 | 
			
		||||
                    targetHasAlbumArtist.AlbumArtists = sourceHasAlbumArtist.AlbumArtists;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeCriticRating(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (replaceData || !target.CriticRating.HasValue)
 | 
			
		||||
            {
 | 
			
		||||
                target.CriticRating = source.CriticRating;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeTrailers(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (replaceData || target.RemoteTrailers.Count == 0)
 | 
			
		||||
            {
 | 
			
		||||
                target.RemoteTrailers = source.RemoteTrailers;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static void MergeVideoInfo(BaseItem source, BaseItem target, bool replaceData)
 | 
			
		||||
        {
 | 
			
		||||
            if (source is Video sourceCast && target is Video targetCast)
 | 
			
		||||
            {
 | 
			
		||||
                if (replaceData || targetCast.Video3DFormat == null)
 | 
			
		||||
                {
 | 
			
		||||
                    targetCast.Video3DFormat = sourceCast.Video3DFormat;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -11,7 +11,7 @@ using Xunit;
 | 
			
		||||
 | 
			
		||||
namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
{
 | 
			
		||||
    public class ProviderUtilsTests
 | 
			
		||||
    public class MetadataServiceTests
 | 
			
		||||
    {
 | 
			
		||||
        [Theory]
 | 
			
		||||
        [InlineData(false, false)]
 | 
			
		||||
@ -55,7 +55,7 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            ProviderUtils.MergeBaseItemData(source, target, Array.Empty<MetadataField>(), true, mergeMetadataSettings);
 | 
			
		||||
            MetadataService<Movie, MovieInfo>.MergeBaseItemData(source, target, Array.Empty<MetadataField>(), true, mergeMetadataSettings);
 | 
			
		||||
 | 
			
		||||
            if (mergeMetadataSettings)
 | 
			
		||||
            {
 | 
			
		||||
@ -90,19 +90,19 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
            var newValue = "New";
 | 
			
		||||
 | 
			
		||||
            // Use type Series to hit DisplayOrder
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Series>(propName, oldValue, newValue, null, false, out _));
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Series, SeriesInfo>(propName, oldValue, newValue, null, false, out _));
 | 
			
		||||
            if (lockField != null)
 | 
			
		||||
            {
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Series>(propName, oldValue, newValue, lockField, true, out _));
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Series>(propName, null, newValue, lockField, false, out _));
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Series>(propName, string.Empty, newValue, lockField, false, out _));
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Series, SeriesInfo>(propName, oldValue, newValue, lockField, true, out _));
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Series, SeriesInfo>(propName, null, newValue, lockField, false, out _));
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Series, SeriesInfo>(propName, string.Empty, newValue, lockField, false, out _));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Series>(propName, oldValue, newValue, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Series>(propName, null, newValue, null, false, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Series>(propName, string.Empty, newValue, null, false, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Series, SeriesInfo>(propName, oldValue, newValue, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Series, SeriesInfo>(propName, null, newValue, null, false, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Series, SeriesInfo>(propName, string.Empty, newValue, null, false, out _));
 | 
			
		||||
 | 
			
		||||
            var replacedWithEmpty = TestMergeBaseItemData<Series>(propName, oldValue, string.Empty, null, true, out _);
 | 
			
		||||
            var replacedWithEmpty = TestMergeBaseItemData<Series, SeriesInfo>(propName, oldValue, string.Empty, null, true, out _);
 | 
			
		||||
            Assert.Equal(replacesWithEmpty, replacedWithEmpty);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -119,17 +119,17 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
            var newValue = new[] { "New" };
 | 
			
		||||
 | 
			
		||||
            // Use type Audio to hit AlbumArtists
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Audio>(propName, oldValue, newValue, null, false, out _));
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Audio, SongInfo>(propName, oldValue, newValue, null, false, out _));
 | 
			
		||||
            if (lockField != null)
 | 
			
		||||
            {
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Audio>(propName, oldValue, newValue, lockField, true, out _));
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Audio>(propName, Array.Empty<string>(), newValue, lockField, false, out _));
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Audio, SongInfo>(propName, oldValue, newValue, lockField, true, out _));
 | 
			
		||||
                Assert.False(TestMergeBaseItemData<Audio, SongInfo>(propName, Array.Empty<string>(), newValue, lockField, false, out _));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Audio>(propName, oldValue, newValue, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Audio>(propName, Array.Empty<string>(), newValue, null, false, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Audio, SongInfo>(propName, oldValue, newValue, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Audio, SongInfo>(propName, Array.Empty<string>(), newValue, null, false, out _));
 | 
			
		||||
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Audio>(propName, oldValue, Array.Empty<string>(), null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Audio, SongInfo>(propName, oldValue, Array.Empty<string>(), null, true, out _));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static TheoryData<string, object, object> MergeBaseItemData_SimpleField_ReplacesAppropriately_TestData()
 | 
			
		||||
@ -150,12 +150,12 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
        public void MergeBaseItemData_SimpleField_ReplacesAppropriately(string propName, object oldValue, object newValue)
 | 
			
		||||
        {
 | 
			
		||||
            // Use type Movie to allow testing of Video3DFormat
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Movie>(propName, oldValue, newValue, null, false, out _));
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, newValue, null, false, out _));
 | 
			
		||||
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie>(propName, oldValue, newValue, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie>(propName, null, newValue, null, false, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, newValue, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie, MovieInfo>(propName, null, newValue, null, false, out _));
 | 
			
		||||
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie>(propName, oldValue, null, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, null, null, true, out _));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
@ -179,12 +179,12 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Movie>(propName, oldValue, newValue, null, false, out _));
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, newValue, null, false, out _));
 | 
			
		||||
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie>(propName, oldValue, newValue, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie>(propName, Array.Empty<MediaUrl>(), newValue, null, false, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, newValue, null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie, MovieInfo>(propName, Array.Empty<MediaUrl>(), newValue, null, false, out _));
 | 
			
		||||
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie>(propName, oldValue, Array.Empty<MediaUrl>(), null, true, out _));
 | 
			
		||||
            Assert.True(TestMergeBaseItemData<Movie, MovieInfo>(propName, oldValue, Array.Empty<MediaUrl>(), null, true, out _));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Fact]
 | 
			
		||||
@ -201,8 +201,8 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
            {
 | 
			
		||||
                { "provider 1", "id 2" }
 | 
			
		||||
            };
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Movie>(propName, new Dictionary<string, string>(oldValue), overwriteNewValue, null, false, out _));
 | 
			
		||||
            TestMergeBaseItemData<Movie>(propName, new Dictionary<string, string>(oldValue), overwriteNewValue, null, true, out var overwritten);
 | 
			
		||||
            Assert.False(TestMergeBaseItemData<Movie, MovieInfo>(propName, new Dictionary<string, string>(oldValue), overwriteNewValue, null, false, out _));
 | 
			
		||||
            TestMergeBaseItemData<Movie, MovieInfo>(propName, new Dictionary<string, string>(oldValue), overwriteNewValue, null, true, out var overwritten);
 | 
			
		||||
            Assert.Equal(overwriteNewValue, overwritten);
 | 
			
		||||
 | 
			
		||||
            // merge without overwriting
 | 
			
		||||
@ -211,13 +211,13 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
                { "provider 1", "id 2" },
 | 
			
		||||
                { "provider 2", "id 3" }
 | 
			
		||||
            };
 | 
			
		||||
            TestMergeBaseItemData<Movie>(propName, new Dictionary<string, string>(oldValue), mergeNewValue, null, false, out var merged);
 | 
			
		||||
            TestMergeBaseItemData<Movie, MovieInfo>(propName, new Dictionary<string, string>(oldValue), mergeNewValue, null, false, out var merged);
 | 
			
		||||
            var actual = (Dictionary<string, string>)merged!;
 | 
			
		||||
            Assert.Equal("id 1", actual["provider 1"]);
 | 
			
		||||
            Assert.Equal("id 3", actual["provider 2"]);
 | 
			
		||||
 | 
			
		||||
            // empty source results in no change
 | 
			
		||||
            TestMergeBaseItemData<Movie>(propName, new Dictionary<string, string>(oldValue), new Dictionary<string, string>(), null, true, out var notOverwritten);
 | 
			
		||||
            TestMergeBaseItemData<Movie, MovieInfo>(propName, new Dictionary<string, string>(oldValue), new Dictionary<string, string>(), null, true, out var notOverwritten);
 | 
			
		||||
            Assert.Equal(oldValue, notOverwritten);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -329,14 +329,14 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var lockedFields = lockField == null ? Array.Empty<MetadataField>() : new[] { (MetadataField)lockField };
 | 
			
		||||
            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, false);
 | 
			
		||||
            MetadataService<Movie, MovieInfo>.MergeBaseItemData(source, target, lockedFields, replaceData, false);
 | 
			
		||||
 | 
			
		||||
            actualValue = target.People;
 | 
			
		||||
            return newValue?.Equals(actualValue) ?? actualValue == null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Makes a call to <see cref="ProviderUtils.MergeBaseItemData{T}"/> with the provided parameters and returns whether the target changed or not.
 | 
			
		||||
        /// Makes a call to <see cref="MetadataService{TItemType,TIdType}.MergeBaseItemData"/> with the provided parameters and returns whether the target changed or not.
 | 
			
		||||
        ///
 | 
			
		||||
        /// Reflection is used to allow testing of all fields using the same logic, rather than relying on copy/pasting test code for each field.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@ -344,12 +344,14 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
        /// <param name="oldValue">The initial value in the target object.</param>
 | 
			
		||||
        /// <param name="newValue">The initial value in the source object.</param>
 | 
			
		||||
        /// <param name="lockField">The metadata field that locks this property if the field should be locked, or <c>null</c> to leave unlocked.</param>
 | 
			
		||||
        /// <param name="replaceData">Passed through to <see cref="ProviderUtils.MergeBaseItemData{T}"/>.</param>
 | 
			
		||||
        /// <param name="replaceData">Passed through to <see cref="MetadataService{TItemType,TIdType}.MergeBaseItemData"/>.</param>
 | 
			
		||||
        /// <param name="actualValue">The resulting value set to the target.</param>
 | 
			
		||||
        /// <typeparam name="TItemType">The <see cref="BaseItem"/> type to test on.</typeparam>
 | 
			
		||||
        /// <returns><c>true</c> if the property on the target updates to match the source value when<see cref="ProviderUtils.MergeBaseItemData{T}"/> is called.</returns>
 | 
			
		||||
        private static bool TestMergeBaseItemData<TItemType>(string propName, object? oldValue, object? newValue, MetadataField? lockField, bool replaceData, out object? actualValue)
 | 
			
		||||
            where TItemType : BaseItem, new()
 | 
			
		||||
        /// <typeparam name="TIdType">The <see cref="BaseItem"/> info type.</typeparam>
 | 
			
		||||
        /// <returns><c>true</c> if the property on the target updates to match the source value when<see cref="MetadataService{TItemType,TIdType}.MergeBaseItemData"/> is called.</returns>
 | 
			
		||||
        private static bool TestMergeBaseItemData<TItemType, TIdType>(string propName, object? oldValue, object? newValue, MetadataField? lockField, bool replaceData, out object? actualValue)
 | 
			
		||||
            where TItemType : BaseItem, IHasLookupInfo<TIdType>, new()
 | 
			
		||||
            where TIdType : ItemLookupInfo, new()
 | 
			
		||||
        {
 | 
			
		||||
            var property = typeof(TItemType).GetProperty(propName)!;
 | 
			
		||||
 | 
			
		||||
@ -366,7 +368,8 @@ namespace Jellyfin.Providers.Tests.Manager
 | 
			
		||||
            property.SetValue(target.Item, oldValue);
 | 
			
		||||
 | 
			
		||||
            var lockedFields = lockField == null ? Array.Empty<MetadataField>() : new[] { (MetadataField)lockField };
 | 
			
		||||
            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, false);
 | 
			
		||||
            // generic type doesn't actually matter to call the static method, just has to be filled in
 | 
			
		||||
            MetadataService<TItemType, TIdType>.MergeBaseItemData(source, target, lockedFields, replaceData, false);
 | 
			
		||||
 | 
			
		||||
            actualValue = property.GetValue(target.Item);
 | 
			
		||||
            return newValue?.Equals(actualValue) ?? actualValue == null;
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user