diff options
| author | Shadowghost <Ghost_of_Stone@web.de> | 2026-01-17 17:10:07 +0100 |
|---|---|---|
| committer | Shadowghost <Ghost_of_Stone@web.de> | 2026-01-18 19:48:46 +0100 |
| commit | 5996c4afce11249804d24f1caa3a99b390543c4d (patch) | |
| tree | d84b98428d95c801492b1354571e2ab3fc0cc99b /MediaBrowser.Providers/Manager/ProviderManager.cs | |
| parent | dfa78590c2899c7e74b142ebbced4140a354aed0 (diff) | |
Complete LinkedChildren integration and batch DTO optimizations
This commit integrates remaining performance changes:
- Add batch user data fetching in DtoService to reduce N+1 queries
- Add GetNextUpEpisodesBatch in TVSeriesManager for efficient batch retrieval
- Update Video/Movie/BoxSet to use LibraryManager for alternate versions
- Transition LinkedChild to use ItemId instead of Path (obsolete Path/LibraryItemId)
- Update providers and controllers for LinkedChildren-based references
- Add NextUpEpisodeBatchResult for batched episode queries
- Integrate IDescendantQueryProvider in SqliteDatabaseProvider
Diffstat (limited to 'MediaBrowser.Providers/Manager/ProviderManager.cs')
| -rw-r--r-- | MediaBrowser.Providers/Manager/ProviderManager.cs | 144 |
1 files changed, 135 insertions, 9 deletions
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index f8e2aece1f..aad9ca2844 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -31,6 +31,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; +using MediaBrowser.Model.Querying; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using Book = MediaBrowser.Controller.Entities.Book; @@ -69,6 +70,13 @@ namespace MediaBrowser.Providers.Manager o.PoolInitialFill = 1; }); + /// <summary> + /// Cache for ordered metadata providers per library/item type combination. + /// Key: (LibraryPath, ItemTypeName, IncludeDisabled, ForceEnableInternetMetadata). + /// Value: Array of ordered metadata providers (before per-item filtering). + /// </summary> + private readonly ConcurrentDictionary<MetadataProviderCacheKey, IMetadataProvider[]> _metadataProviderCache = new(); + private IImageProvider[] _imageProviders = []; private IMetadataService[] _metadataServices = []; private IMetadataProvider[] _metadataProviders = []; @@ -119,6 +127,8 @@ namespace MediaBrowser.Providers.Manager _lyricManager = lyricManager; _memoryCache = memoryCache; _mediaSegmentManager = mediaSegmentManager; + + CollectionFolder.LibraryOptionsUpdated += OnLibraryOptionsUpdated; } /// <inheritdoc/> @@ -427,8 +437,37 @@ namespace MediaBrowser.Providers.Manager where T : BaseItem { var globalMetadataOptions = GetMetadataOptions(item); + var libraryPath = GetLibraryPathForItem(item); + + return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false, libraryPath); + } + + /// <summary> + /// Gets metadata providers for the specified item. + /// </summary> + /// <typeparam name="T">The item type.</typeparam> + /// <param name="item">The item.</param> + /// <param name="libraryOptions">The library options.</param> + /// <param name="includeDisabled">Whether to include disabled providers.</param> + /// <returns>The metadata providers.</returns> + public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions, bool includeDisabled) + where T : BaseItem + { + var globalMetadataOptions = GetMetadataOptions(item); + var libraryPath = GetLibraryPathForItem(item); - return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false); + return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, includeDisabled, false, libraryPath); + } + + private static string GetLibraryPathForItem(BaseItem item) + { + if (item is CollectionFolder collectionFolder) + { + return collectionFolder.Path ?? string.Empty; + } + + var topParent = item.GetTopParent(); + return topParent?.Path ?? string.Empty; } /// <inheritdoc /> @@ -437,15 +476,37 @@ namespace MediaBrowser.Providers.Manager return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false)); } - private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions, bool includeDisabled, bool forceEnableInternetMetadata) + private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions, bool includeDisabled, bool forceEnableInternetMetadata, string libraryPath) where T : BaseItem { - var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadataReaderOrder; var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name); + + var orderedProviders = GetOrCreateOrderedProviders<T>(item.GetType().Name, libraryOptions, globalMetadataOptions, includeDisabled, forceEnableInternetMetadata, libraryPath); + + return orderedProviders.Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInternetMetadata)); + } + + private IMetadataProvider<T>[] GetOrCreateOrderedProviders<T>( + string itemTypeName, + LibraryOptions libraryOptions, + MetadataOptions globalMetadataOptions, + bool includeDisabled, + bool forceEnableInternetMetadata, + string libraryPath) + where T : BaseItem + { + var cacheKey = new MetadataProviderCacheKey(libraryPath, itemTypeName, includeDisabled, forceEnableInternetMetadata); + if (_metadataProviderCache.TryGetValue(cacheKey, out var cachedProviders)) + { + return cachedProviders.OfType<IMetadataProvider<T>>().ToArray(); + } + + var localMetadataReaderOrder = libraryOptions.LocalMetadataReaderOrder ?? globalMetadataOptions.LocalMetadataReaderOrder; + var typeOptions = libraryOptions.GetTypeOptions(itemTypeName); var metadataFetcherOrder = typeOptions?.MetadataFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder; - return _metadataProviders.OfType<IMetadataProvider<T>>() - .Where(i => CanRefreshMetadata(i, item, typeOptions, includeDisabled, forceEnableInternetMetadata)) + var orderedProviders = _metadataProviders.OfType<IMetadataProvider<T>>() + .Where(i => CanRefreshMetadataForCache(i, typeOptions, includeDisabled, forceEnableInternetMetadata)) .OrderBy(i => // local and remote providers will be interleaved in the final order // only relative order within a type matters: consumers of the list filter to one or the other @@ -456,7 +517,36 @@ namespace MediaBrowser.Providers.Manager // Default to end _ => int.MaxValue }) - .ThenBy(GetDefaultOrder); + .ThenBy(GetDefaultOrder) + .ToArray(); + + _metadataProviderCache.TryAdd(cacheKey, orderedProviders.Cast<IMetadataProvider>().ToArray()); + + return orderedProviders; + } + + private static bool CanRefreshMetadataForCache( + IMetadataProvider provider, + TypeOptions? libraryTypeOptions, + bool includeDisabled, + bool forceEnableInternetMetadata) + { + if (includeDisabled) + { + return true; + } + + if (forceEnableInternetMetadata || provider is not IRemoteMetadataProvider) + { + return true; + } + + if (libraryTypeOptions?.MetadataFetchers is { Length: > 0 } metadataFetchers) + { + return metadataFetchers.Contains(provider.Name, StringComparer.OrdinalIgnoreCase); + } + + return true; } private bool CanRefreshMetadata( @@ -607,7 +697,8 @@ namespace MediaBrowser.Providers.Manager private void AddMetadataPlugins<T>(List<MetadataPlugin> list, T item, LibraryOptions libraryOptions, MetadataOptions options) where T : BaseItem { - var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true).ToList(); + var libraryPath = GetLibraryPathForItem(item); + var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true, libraryPath).ToList(); // Locals list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin @@ -824,8 +915,8 @@ namespace MediaBrowser.Providers.Manager } var options = GetMetadataOptions(referenceItem); - - var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.IncludeDisabledProviders, false) + var libraryPath = GetLibraryPathForItem(referenceItem); + var providers = GetMetadataProvidersInternal<TItemType>(referenceItem, libraryOptions, options, searchInfo.IncludeDisabledProviders, false, libraryPath) .OfType<IRemoteSearchProvider<TLookupType>>(); if (!string.IsNullOrEmpty(searchInfo.SearchProviderName)) @@ -1157,6 +1248,8 @@ namespace MediaBrowser.Providers.Manager if (disposing) { + CollectionFolder.LibraryOptionsUpdated -= OnLibraryOptionsUpdated; + if (!_disposeCancellationTokenSource.IsCancellationRequested) { _disposeCancellationTokenSource.Cancel(); @@ -1168,5 +1261,38 @@ namespace MediaBrowser.Providers.Manager _disposed = true; } + + private void OnLibraryOptionsUpdated(object? sender, LibraryOptionsUpdatedEventArgs e) + { + var keysToRemove = _metadataProviderCache.Keys + .Where(k => string.Equals(k.LibraryPath, e.LibraryPath, StringComparison.Ordinal)) + .ToList(); + + foreach (var key in keysToRemove) + { + _metadataProviderCache.TryRemove(key, out _); + } + + _logger.LogDebug("Invalidated metadata provider cache for library: {LibraryPath}", e.LibraryPath); + } + + internal void ClearMetadataProviderCache() + { + _metadataProviderCache.Clear(); + _logger.LogDebug("Cleared entire metadata provider cache"); + } + + /// <summary> + /// Cache key for metadata provider lookups. + /// </summary> + /// <param name="LibraryPath">The library path for the collection folder.</param> + /// <param name="ItemTypeName">The item type name.</param> + /// <param name="IncludeDisabled">Whether to include disabled providers.</param> + /// <param name="ForceEnableInternetMetadata">Whether internet metadata is force-enabled.</param> + private readonly record struct MetadataProviderCacheKey( + string LibraryPath, + string ItemTypeName, + bool IncludeDisabled, + bool ForceEnableInternetMetadata); } } |
