TvDbClientManager.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using MediaBrowser.Controller.Library;
  7. using MediaBrowser.Controller.Providers;
  8. using MediaBrowser.Model.Entities;
  9. using Microsoft.Extensions.Caching.Memory;
  10. using TvDbSharper;
  11. using TvDbSharper.Dto;
  12. namespace MediaBrowser.Providers.TV
  13. {
  14. // TODO add to DI once Bond's PR is merged
  15. public sealed class TvDbClientManager
  16. {
  17. private static volatile TvDbClientManager instance;
  18. // TODO add to DI once Bond's PR is merged
  19. private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1);
  20. private static MemoryCache _cache;
  21. private static readonly object syncRoot = new object();
  22. private static TvDbClient tvDbClient;
  23. private static DateTime tokenCreatedAt;
  24. private TvDbClientManager()
  25. {
  26. tvDbClient = new TvDbClient();
  27. tvDbClient.Authentication.AuthenticateAsync(TVUtils.TvdbApiKey);
  28. tokenCreatedAt = DateTime.Now;
  29. }
  30. public static TvDbClientManager Instance
  31. {
  32. get
  33. {
  34. if (instance != null)
  35. {
  36. return instance;
  37. }
  38. lock (syncRoot)
  39. {
  40. if (instance == null)
  41. {
  42. instance = new TvDbClientManager();
  43. _cache = new MemoryCache(new MemoryCacheOptions());
  44. }
  45. }
  46. return instance;
  47. }
  48. }
  49. public TvDbClient TvDbClient
  50. {
  51. get
  52. {
  53. // Refresh if necessary
  54. if (tokenCreatedAt > DateTime.Now.Subtract(TimeSpan.FromHours(20)))
  55. {
  56. try
  57. {
  58. tvDbClient.Authentication.RefreshTokenAsync();
  59. }
  60. catch
  61. {
  62. tvDbClient.Authentication.AuthenticateAsync(TVUtils.TvdbApiKey);
  63. }
  64. tokenCreatedAt = DateTime.Now;
  65. }
  66. // Default to English
  67. tvDbClient.AcceptedLanguage = "en";
  68. return tvDbClient;
  69. }
  70. }
  71. public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByNameAsync(string name, CancellationToken cancellationToken)
  72. {
  73. return TryGetValue("series" + name,() => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken));
  74. }
  75. public Task<TvDbResponse<Series>> GetSeriesByIdAsync(int tvdbId, CancellationToken cancellationToken)
  76. {
  77. return TryGetValue("series" + tvdbId,() => TvDbClient.Series.GetAsync(tvdbId, cancellationToken));
  78. }
  79. public Task<TvDbResponse<EpisodeRecord>> GetEpisodesAsync(int episodeTvdbId, CancellationToken cancellationToken)
  80. {
  81. return TryGetValue("episode" + episodeTvdbId,() => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
  82. }
  83. public async Task<List<EpisodeRecord>> GetAllEpisodesAsync(int tvdbId, CancellationToken cancellationToken)
  84. {
  85. // Traverse all episode pages and join them together
  86. var episodes = new List<EpisodeRecord>();
  87. var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), cancellationToken);
  88. episodes.AddRange(episodePage.Data);
  89. if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue)
  90. {
  91. return episodes;
  92. }
  93. int next = episodePage.Links.Next.Value;
  94. int last = episodePage.Links.Last.Value;
  95. for (var page = next; page <= last; ++page)
  96. {
  97. episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), cancellationToken);
  98. episodes.AddRange(episodePage.Data);
  99. }
  100. return episodes;
  101. }
  102. public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByImdbIdAsync(string imdbId, CancellationToken cancellationToken)
  103. {
  104. return TryGetValue("series" + imdbId,() => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken));
  105. }
  106. public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByZap2ItIdAsync(string zap2ItId, CancellationToken cancellationToken)
  107. {
  108. return TryGetValue("series" + zap2ItId,() => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken));
  109. }
  110. public Task<TvDbResponse<Actor[]>> GetActorsAsync(int tvdbId, CancellationToken cancellationToken)
  111. {
  112. return TryGetValue("actors" + tvdbId,() => TvDbClient.Series.GetActorsAsync(tvdbId, cancellationToken));
  113. }
  114. public Task<TvDbResponse<Image[]>> GetImagesAsync(int tvdbId, ImagesQuery imageQuery, CancellationToken cancellationToken)
  115. {
  116. var cacheKey = "images" + tvdbId + "keytype" + imageQuery.KeyType + "subkey" + imageQuery.SubKey;
  117. return TryGetValue(cacheKey,() => TvDbClient.Series.GetImagesAsync(tvdbId, imageQuery, cancellationToken));
  118. }
  119. public Task<TvDbResponse<Language[]>> GetLanguagesAsync(CancellationToken cancellationToken)
  120. {
  121. return TryGetValue("languages",() => TvDbClient.Languages.GetAllAsync(cancellationToken));
  122. }
  123. public Task<TvDbResponse<EpisodesSummary>> GetSeriesEpisodeSummaryAsync(int tvdbId, CancellationToken cancellationToken)
  124. {
  125. return TryGetValue("seriesepisodesummary" + tvdbId,
  126. () => TvDbClient.Series.GetEpisodesSummaryAsync(tvdbId, cancellationToken));
  127. }
  128. public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(int tvdbId, int page, EpisodeQuery episodeQuery, CancellationToken cancellationToken)
  129. {
  130. // Not quite as dynamic as it could be
  131. var cacheKey = "episodespage" + tvdbId + "page" + page;
  132. if (episodeQuery.AiredSeason.HasValue)
  133. {
  134. cacheKey += "airedseason" + episodeQuery.AiredSeason.Value;
  135. }
  136. if (episodeQuery.AiredEpisode.HasValue)
  137. {
  138. cacheKey += "airedepisode" + episodeQuery.AiredEpisode.Value;
  139. }
  140. return TryGetValue(cacheKey,
  141. () => TvDbClient.Series.GetEpisodesAsync(tvdbId, page, episodeQuery, cancellationToken));
  142. }
  143. public Task<string> GetEpisodeTvdbId(EpisodeInfo searchInfo, CancellationToken cancellationToken)
  144. {
  145. searchInfo.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(),
  146. out var seriesTvdbId);
  147. var episodeNumber = searchInfo.IndexNumber.Value;
  148. var seasonNumber = searchInfo.ParentIndexNumber.Value;
  149. return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId), episodeNumber, seasonNumber, cancellationToken);
  150. }
  151. public async Task<string> GetEpisodeTvdbId(int seriesTvdbId, int episodeNumber, int seasonNumber, CancellationToken cancellationToken)
  152. {
  153. var episodeQuery = new EpisodeQuery
  154. {
  155. AiredSeason = seasonNumber,
  156. AiredEpisode = episodeNumber
  157. };
  158. var episodePage = await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId),
  159. episodeQuery, cancellationToken);
  160. return episodePage.Data.FirstOrDefault()?.Id.ToString();
  161. }
  162. public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(int tvdbId, EpisodeQuery episodeQuery, CancellationToken cancellationToken)
  163. {
  164. return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, cancellationToken);
  165. }
  166. private async Task<T> TryGetValue<T>(object key, Func<Task<T>> resultFactory)
  167. {
  168. if (_cache.TryGetValue(key, out T cachedValue))
  169. {
  170. return cachedValue;
  171. }
  172. await _cacheWriteLock.WaitAsync().ConfigureAwait(false);
  173. try
  174. {
  175. if (_cache.TryGetValue(key, out cachedValue))
  176. {
  177. return cachedValue;
  178. }
  179. var result = await resultFactory.Invoke();
  180. _cache.Set(key, result, TimeSpan.FromHours(1));
  181. return result;
  182. }
  183. finally
  184. {
  185. _cacheWriteLock.Release();
  186. }
  187. }
  188. }
  189. }