aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers/Manager/ProviderManager.cs
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-01-17 17:10:07 +0100
committerShadowghost <Ghost_of_Stone@web.de>2026-01-18 19:48:46 +0100
commit5996c4afce11249804d24f1caa3a99b390543c4d (patch)
treed84b98428d95c801492b1354571e2ab3fc0cc99b /MediaBrowser.Providers/Manager/ProviderManager.cs
parentdfa78590c2899c7e74b142ebbced4140a354aed0 (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.cs144
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);
}
}