TvdbClientManager.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Controller.Providers;
  9. using MediaBrowser.Model.Entities;
  10. using Microsoft.Extensions.Caching.Memory;
  11. using TvDbSharper;
  12. using TvDbSharper.Dto;
  13. namespace MediaBrowser.Providers.Plugins.TheTvdb
  14. {
  15. public class TvdbClientManager
  16. {
  17. private const string DefaultLanguage = "en";
  18. private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1);
  19. private readonly IMemoryCache _cache;
  20. private readonly TvDbClient _tvDbClient;
  21. private DateTime _tokenCreatedAt;
  22. public TvdbClientManager(IMemoryCache memoryCache)
  23. {
  24. _cache = memoryCache;
  25. _tvDbClient = new TvDbClient();
  26. }
  27. private TvDbClient TvDbClient
  28. {
  29. get
  30. {
  31. if (string.IsNullOrEmpty(_tvDbClient.Authentication.Token))
  32. {
  33. _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult();
  34. _tokenCreatedAt = DateTime.Now;
  35. }
  36. // Refresh if necessary
  37. if (_tokenCreatedAt < DateTime.Now.Subtract(TimeSpan.FromHours(20)))
  38. {
  39. try
  40. {
  41. _tvDbClient.Authentication.RefreshTokenAsync().GetAwaiter().GetResult();
  42. }
  43. catch
  44. {
  45. _tvDbClient.Authentication.AuthenticateAsync(TvdbUtils.TvdbApiKey).GetAwaiter().GetResult();
  46. }
  47. _tokenCreatedAt = DateTime.Now;
  48. }
  49. return _tvDbClient;
  50. }
  51. }
  52. public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByNameAsync(string name, string language,
  53. CancellationToken cancellationToken)
  54. {
  55. var cacheKey = GenerateKey("series", name, language);
  56. return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByNameAsync(name, cancellationToken));
  57. }
  58. public Task<TvDbResponse<Series>> GetSeriesByIdAsync(int tvdbId, string language,
  59. CancellationToken cancellationToken)
  60. {
  61. var cacheKey = GenerateKey("series", tvdbId, language);
  62. return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetAsync(tvdbId, cancellationToken));
  63. }
  64. public Task<TvDbResponse<EpisodeRecord>> GetEpisodesAsync(int episodeTvdbId, string language,
  65. CancellationToken cancellationToken)
  66. {
  67. var cacheKey = GenerateKey("episode", episodeTvdbId, language);
  68. return TryGetValue(cacheKey, language, () => TvDbClient.Episodes.GetAsync(episodeTvdbId, cancellationToken));
  69. }
  70. public async Task<List<EpisodeRecord>> GetAllEpisodesAsync(int tvdbId, string language,
  71. CancellationToken cancellationToken)
  72. {
  73. // Traverse all episode pages and join them together
  74. var episodes = new List<EpisodeRecord>();
  75. var episodePage = await GetEpisodesPageAsync(tvdbId, new EpisodeQuery(), language, cancellationToken)
  76. .ConfigureAwait(false);
  77. episodes.AddRange(episodePage.Data);
  78. if (!episodePage.Links.Next.HasValue || !episodePage.Links.Last.HasValue)
  79. {
  80. return episodes;
  81. }
  82. int next = episodePage.Links.Next.Value;
  83. int last = episodePage.Links.Last.Value;
  84. for (var page = next; page <= last; ++page)
  85. {
  86. episodePage = await GetEpisodesPageAsync(tvdbId, page, new EpisodeQuery(), language, cancellationToken)
  87. .ConfigureAwait(false);
  88. episodes.AddRange(episodePage.Data);
  89. }
  90. return episodes;
  91. }
  92. public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByImdbIdAsync(
  93. string imdbId,
  94. string language,
  95. CancellationToken cancellationToken)
  96. {
  97. var cacheKey = GenerateKey("series", imdbId, language);
  98. return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByImdbIdAsync(imdbId, cancellationToken));
  99. }
  100. public Task<TvDbResponse<SeriesSearchResult[]>> GetSeriesByZap2ItIdAsync(
  101. string zap2ItId,
  102. string language,
  103. CancellationToken cancellationToken)
  104. {
  105. var cacheKey = GenerateKey("series", zap2ItId, language);
  106. return TryGetValue(cacheKey, language, () => TvDbClient.Search.SearchSeriesByZap2ItIdAsync(zap2ItId, cancellationToken));
  107. }
  108. public Task<TvDbResponse<Actor[]>> GetActorsAsync(
  109. int tvdbId,
  110. string language,
  111. CancellationToken cancellationToken)
  112. {
  113. var cacheKey = GenerateKey("actors", tvdbId, language);
  114. return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetActorsAsync(tvdbId, cancellationToken));
  115. }
  116. public Task<TvDbResponse<Image[]>> GetImagesAsync(
  117. int tvdbId,
  118. ImagesQuery imageQuery,
  119. string language,
  120. CancellationToken cancellationToken)
  121. {
  122. var cacheKey = GenerateKey("images", tvdbId, language, imageQuery);
  123. return TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesAsync(tvdbId, imageQuery, cancellationToken));
  124. }
  125. public Task<TvDbResponse<Language[]>> GetLanguagesAsync(CancellationToken cancellationToken)
  126. {
  127. return TryGetValue("languages", null, () => TvDbClient.Languages.GetAllAsync(cancellationToken));
  128. }
  129. public Task<TvDbResponse<EpisodesSummary>> GetSeriesEpisodeSummaryAsync(
  130. int tvdbId,
  131. string language,
  132. CancellationToken cancellationToken)
  133. {
  134. var cacheKey = GenerateKey("seriesepisodesummary", tvdbId, language);
  135. return TryGetValue(cacheKey, language,
  136. () => TvDbClient.Series.GetEpisodesSummaryAsync(tvdbId, cancellationToken));
  137. }
  138. public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(
  139. int tvdbId,
  140. int page,
  141. EpisodeQuery episodeQuery,
  142. string language,
  143. CancellationToken cancellationToken)
  144. {
  145. var cacheKey = GenerateKey(language, tvdbId, episodeQuery);
  146. return TryGetValue(cacheKey, language,
  147. () => TvDbClient.Series.GetEpisodesAsync(tvdbId, page, episodeQuery, cancellationToken));
  148. }
  149. public Task<string> GetEpisodeTvdbId(
  150. EpisodeInfo searchInfo,
  151. string language,
  152. CancellationToken cancellationToken)
  153. {
  154. searchInfo.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(),
  155. out var seriesTvdbId);
  156. var episodeQuery = new EpisodeQuery();
  157. // Prefer SxE over premiere date as it is more robust
  158. if (searchInfo.IndexNumber.HasValue && searchInfo.ParentIndexNumber.HasValue)
  159. {
  160. switch (searchInfo.SeriesDisplayOrder)
  161. {
  162. case "dvd":
  163. episodeQuery.DvdEpisode = searchInfo.IndexNumber.Value;
  164. episodeQuery.DvdSeason = searchInfo.ParentIndexNumber.Value;
  165. break;
  166. case "absolute":
  167. episodeQuery.AbsoluteNumber = searchInfo.IndexNumber.Value;
  168. break;
  169. default:
  170. // aired order
  171. episodeQuery.AiredEpisode = searchInfo.IndexNumber.Value;
  172. episodeQuery.AiredSeason = searchInfo.ParentIndexNumber.Value;
  173. break;
  174. }
  175. }
  176. else if (searchInfo.PremiereDate.HasValue)
  177. {
  178. // tvdb expects yyyy-mm-dd format
  179. episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd");
  180. }
  181. return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken);
  182. }
  183. public async Task<string> GetEpisodeTvdbId(
  184. int seriesTvdbId,
  185. EpisodeQuery episodeQuery,
  186. string language,
  187. CancellationToken cancellationToken)
  188. {
  189. var episodePage =
  190. await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken)
  191. .ConfigureAwait(false);
  192. return episodePage.Data.FirstOrDefault()?.Id.ToString();
  193. }
  194. public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(
  195. int tvdbId,
  196. EpisodeQuery episodeQuery,
  197. string language,
  198. CancellationToken cancellationToken)
  199. {
  200. return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
  201. }
  202. private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
  203. {
  204. if (_cache.TryGetValue(key, out T cachedValue))
  205. {
  206. return cachedValue;
  207. }
  208. await _cacheWriteLock.WaitAsync().ConfigureAwait(false);
  209. try
  210. {
  211. if (_cache.TryGetValue(key, out cachedValue))
  212. {
  213. return cachedValue;
  214. }
  215. _tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
  216. var result = await resultFactory.Invoke().ConfigureAwait(false);
  217. _cache.Set(key, result, TimeSpan.FromHours(1));
  218. return result;
  219. }
  220. finally
  221. {
  222. _cacheWriteLock.Release();
  223. }
  224. }
  225. private static string GenerateKey(params object[] objects)
  226. {
  227. var key = string.Empty;
  228. foreach (var obj in objects)
  229. {
  230. var objType = obj.GetType();
  231. if (objType.IsPrimitive || objType == typeof(string))
  232. {
  233. key += obj + ";";
  234. }
  235. else
  236. {
  237. foreach (PropertyInfo propertyInfo in objType.GetProperties())
  238. {
  239. var currentValue = propertyInfo.GetValue(obj, null);
  240. if (currentValue == null)
  241. {
  242. continue;
  243. }
  244. key += propertyInfo.Name + "=" + currentValue + ";";
  245. }
  246. }
  247. }
  248. return key;
  249. }
  250. }
  251. }