using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; using Microsoft.Extensions.Caching.Memory; using TvDbSharper; using TvDbSharper.Dto; namespace MediaBrowser.Providers.TV { // TODO add to DI once Bond's PR is merged public sealed class TvDbClientManager { private static volatile TvDbClientManager instance; // TODO add to DI once Bond's PR is merged private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1); private static MemoryCache _cache; private static readonly object syncRoot = new object(); private static TvDbClient tvDbClient; private static DateTime tokenCreatedAt; private TvDbClientManager() { tvDbClient = new TvDbClient(); tvDbClient.Authentication.AuthenticateAsync(TVUtils.TvdbApiKey); tokenCreatedAt = DateTime.Now; } public static TvDbClientManager Instance { get { if (instance != null) { return instance; } lock (syncRoot) { if (instance == null) { instance = new TvDbClientManager(); _cache = new MemoryCache(new MemoryCacheOptions()); } } return instance; } } public TvDbClient TvDbClient { get { // Refresh if necessary if (tokenCreatedAt > DateTime.Now.Subtract(TimeSpan.FromHours(20))) { try { tvDbClient.Authentication.RefreshTokenAsync(); } catch { tvDbClient.Authentication.AuthenticateAsync(TVUtils.TvdbApiKey); } tokenCreatedAt = DateTime.Now; } // Default to English tvDbClient.AcceptedLanguage = "en"; return tvDbClient; } } public Task> GetSeriesByNameAsync(string name, CancellationToken cancellationToken) { return TryGetValue("series" + name,() => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken)); } public Task> GetSeriesByIdAsync(int tvdbId, CancellationToken cancellationToken) { return TryGetValue("series" + tvdbId,() => TvDbClient.Series.GetAsync(tvdbId, cancellationToken)); } public Task> GetEpisodesAsync(int episodeTvdbId, CancellationToken cancellationToken) { return TryGetValue("episode" + episodeTvdbId,() => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken)); } public async Task> GetAllEpisodesAsync(int tvdbId, CancellationToken cancellationToken) { // Traverse all episode pages and join them together var episodes = new List(); var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), cancellationToken); episodes.AddRange(episodePage.Data); int next = episodePage.Links.Next.GetValueOrDefault(0); int last = episodePage.Links.Last.GetValueOrDefault(0); for (var page = next; page <= last; ++page) { episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), cancellationToken); episodes.AddRange(episodePage.Data); } return episodes; } public Task> GetSeriesByImdbIdAsync(string imdbId, CancellationToken cancellationToken) { return TryGetValue("series" + imdbId,() => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken)); } public Task> GetSeriesByZap2ItIdAsync(string zap2ItId, CancellationToken cancellationToken) { return TryGetValue("series" + zap2ItId,() => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken)); } public Task> GetActorsAsync(int tvdbId, CancellationToken cancellationToken) { return TryGetValue("actors" + tvdbId,() => TvDbClient.Series.GetActorsAsync(tvdbId, cancellationToken)); } public Task> GetImagesAsync(int tvdbId, ImagesQuery imageQuery, CancellationToken cancellationToken) { return TryGetValue("images" + tvdbId,() => TvDbClient.Series.GetImagesAsync(tvdbId, imageQuery, cancellationToken)); } public Task> GetLanguagesAsync(CancellationToken cancellationToken) { return TryGetValue("languages",() => TvDbClient.Languages.GetAllAsync(cancellationToken)); } public Task> GetSeriesEpisodeSummaryAsync(int tvdbId, CancellationToken cancellationToken) { return TryGetValue("seriesepisodesummary" + tvdbId, () => TvDbClient.Series.GetEpisodesSummaryAsync(tvdbId, cancellationToken)); } public Task> GetEpisodesPageAsync(int tvdbId, int page, EpisodeQuery episodeQuery, CancellationToken cancellationToken) { // Not quite as dynamic as it could be var cacheKey = "episodespage" + tvdbId + "page" + page; if (episodeQuery.AiredSeason.HasValue) { cacheKey += "airedseason" + episodeQuery.AiredSeason.Value; } return TryGetValue(cacheKey, () => TvDbClient.Series.GetEpisodesAsync(tvdbId, page, episodeQuery, cancellationToken)); } public Task> GetEpisodesPageAsync(int tvdbId, EpisodeQuery episodeQuery, CancellationToken cancellationToken) { return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, cancellationToken); } private async Task TryGetValue(object key, Func> resultFactory) { if (_cache.TryGetValue(key, out T cachedValue)) { return cachedValue; } await _cacheWriteLock.WaitAsync().ConfigureAwait(false); try { if (_cache.TryGetValue(key, out cachedValue)) { return cachedValue; } var result = await resultFactory.Invoke(); _cache.Set(key, result, TimeSpan.FromHours(1)); return result; } finally { _cacheWriteLock.Release(); } } } }