< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Plugins.ListenBrainz.Api.ListenBrainzLabsClient
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Plugins/ListenBrainz/Api/ListenBrainzLabsClient.cs
Line coverage
25%
Covered lines: 11
Uncovered lines: 32
Coverable lines: 43
Total lines: 128
Line coverage: 25.5%
Branch coverage
5%
Covered branches: 1
Total branches: 20
Branch coverage: 5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 25.5% (11/43) Branch coverage: 10% (2/20) Total lines: 1285/20/2026 - 12:15:44 AM Line coverage: 25.5% (11/43) Branch coverage: 5% (1/20) Total lines: 128 5/16/2026 - 12:15:55 AM Line coverage: 25.5% (11/43) Branch coverage: 10% (2/20) Total lines: 1285/20/2026 - 12:15:44 AM Line coverage: 25.5% (11/43) Branch coverage: 5% (1/20) Total lines: 128

Coverage delta

Coverage delta 5 -5

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetSimilarArtistsAsync()0%272160%
Dispose()100%11100%
Dispose(...)50%22100%
EnforceRateLimitAsync()0%620%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Plugins/ListenBrainz/Api/ListenBrainzLabsClient.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4using System.Net.Http;
 5using System.Net.Http.Json;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using MediaBrowser.Common.Net;
 9using MediaBrowser.Providers.Plugins.ListenBrainz.Api.Models;
 10using MediaBrowser.Providers.Plugins.ListenBrainz.Configuration;
 11using Microsoft.Extensions.Logging;
 12
 13namespace MediaBrowser.Providers.Plugins.ListenBrainz.Api;
 14
 15/// <summary>
 16/// Client for the ListenBrainz Labs API.
 17/// </summary>
 18public class ListenBrainzLabsClient : IDisposable
 19{
 20    private readonly IHttpClientFactory _httpClientFactory;
 21    private readonly ILogger<ListenBrainzLabsClient> _logger;
 2122    private readonly SemaphoreSlim _rateLimitLock = new(1, 1);
 23
 2124    private DateTime _lastRequestTime = DateTime.MinValue;
 25
 26    /// <summary>
 27    /// Initializes a new instance of the <see cref="ListenBrainzLabsClient"/> class.
 28    /// </summary>
 29    /// <param name="httpClientFactory">The HTTP client factory.</param>
 30    /// <param name="logger">The logger.</param>
 31    public ListenBrainzLabsClient(
 32        IHttpClientFactory httpClientFactory,
 33        ILogger<ListenBrainzLabsClient> logger)
 34    {
 2135        _httpClientFactory = httpClientFactory;
 2136        _logger = logger;
 2137    }
 38
 39    /// <summary>
 40    /// Gets similar artists for the given MusicBrainz artist ID.
 41    /// </summary>
 42    /// <param name="artistMbid">The MusicBrainz artist ID.</param>
 43    /// <param name="cancellationToken">The cancellation token.</param>
 44    /// <returns>A list of similar artist MusicBrainz IDs ordered by similarity score.</returns>
 45    public async Task<IReadOnlyList<Guid>> GetSimilarArtistsAsync(
 46        Guid artistMbid,
 47        CancellationToken cancellationToken)
 48    {
 049        var config = ListenBrainzPlugin.Instance?.Configuration;
 050        var baseUrl = config?.LabsServer ?? PluginConfiguration.DefaultLabsServer;
 051        var algorithm = config?.AlgorithmString ?? new PluginConfiguration().AlgorithmString;
 052        var rateLimit = config?.RateLimit ?? PluginConfiguration.DefaultRateLimit;
 53
 54        // Enforce rate limit
 055        await EnforceRateLimitAsync(rateLimit, cancellationToken).ConfigureAwait(false);
 56
 057        var url = $"{baseUrl}/similar-artists/json?artist_mbids={artistMbid}&algorithm={algorithm}";
 58
 059        _logger.LogDebug("Fetching similar artists from ListenBrainz Labs: {Url}", url);
 60
 61        try
 62        {
 063            var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
 064            var response = await httpClient.GetFromJsonAsync<List<SimilarArtistData>>(url, cancellationToken).ConfigureA
 65
 066            if (response is null || response.Count == 0)
 67            {
 068                _logger.LogDebug("No similar artists found for {ArtistMbid}", artistMbid);
 069                return [];
 70            }
 71
 072            var similarMbids = response
 073                .Where(a => !a.ArtistMbid.Equals(artistMbid)) // Exclude the source artist
 074                .OrderByDescending(a => a.Score)
 075                .Select(a => a.ArtistMbid)
 076                .ToList();
 77
 078            _logger.LogDebug("Found {Count} similar artists for {ArtistMbid}", similarMbids.Count, artistMbid);
 79
 080            return similarMbids;
 81        }
 082        catch (HttpRequestException ex)
 83        {
 084            _logger.LogWarning(ex, "Failed to fetch similar artists from ListenBrainz Labs for {ArtistMbid}", artistMbid
 085            return [];
 86        }
 087    }
 88
 89    /// <inheritdoc />
 90    public void Dispose()
 91    {
 2192        Dispose(true);
 2193        GC.SuppressFinalize(this);
 2194    }
 95
 96    /// <summary>
 97    /// Releases unmanaged and - optionally - managed resources.
 98    /// </summary>
 99    /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release onl
 100    protected virtual void Dispose(bool disposing)
 101    {
 21102        if (disposing)
 103        {
 21104            _rateLimitLock.Dispose();
 105        }
 21106    }
 107
 108    private async Task EnforceRateLimitAsync(double rateLimitSeconds, CancellationToken cancellationToken)
 109    {
 0110        await _rateLimitLock.WaitAsync(cancellationToken).ConfigureAwait(false);
 111        try
 112        {
 0113            var timeSinceLastRequest = DateTime.UtcNow - _lastRequestTime;
 0114            var requiredDelay = TimeSpan.FromSeconds(rateLimitSeconds) - timeSinceLastRequest;
 115
 0116            if (requiredDelay > TimeSpan.Zero)
 117            {
 0118                await Task.Delay(requiredDelay, cancellationToken).ConfigureAwait(false);
 119            }
 120
 0121            _lastRequestTime = DateTime.UtcNow;
 0122        }
 123        finally
 124        {
 0125            _rateLimitLock.Release();
 126        }
 0127    }
 128}