TvdbClientManager.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeriesAsync(int tvdbId, string language, CancellationToken cancellationToken)
  203. {
  204. var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
  205. var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
  206. if (imagesSummary.Data.Fanart > 0)
  207. {
  208. yield return KeyType.Fanart;
  209. }
  210. if (imagesSummary.Data.Series > 0)
  211. {
  212. yield return KeyType.Series;
  213. }
  214. if (imagesSummary.Data.Poster > 0)
  215. {
  216. yield return KeyType.Poster;
  217. }
  218. }
  219. public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeasonAsync(int tvdbId, string language, CancellationToken cancellationToken)
  220. {
  221. var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
  222. var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
  223. if (imagesSummary.Data.Season > 0)
  224. {
  225. yield return KeyType.Season;
  226. }
  227. if (imagesSummary.Data.Fanart > 0)
  228. {
  229. yield return KeyType.Fanart;
  230. }
  231. // TODO seasonwide is not supported in TvDbSharper
  232. }
  233. private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
  234. {
  235. if (_cache.TryGetValue(key, out T cachedValue))
  236. {
  237. return cachedValue;
  238. }
  239. await _cacheWriteLock.WaitAsync().ConfigureAwait(false);
  240. try
  241. {
  242. if (_cache.TryGetValue(key, out cachedValue))
  243. {
  244. return cachedValue;
  245. }
  246. _tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
  247. var result = await resultFactory.Invoke().ConfigureAwait(false);
  248. _cache.Set(key, result, TimeSpan.FromHours(1));
  249. return result;
  250. }
  251. finally
  252. {
  253. _cacheWriteLock.Release();
  254. }
  255. }
  256. private static string GenerateKey(params object[] objects)
  257. {
  258. var key = string.Empty;
  259. foreach (var obj in objects)
  260. {
  261. var objType = obj.GetType();
  262. if (objType.IsPrimitive || objType == typeof(string))
  263. {
  264. key += obj + ";";
  265. }
  266. else
  267. {
  268. foreach (PropertyInfo propertyInfo in objType.GetProperties())
  269. {
  270. var currentValue = propertyInfo.GetValue(obj, null);
  271. if (currentValue == null)
  272. {
  273. continue;
  274. }
  275. key += propertyInfo.Name + "=" + currentValue + ";";
  276. }
  277. }
  278. }
  279. return key;
  280. }
  281. }
  282. }