Skip to content

Commit

Permalink
Merge pull request #118 from scampower3/cache
Browse files Browse the repository at this point in the history
Add Caching for api calls
  • Loading branch information
crobibero authored Mar 8, 2024
2 parents 98856e5 + 55c5c83 commit 947fbe5
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 2 deletions.
65 changes: 65 additions & 0 deletions Jellyfin.Plugin.Tvdb/ScheduledTasks/PurgeCacheTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;

namespace Jellyfin.Plugin.Tvdb.ScheduledTasks
{
/// <summary>
/// Task to purge TheTVDB plugin cache.
/// </summary>
public class PurgeCacheTask : IScheduledTask
{
private readonly ILogger<PurgeCacheTask> _logger;
private readonly TvdbClientManager _tvdbClientManager;

/// <summary>
/// Initializes a new instance of the <see cref="PurgeCacheTask"/> class.
/// </summary>
/// <param name="logger">Instance of the <see cref="ILogger{TvdbScheduledTask}"/> interface.</param>
/// <param name="tvdbClientManager">Instance of <see cref="TvdbClientManager"/>.</param>
public PurgeCacheTask(
ILogger<PurgeCacheTask> logger,
TvdbClientManager tvdbClientManager)
{
_logger = logger;
_tvdbClientManager = tvdbClientManager;
}

/// <inheritdoc/>
public string Name => "Purge TheTVDB plugin cache";

/// <inheritdoc/>
public string Key => "PurgeTheTVDBPluginCache";

/// <inheritdoc/>
public string Description => "Purges the TheTVDB Cache";

/// <inheritdoc/>
public string Category => "TheTVDB";

/// <inheritdoc/>
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
if (_tvdbClientManager.PurgeCache())
{
_logger.LogInformation("TheTvdb plugin cache purged successfully");
}
else
{
_logger.LogError("TheTvdb plugin cache purge failed");
}

return Task.CompletedTask;
}

/// <inheritdoc/>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return Enumerable.Empty<TaskTriggerInfo>();
}
}
}
143 changes: 141 additions & 2 deletions Jellyfin.Plugin.Tvdb/TvdbClientManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Jellyfin.Plugin.Tvdb.Configuration;
using MediaBrowser.Common;
using MediaBrowser.Controller.Providers;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Tvdb.Sdk;

Expand All @@ -18,13 +19,15 @@ namespace Jellyfin.Plugin.Tvdb;
/// <summary>
/// Tvdb client manager.
/// </summary>
public class TvdbClientManager
public class TvdbClientManager : IDisposable
{
private const string TvdbHttpClient = "TvdbHttpClient";
private const int CacheDurationInHours = 1;
private static readonly SemaphoreSlim _tokenUpdateLock = new SemaphoreSlim(1, 1);

private readonly IHttpClientFactory _httpClientFactory;
private readonly IServiceProvider _serviceProvider;
private readonly IMemoryCache _memoryCache;
private readonly SdkClientSettings _sdkClientSettings;

private DateTime _tokenUpdatedAt;
Expand All @@ -38,6 +41,7 @@ public TvdbClientManager(IApplicationHost applicationHost)
_serviceProvider = ConfigureService(applicationHost);
_httpClientFactory = _serviceProvider.GetRequiredService<IHttpClientFactory>();
_sdkClientSettings = _serviceProvider.GetRequiredService<SdkClientSettings>();
_memoryCache = new MemoryCache(new MemoryCacheOptions());

_tokenUpdatedAt = DateTime.MinValue;
}
Expand Down Expand Up @@ -95,10 +99,17 @@ public async Task<IReadOnlyList<SearchResult>> GetSeriesByNameAsync(
string language,
CancellationToken cancellationToken)
{
var key = $"TvdbSeriesSearch_{name}";
if (_memoryCache.TryGetValue(key, out IReadOnlyList<SearchResult> series))
{
return series;
}

var searchClient = _serviceProvider.GetRequiredService<ISearchClient>();
await LoginAsync().ConfigureAwait(false);
var searchResult = await searchClient.GetSearchResultsAsync(query: name, type: "series", limit: 5, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, searchResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return searchResult.Data;
}

Expand All @@ -114,10 +125,17 @@ public async Task<SeriesBaseRecord> GetSeriesByIdAsync(
string language,
CancellationToken cancellationToken)
{
var key = $"TvdbSeries_{tvdbId.ToString(CultureInfo.InvariantCulture)}";
if (_memoryCache.TryGetValue(key, out SeriesBaseRecord series))
{
return series;
}

var seriesClient = _serviceProvider.GetRequiredService<ISeriesClient>();
await LoginAsync().ConfigureAwait(false);
var seriesResult = await seriesClient.GetSeriesBaseAsync(id: tvdbId, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, seriesResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return seriesResult.Data;
}

Expand All @@ -137,10 +155,17 @@ public async Task<SeriesExtendedRecord> GetSeriesExtendedByIdAsync(
Meta4? meta = null,
bool? small = null)
{
var key = $"TvdbSeriesExtended_{tvdbId.ToString(CultureInfo.InvariantCulture)}";
if (_memoryCache.TryGetValue(key, out SeriesExtendedRecord series))
{
return series;
}

var seriesClient = _serviceProvider.GetRequiredService<ISeriesClient>();
await LoginAsync().ConfigureAwait(false);
var seriesResult = await seriesClient.GetSeriesExtendedAsync(id: tvdbId, meta: meta, @short: small, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, seriesResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return seriesResult.Data;
}

Expand All @@ -158,10 +183,17 @@ public async Task<Data2> GetSeriesEpisodesAsync(
string seasonType,
CancellationToken cancellationToken)
{
var key = $"TvdbSeriesEpisodes_{tvdbId.ToString(CultureInfo.InvariantCulture)}";
if (_memoryCache.TryGetValue(key, out Data2 series))
{
return series;
}

var seriesClient = _serviceProvider.GetRequiredService<ISeriesClient>();
await LoginAsync().ConfigureAwait(false);
var seriesResult = await seriesClient.GetSeriesEpisodesAsync(id: tvdbId, season_type: seasonType, cancellationToken: cancellationToken, page: 0)
.ConfigureAwait(false);
_memoryCache.Set(key, seriesResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return seriesResult.Data;
}

Expand All @@ -177,10 +209,17 @@ public async Task<SeasonExtendedRecord> GetSeasonByIdAsync(
string language,
CancellationToken cancellationToken)
{
var key = $"TvdbSeason_{seasonTvdbId.ToString(CultureInfo.InvariantCulture)}";
if (_memoryCache.TryGetValue(key, out SeasonExtendedRecord season))
{
return season;
}

var seasonClient = _serviceProvider.GetRequiredService<ISeasonsClient>();
await LoginAsync().ConfigureAwait(false);
var seasonResult = await seasonClient.GetSeasonExtendedAsync(id: seasonTvdbId, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, seasonResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return seasonResult.Data;
}

Expand All @@ -196,10 +235,17 @@ public async Task<EpisodeExtendedRecord> GetEpisodesAsync(
string language,
CancellationToken cancellationToken)
{
var key = $"TvdbEpisode_{episodeTvdbId.ToString(CultureInfo.InvariantCulture)}";
if (_memoryCache.TryGetValue(key, out EpisodeExtendedRecord episode))
{
return episode;
}

var episodeClient = _serviceProvider.GetRequiredService<IEpisodesClient>();
await LoginAsync().ConfigureAwait(false);
var episodeResult = await episodeClient.GetEpisodeExtendedAsync(id: episodeTvdbId, meta: Meta.Translations, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, episodeResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return episodeResult.Data;
}

Expand All @@ -215,10 +261,17 @@ public async Task<IReadOnlyList<SearchByRemoteIdResult>> GetSeriesByRemoteIdAsyn
string language,
CancellationToken cancellationToken)
{
var key = $"TvdbSeriesRemoteId_{remoteId}";
if (_memoryCache.TryGetValue(key, out IReadOnlyList<SearchByRemoteIdResult> series))
{
return series;
}

var searchClient = _serviceProvider.GetRequiredService<ISearchClient>();
await LoginAsync().ConfigureAwait(false);
var searchResult = await searchClient.GetSearchResultsByRemoteIdAsync(remoteId: remoteId, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, searchResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return searchResult.Data;
}

Expand All @@ -234,10 +287,17 @@ public async Task<PeopleBaseRecord> GetActorAsync(
string language,
CancellationToken cancellationToken)
{
var key = $"TvdbPeople_{tvdbId.ToString(CultureInfo.InvariantCulture)}";
if (_memoryCache.TryGetValue(key, out PeopleBaseRecord people))
{
return people;
}

var peopleClient = _serviceProvider.GetRequiredService<IPeopleClient>();
await LoginAsync().ConfigureAwait(false);
var peopleResult = await peopleClient.GetPeopleBaseAsync(id: tvdbId, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, peopleResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return peopleResult.Data;
}

Expand All @@ -253,10 +313,17 @@ public async Task<ArtworkExtendedRecord> GetImageAsync(
string language,
CancellationToken cancellationToken)
{
var key = $"TvdbArtwork_{imageTvdbId.ToString(CultureInfo.InvariantCulture)}";
if (_memoryCache.TryGetValue(key, out ArtworkExtendedRecord artwork))
{
return artwork;
}

var artworkClient = _serviceProvider.GetRequiredService<IArtworkClient>();
await LoginAsync().ConfigureAwait(false);
var artworkResult = await artworkClient.GetArtworkExtendedAsync(id: imageTvdbId, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, artworkResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return artworkResult.Data;
}

Expand All @@ -272,10 +339,17 @@ public async Task<SeriesExtendedRecord> GetSeriesImagesAsync(
string language,
CancellationToken cancellationToken)
{
var key = $"TvdbSeriesArtwork_{tvdbId.ToString(CultureInfo.InvariantCulture)}";
if (_memoryCache.TryGetValue(key, out SeriesExtendedRecord series))
{
return series;
}

var seriesClient = _serviceProvider.GetRequiredService<ISeriesClient>();
await LoginAsync().ConfigureAwait(false);
var seriesResult = await seriesClient.GetSeriesArtworksAsync(id: tvdbId, cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, seriesResult.Data, TimeSpan.FromHours(CacheDurationInHours));
return seriesResult.Data;
}

Expand All @@ -286,10 +360,17 @@ public async Task<SeriesExtendedRecord> GetSeriesImagesAsync(
/// <returns>All tvdb languages.</returns>
public async Task<IReadOnlyList<Language>> GetLanguagesAsync(CancellationToken cancellationToken)
{
var key = "TvdbLanguages";
if (_memoryCache.TryGetValue(key, out IReadOnlyList<Language> languages))
{
return languages;
}

var languagesClient = _serviceProvider.GetRequiredService<ILanguagesClient>();
await LoginAsync().ConfigureAwait(false);
var languagesResult = await languagesClient.GetAllLanguagesAsync(cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, languagesResult.Data, TimeSpan.FromDays(1));
return languagesResult.Data;
}

Expand All @@ -300,10 +381,17 @@ public async Task<IReadOnlyList<Language>> GetLanguagesAsync(CancellationToken c
/// <returns>All tvdb artwork types.</returns>
public async Task<IReadOnlyList<ArtworkType>> GetArtworkTypeAsync(CancellationToken cancellationToken)
{
var key = "TvdbArtworkTypes";
if (_memoryCache.TryGetValue(key, out IReadOnlyList<ArtworkType> artworkTypes))
{
return artworkTypes;
}

var artworkTypesClient = _serviceProvider.GetRequiredService<IArtwork_TypesClient>();
await LoginAsync().ConfigureAwait(false);
var artworkTypesResult = await artworkTypesClient.GetAllArtworkTypesAsync(cancellationToken: cancellationToken)
.ConfigureAwait(false);
_memoryCache.Set(key, artworkTypesResult.Data, TimeSpan.FromDays(1));
return artworkTypesResult.Data;
}

Expand Down Expand Up @@ -331,6 +419,7 @@ public async Task<IReadOnlyList<ArtworkType>> GetArtworkTypeAsync(CancellationTo
int? seasonNumber = null;
string? airDate = null;
bool special = false;
string? key = null;
// Prefer SxE over premiere date as it is more robust
if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue)
{
Expand Down Expand Up @@ -359,11 +448,19 @@ public async Task<IReadOnlyList<ArtworkType>> GetArtworkTypeAsync(CancellationTo
seasonNumber = searchInfo.ParentIndexNumber.Value;
break;
}

key = $"FindTvdbEpisodeId_{seriesTvdbIdString}_{seasonNumber.Value.ToString(CultureInfo.InvariantCulture)}_{episodeNumber.Value.ToString(CultureInfo.InvariantCulture)}";
}
else if (searchInfo.PremiereDate.HasValue)
{
// tvdb expects yyyy-mm-dd format
airDate = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
key = $"FindTvdbEpisodeId_{seriesTvdbIdString}_{airDate}";
}

if (key != null && _memoryCache.TryGetValue(key, out string? episodeTvdbId))
{
return episodeTvdbId;
}

Response56 seriesResponse;
Expand Down Expand Up @@ -393,7 +490,30 @@ public async Task<IReadOnlyList<ArtworkType>> GetArtworkTypeAsync(CancellationTo
}
else
{
return seriesData.Episodes[0].Id?.ToString(CultureInfo.InvariantCulture);
var tvdbId = seriesData.Episodes[0].Id?.ToString(CultureInfo.InvariantCulture);
if (key != null)
{
_memoryCache.Set(key, tvdbId, TimeSpan.FromHours(CacheDurationInHours));
}

return tvdbId;
}
}

/// <summary>
/// Purge the cache.
/// </summary>
/// <returns>True if success else false.</returns>
public bool PurgeCache()
{
if (_memoryCache is MemoryCache memoryCache)
{
memoryCache.Compact(1);
return true;
}
else
{
return false;
}
}

Expand Down Expand Up @@ -441,4 +561,23 @@ private ServiceProvider ConfigureService(IApplicationHost applicationHost)

return services.BuildServiceProvider();
}

/// <inheritdoc/>
public void Dispose()
{
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)
{
_memoryCache?.Dispose();
}
}
}

0 comments on commit 947fbe5

Please sign in to comment.