mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-05-16 05:36:52 +01:00
Apply review suggestions
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Library.SimilarItems;
|
||||
/// <summary>
|
||||
/// Provides similar items for movies and trailers.
|
||||
/// </summary>
|
||||
public class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie>, ILocalSimilarItemsProvider<Trailer>
|
||||
public sealed class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie>, ILocalSimilarItemsProvider<Trailer>
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
@@ -51,6 +52,17 @@ public class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie>, ILoc
|
||||
return Task.FromResult(GetSimilarMovieItems(item, query));
|
||||
}
|
||||
|
||||
bool ILocalSimilarItemsProvider.Supports(Type itemType)
|
||||
=> typeof(Movie).IsAssignableFrom(itemType) || typeof(Trailer).IsAssignableFrom(itemType);
|
||||
|
||||
Task<IReadOnlyList<BaseItem>> ILocalSimilarItemsProvider.GetSimilarItemsAsync(BaseItem item, SimilarItemsQuery query, CancellationToken cancellationToken)
|
||||
=> item switch
|
||||
{
|
||||
Movie movie => GetSimilarItemsAsync(movie, query, cancellationToken),
|
||||
Trailer trailer => GetSimilarItemsAsync(trailer, query, cancellationToken),
|
||||
_ => throw new ArgumentException($"Unsupported item type {item.GetType()}", nameof(item))
|
||||
};
|
||||
|
||||
private IReadOnlyList<BaseItem> GetSimilarMovieItems(BaseItem item, SimilarItemsQuery query)
|
||||
{
|
||||
var includeItemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -28,10 +26,6 @@ namespace Emby.Server.Implementations.Library.SimilarItems;
|
||||
/// </summary>
|
||||
public class SimilarItemsManager : ISimilarItemsManager
|
||||
{
|
||||
private static readonly ConcurrentDictionary<Type, MethodInfo> _genericMethodCache = new();
|
||||
private static readonly MethodInfo _getSimilarItemsInternalMethod = typeof(SimilarItemsManager)
|
||||
.GetMethod(nameof(GetSimilarItemsInternalAsync), BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
|
||||
private readonly ILogger<SimilarItemsManager> _logger;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@@ -67,10 +61,10 @@ public class SimilarItemsManager : ISimilarItemsManager
|
||||
public IReadOnlyList<ISimilarItemsProvider> GetSimilarItemsProviders<T>()
|
||||
where T : BaseItem
|
||||
{
|
||||
var itemType = typeof(T);
|
||||
return _similarItemsProviders
|
||||
.OfType<ILocalSimilarItemsProvider<T>>()
|
||||
.Cast<ISimilarItemsProvider>()
|
||||
.Concat(_similarItemsProviders.OfType<IRemoteSimilarItemsProvider<T>>())
|
||||
.Where(p => (p is ILocalSimilarItemsProvider local && local.Supports(itemType))
|
||||
|| (p is IRemoteSimilarItemsProvider remote && remote.Supports(itemType)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -88,22 +82,6 @@ public class SimilarItemsManager : ISimilarItemsManager
|
||||
ArgumentNullException.ThrowIfNull(excludeArtistIds);
|
||||
|
||||
var itemType = item.GetType();
|
||||
var method = _genericMethodCache.GetOrAdd(itemType, static type => _getSimilarItemsInternalMethod.MakeGenericMethod(type));
|
||||
|
||||
var task = (Task<IReadOnlyList<BaseItem>>)method.Invoke(this, [item, excludeArtistIds, user, dtoOptions, limit, libraryOptions, cancellationToken])!;
|
||||
return await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<BaseItem>> GetSimilarItemsInternalAsync<T>(
|
||||
T item,
|
||||
IReadOnlyList<Guid> excludeArtistIds,
|
||||
User? user,
|
||||
DtoOptions dtoOptions,
|
||||
int? limit,
|
||||
LibraryOptions? libraryOptions,
|
||||
CancellationToken cancellationToken)
|
||||
where T : BaseItem
|
||||
{
|
||||
var requestedLimit = limit ?? 50;
|
||||
var itemKind = item.GetBaseItemKind();
|
||||
|
||||
@@ -114,11 +92,16 @@ public class SimilarItemsManager : ISimilarItemsManager
|
||||
}
|
||||
|
||||
// Local providers are always enabled. Remote providers must be explicitly enabled.
|
||||
var localProviders = _similarItemsProviders.OfType<ILocalSimilarItemsProvider<T>>().Cast<ISimilarItemsProvider>().ToList();
|
||||
var remoteProviders = _similarItemsProviders.OfType<IRemoteSimilarItemsProvider<T>>().Cast<ISimilarItemsProvider>();
|
||||
var localProviders = _similarItemsProviders
|
||||
.OfType<ILocalSimilarItemsProvider>()
|
||||
.Where(p => p.Supports(itemType))
|
||||
.ToList();
|
||||
var remoteProviders = _similarItemsProviders
|
||||
.OfType<IRemoteSimilarItemsProvider>()
|
||||
.Where(p => p.Supports(itemType));
|
||||
var matchingProviders = new List<ISimilarItemsProvider>(localProviders);
|
||||
|
||||
var typeOptions = libraryOptions?.GetTypeOptions(typeof(T).Name);
|
||||
var typeOptions = libraryOptions?.GetTypeOptions(itemType.Name);
|
||||
if (typeOptions?.SimilarItemProviders?.Length > 0)
|
||||
{
|
||||
matchingProviders.AddRange(remoteProviders
|
||||
@@ -143,7 +126,7 @@ public class SimilarItemsManager : ISimilarItemsManager
|
||||
|
||||
try
|
||||
{
|
||||
if (provider is ILocalSimilarItemsProvider<T> localProvider)
|
||||
if (provider is ILocalSimilarItemsProvider localProvider)
|
||||
{
|
||||
var query = new SimilarItemsQuery
|
||||
{
|
||||
@@ -165,9 +148,9 @@ public class SimilarItemsManager : ISimilarItemsManager
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (provider is IRemoteSimilarItemsProvider<T> remoteProvider)
|
||||
else if (provider is IRemoteSimilarItemsProvider remoteProvider)
|
||||
{
|
||||
var cachePath = GetSimilarItemsCachePath(provider.Name, typeof(T).Name, item.Id);
|
||||
var cachePath = GetSimilarItemsCachePath(provider.Name, itemType.Name, item.Id);
|
||||
|
||||
var cachedReferences = await TryReadSimilarItemsCacheAsync(cachePath, cancellationToken).ConfigureAwait(false);
|
||||
if (cachedReferences is not null)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -5,12 +6,38 @@ using MediaBrowser.Controller.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Library;
|
||||
|
||||
/// <summary>
|
||||
/// Provides similar items from the local library.
|
||||
/// Returns fully resolved BaseItems directly - no additional resolution needed.
|
||||
/// </summary>
|
||||
public interface ILocalSimilarItemsProvider : ISimilarItemsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the provider can handle items of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="itemType">The item type.</param>
|
||||
/// <returns><c>true</c> if the provider handles this item type; otherwise <c>false</c>.</returns>
|
||||
bool Supports(Type itemType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets similar items from the local library.
|
||||
/// </summary>
|
||||
/// <param name="item">The source item to find similar items for.</param>
|
||||
/// <param name="query">The query options (user, limit, exclusions, etc.).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The list of similar items from the library.</returns>
|
||||
Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(
|
||||
BaseItem item,
|
||||
SimilarItemsQuery query,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides similar items from the local library for a specific item type.
|
||||
/// Returns fully resolved BaseItems directly - no additional resolution needed.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemType">The type of item this provider handles.</typeparam>
|
||||
public interface ILocalSimilarItemsProvider<TItemType> : ISimilarItemsProvider
|
||||
public interface ILocalSimilarItemsProvider<TItemType> : ILocalSimilarItemsProvider
|
||||
where TItemType : BaseItem
|
||||
{
|
||||
/// <summary>
|
||||
@@ -24,4 +51,13 @@ public interface ILocalSimilarItemsProvider<TItemType> : ISimilarItemsProvider
|
||||
TItemType item,
|
||||
SimilarItemsQuery query,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
bool ILocalSimilarItemsProvider.Supports(Type itemType)
|
||||
=> typeof(TItemType).IsAssignableFrom(itemType);
|
||||
|
||||
Task<IReadOnlyList<BaseItem>> ILocalSimilarItemsProvider.GetSimilarItemsAsync(
|
||||
BaseItem item,
|
||||
SimilarItemsQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
=> GetSimilarItemsAsync((TItemType)item, query, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Library;
|
||||
|
||||
/// <summary>
|
||||
/// Provides similar item references from remote/external sources.
|
||||
/// Returns lightweight references with ProviderIds that the manager resolves to library items.
|
||||
/// </summary>
|
||||
public interface IRemoteSimilarItemsProvider : ISimilarItemsProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the provider can handle items of the specified type.
|
||||
/// </summary>
|
||||
/// <param name="itemType">The item type.</param>
|
||||
/// <returns><c>true</c> if the provider handles this item type; otherwise <c>false</c>.</returns>
|
||||
bool Supports(Type itemType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets similar item references from an external source as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="item">The source item to find similar items for.</param>
|
||||
/// <param name="query">The query options (user, limit, exclusions).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>An async enumerable of similar item references.</returns>
|
||||
IAsyncEnumerable<SimilarItemReference> GetSimilarItemsAsync(
|
||||
BaseItem item,
|
||||
SimilarItemsQuery query,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides similar item references from remote/external sources for a specific item type.
|
||||
/// Returns lightweight references with ProviderIds that the manager resolves to library items.
|
||||
/// </summary>
|
||||
/// <typeparam name="TItemType">The type of item this provider handles.</typeparam>
|
||||
public interface IRemoteSimilarItemsProvider<TItemType> : ISimilarItemsProvider
|
||||
public interface IRemoteSimilarItemsProvider<TItemType> : IRemoteSimilarItemsProvider
|
||||
where TItemType : BaseItem
|
||||
{
|
||||
/// <summary>
|
||||
@@ -23,4 +50,13 @@ public interface IRemoteSimilarItemsProvider<TItemType> : ISimilarItemsProvider
|
||||
TItemType item,
|
||||
SimilarItemsQuery query,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
bool IRemoteSimilarItemsProvider.Supports(Type itemType)
|
||||
=> typeof(TItemType).IsAssignableFrom(itemType);
|
||||
|
||||
IAsyncEnumerable<SimilarItemReference> IRemoteSimilarItemsProvider.GetSimilarItemsAsync(
|
||||
BaseItem item,
|
||||
SimilarItemsQuery query,
|
||||
CancellationToken cancellationToken)
|
||||
=> GetSimilarItemsAsync((TItemType)item, query, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ namespace MediaBrowser.Providers.Plugins.ListenBrainz.Api;
|
||||
/// <summary>
|
||||
/// Client for the ListenBrainz Labs API.
|
||||
/// </summary>
|
||||
public class ListenBrainzLabsClient
|
||||
public class ListenBrainzLabsClient : IDisposable
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILogger<ListenBrainzLabsClient> _logger;
|
||||
private readonly Lock _rateLimitLock = new();
|
||||
private readonly SemaphoreSlim _rateLimitLock = new(1, 1);
|
||||
|
||||
private DateTime _lastRequestTime = DateTime.MinValue;
|
||||
|
||||
@@ -52,7 +52,7 @@ public class ListenBrainzLabsClient
|
||||
var rateLimit = config?.RateLimit ?? PluginConfiguration.DefaultRateLimit;
|
||||
|
||||
// Enforce rate limit
|
||||
EnforceRateLimit(rateLimit);
|
||||
await EnforceRateLimitAsync(rateLimit, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var url = $"{baseUrl}/similar-artists/json?artist_mbids={artistMbid}&algorithm={algorithm}";
|
||||
|
||||
@@ -86,19 +86,43 @@ public class ListenBrainzLabsClient
|
||||
}
|
||||
}
|
||||
|
||||
private void EnforceRateLimit(double rateLimitSeconds)
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_rateLimitLock)
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_rateLimitLock.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnforceRateLimitAsync(double rateLimitSeconds, CancellationToken cancellationToken)
|
||||
{
|
||||
await _rateLimitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var timeSinceLastRequest = DateTime.UtcNow - _lastRequestTime;
|
||||
var requiredDelay = TimeSpan.FromSeconds(rateLimitSeconds) - timeSinceLastRequest;
|
||||
|
||||
if (requiredDelay > TimeSpan.Zero)
|
||||
{
|
||||
Thread.Sleep(requiredDelay);
|
||||
await Task.Delay(requiredDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_lastRequestTime = DateTime.UtcNow;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_rateLimitLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user