TvdbPrescanTask.cs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Common.Net;
  3. using MediaBrowser.Controller.Extensions;
  4. using MediaBrowser.Controller.Library;
  5. using MediaBrowser.Model.Logging;
  6. using MediaBrowser.Model.Net;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Text;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. using System.Xml;
  15. namespace MediaBrowser.Controller.Providers.TV
  16. {
  17. /// <summary>
  18. /// Class TvdbPrescanTask
  19. /// </summary>
  20. public class TvdbPrescanTask : ILibraryPrescanTask
  21. {
  22. /// <summary>
  23. /// The server time URL
  24. /// </summary>
  25. private const string ServerTimeUrl = "http://thetvdb.com/api/Updates.php?type=none";
  26. /// <summary>
  27. /// The updates URL
  28. /// </summary>
  29. private const string UpdatesUrl = "http://thetvdb.com/api/Updates.php?type=all&time={0}";
  30. /// <summary>
  31. /// The _HTTP client
  32. /// </summary>
  33. private readonly IHttpClient _httpClient;
  34. /// <summary>
  35. /// The _logger
  36. /// </summary>
  37. private readonly ILogger _logger;
  38. /// <summary>
  39. /// The _config
  40. /// </summary>
  41. private readonly IConfigurationManager _config;
  42. /// <summary>
  43. /// Initializes a new instance of the <see cref="TvdbPrescanTask"/> class.
  44. /// </summary>
  45. /// <param name="logger">The logger.</param>
  46. /// <param name="httpClient">The HTTP client.</param>
  47. /// <param name="config">The config.</param>
  48. public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IConfigurationManager config)
  49. {
  50. _logger = logger;
  51. _httpClient = httpClient;
  52. _config = config;
  53. }
  54. /// <summary>
  55. /// Runs the specified progress.
  56. /// </summary>
  57. /// <param name="progress">The progress.</param>
  58. /// <param name="cancellationToken">The cancellation token.</param>
  59. /// <returns>Task.</returns>
  60. public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
  61. {
  62. var path = RemoteSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
  63. var timestampFile = Path.Combine(path, "time.txt");
  64. var timestampFileInfo = new FileInfo(timestampFile);
  65. // Don't check for tvdb updates anymore frequently than 24 hours
  66. if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1)
  67. {
  68. return;
  69. }
  70. // Find out the last time we queried tvdb for updates
  71. var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
  72. string newUpdateTime;
  73. var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList();
  74. // If this is our first time, update all series
  75. if (string.IsNullOrEmpty(lastUpdateTime))
  76. {
  77. // First get tvdb server time
  78. using (var stream = await _httpClient.Get(new HttpRequestOptions
  79. {
  80. Url = ServerTimeUrl,
  81. CancellationToken = cancellationToken,
  82. EnableHttpCompression = true,
  83. ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool
  84. }).ConfigureAwait(false))
  85. {
  86. var doc = new XmlDocument();
  87. doc.Load(stream);
  88. newUpdateTime = doc.SafeGetString("//Time");
  89. }
  90. await UpdateSeries(existingDirectories, path, progress, cancellationToken).ConfigureAwait(false);
  91. }
  92. else
  93. {
  94. var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false);
  95. newUpdateTime = seriesToUpdate.Item2;
  96. await UpdateSeries(seriesToUpdate.Item1, path, progress, cancellationToken).ConfigureAwait(false);
  97. }
  98. File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
  99. progress.Report(100);
  100. }
  101. /// <summary>
  102. /// Gets the series ids to update.
  103. /// </summary>
  104. /// <param name="existingSeriesIds">The existing series ids.</param>
  105. /// <param name="lastUpdateTime">The last update time.</param>
  106. /// <param name="cancellationToken">The cancellation token.</param>
  107. /// <returns>Task{IEnumerable{System.String}}.</returns>
  108. private async Task<Tuple<IEnumerable<string>, string>> GetSeriesIdsToUpdate(IEnumerable<string> existingSeriesIds, string lastUpdateTime, CancellationToken cancellationToken)
  109. {
  110. // First get last time
  111. using (var stream = await _httpClient.Get(new HttpRequestOptions
  112. {
  113. Url = string.Format(UpdatesUrl, lastUpdateTime),
  114. CancellationToken = cancellationToken,
  115. EnableHttpCompression = true,
  116. ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool
  117. }).ConfigureAwait(false))
  118. {
  119. var doc = new XmlDocument();
  120. doc.Load(stream);
  121. var newUpdateTime = doc.SafeGetString("//Time");
  122. var seriesNodes = doc.SelectNodes("//Series");
  123. var seriesList = seriesNodes == null ? new string[] { } :
  124. seriesNodes.Cast<XmlNode>()
  125. .Select(i => i.InnerText)
  126. .Where(i => !string.IsNullOrWhiteSpace(i) && existingSeriesIds.Contains(i, StringComparer.OrdinalIgnoreCase));
  127. return new Tuple<IEnumerable<string>, string>(seriesList, newUpdateTime);
  128. }
  129. }
  130. /// <summary>
  131. /// Updates the series.
  132. /// </summary>
  133. /// <param name="seriesIds">The series ids.</param>
  134. /// <param name="seriesDataPath">The series data path.</param>
  135. /// <param name="progress">The progress.</param>
  136. /// <param name="cancellationToken">The cancellation token.</param>
  137. /// <returns>Task.</returns>
  138. private async Task UpdateSeries(IEnumerable<string> seriesIds, string seriesDataPath, IProgress<double> progress, CancellationToken cancellationToken)
  139. {
  140. var list = seriesIds.ToList();
  141. var numComplete = 0;
  142. foreach (var seriesId in list)
  143. {
  144. try
  145. {
  146. await UpdateSeries(seriesId, seriesDataPath, cancellationToken).ConfigureAwait(false);
  147. }
  148. catch (HttpException ex)
  149. {
  150. // Already logged at lower levels, but don't fail the whole operation, unless timed out
  151. // We have to fail this to make it run again otherwise new episode data could potentially be missing
  152. if (ex.IsTimedOut)
  153. {
  154. throw;
  155. }
  156. }
  157. numComplete++;
  158. double percent = numComplete;
  159. percent /= list.Count;
  160. percent *= 100;
  161. progress.Report(percent);
  162. }
  163. }
  164. /// <summary>
  165. /// Updates the series.
  166. /// </summary>
  167. /// <param name="id">The id.</param>
  168. /// <param name="seriesDataPath">The series data path.</param>
  169. /// <param name="cancellationToken">The cancellation token.</param>
  170. /// <returns>Task.</returns>
  171. private Task UpdateSeries(string id, string seriesDataPath, CancellationToken cancellationToken)
  172. {
  173. _logger.Info("Updating series " + id);
  174. seriesDataPath = Path.Combine(seriesDataPath, id);
  175. if (!Directory.Exists(seriesDataPath))
  176. {
  177. Directory.CreateDirectory(seriesDataPath);
  178. }
  179. return RemoteSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, cancellationToken);
  180. }
  181. }
  182. }