TvdbPrescanTask.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller.Configuration;
  3. using MediaBrowser.Controller.Library;
  4. using MediaBrowser.Model.Logging;
  5. using MediaBrowser.Model.Net;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. using System.Xml;
  14. using MediaBrowser.Providers.Extensions;
  15. namespace MediaBrowser.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 IServerConfigurationManager _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, IServerConfigurationManager 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. if (!_config.Configuration.EnableInternetProviders)
  63. {
  64. progress.Report(100);
  65. return;
  66. }
  67. var path = RemoteSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths);
  68. var timestampFile = Path.Combine(path, "time.txt");
  69. var timestampFileInfo = new FileInfo(timestampFile);
  70. // Don't check for tvdb updates anymore frequently than 24 hours
  71. if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1)
  72. {
  73. return;
  74. }
  75. // Find out the last time we queried tvdb for updates
  76. var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty;
  77. string newUpdateTime;
  78. var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList();
  79. // If this is our first time, update all series
  80. if (string.IsNullOrEmpty(lastUpdateTime))
  81. {
  82. // First get tvdb server time
  83. using (var stream = await _httpClient.Get(new HttpRequestOptions
  84. {
  85. Url = ServerTimeUrl,
  86. CancellationToken = cancellationToken,
  87. EnableHttpCompression = true,
  88. ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool
  89. }).ConfigureAwait(false))
  90. {
  91. var doc = new XmlDocument();
  92. doc.Load(stream);
  93. newUpdateTime = doc.SafeGetString("//Time");
  94. }
  95. await UpdateSeries(existingDirectories, path, progress, cancellationToken).ConfigureAwait(false);
  96. }
  97. else
  98. {
  99. var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false);
  100. newUpdateTime = seriesToUpdate.Item2;
  101. await UpdateSeries(seriesToUpdate.Item1, path, progress, cancellationToken).ConfigureAwait(false);
  102. }
  103. File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8);
  104. progress.Report(100);
  105. }
  106. /// <summary>
  107. /// Gets the series ids to update.
  108. /// </summary>
  109. /// <param name="existingSeriesIds">The existing series ids.</param>
  110. /// <param name="lastUpdateTime">The last update time.</param>
  111. /// <param name="cancellationToken">The cancellation token.</param>
  112. /// <returns>Task{IEnumerable{System.String}}.</returns>
  113. private async Task<Tuple<IEnumerable<string>, string>> GetSeriesIdsToUpdate(IEnumerable<string> existingSeriesIds, string lastUpdateTime, CancellationToken cancellationToken)
  114. {
  115. // First get last time
  116. using (var stream = await _httpClient.Get(new HttpRequestOptions
  117. {
  118. Url = string.Format(UpdatesUrl, lastUpdateTime),
  119. CancellationToken = cancellationToken,
  120. EnableHttpCompression = true,
  121. ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool
  122. }).ConfigureAwait(false))
  123. {
  124. var doc = new XmlDocument();
  125. doc.Load(stream);
  126. var newUpdateTime = doc.SafeGetString("//Time");
  127. var seriesNodes = doc.SelectNodes("//Series");
  128. var seriesList = seriesNodes == null ? new string[] { } :
  129. seriesNodes.Cast<XmlNode>()
  130. .Select(i => i.InnerText)
  131. .Where(i => !string.IsNullOrWhiteSpace(i) && existingSeriesIds.Contains(i, StringComparer.OrdinalIgnoreCase));
  132. return new Tuple<IEnumerable<string>, string>(seriesList, newUpdateTime);
  133. }
  134. }
  135. /// <summary>
  136. /// Updates the series.
  137. /// </summary>
  138. /// <param name="seriesIds">The series ids.</param>
  139. /// <param name="seriesDataPath">The series data path.</param>
  140. /// <param name="progress">The progress.</param>
  141. /// <param name="cancellationToken">The cancellation token.</param>
  142. /// <returns>Task.</returns>
  143. private async Task UpdateSeries(IEnumerable<string> seriesIds, string seriesDataPath, IProgress<double> progress, CancellationToken cancellationToken)
  144. {
  145. var list = seriesIds.ToList();
  146. var numComplete = 0;
  147. foreach (var seriesId in list)
  148. {
  149. try
  150. {
  151. await UpdateSeries(seriesId, seriesDataPath, cancellationToken).ConfigureAwait(false);
  152. }
  153. catch (HttpException ex)
  154. {
  155. // Already logged at lower levels, but don't fail the whole operation, unless timed out
  156. // We have to fail this to make it run again otherwise new episode data could potentially be missing
  157. if (ex.IsTimedOut)
  158. {
  159. throw;
  160. }
  161. }
  162. numComplete++;
  163. double percent = numComplete;
  164. percent /= list.Count;
  165. percent *= 100;
  166. progress.Report(percent);
  167. }
  168. }
  169. /// <summary>
  170. /// Updates the series.
  171. /// </summary>
  172. /// <param name="id">The id.</param>
  173. /// <param name="seriesDataPath">The series data path.</param>
  174. /// <param name="cancellationToken">The cancellation token.</param>
  175. /// <returns>Task.</returns>
  176. private Task UpdateSeries(string id, string seriesDataPath, CancellationToken cancellationToken)
  177. {
  178. _logger.Info("Updating series " + id);
  179. seriesDataPath = Path.Combine(seriesDataPath, id);
  180. if (!Directory.Exists(seriesDataPath))
  181. {
  182. Directory.CreateDirectory(seriesDataPath);
  183. }
  184. return RemoteSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, cancellationToken);
  185. }
  186. }
  187. }