using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using MediaBrowser.Providers.Extensions; namespace MediaBrowser.Providers.TV { /// /// Class TvdbPrescanTask /// public class TvdbPrescanTask : ILibraryPrescanTask { /// /// The server time URL /// private const string ServerTimeUrl = "http://thetvdb.com/api/Updates.php?type=none"; /// /// The updates URL /// private const string UpdatesUrl = "http://thetvdb.com/api/Updates.php?type=all&time={0}"; /// /// The _HTTP client /// private readonly IHttpClient _httpClient; /// /// The _logger /// private readonly ILogger _logger; /// /// The _config /// private readonly IServerConfigurationManager _config; /// /// Initializes a new instance of the class. /// /// The logger. /// The HTTP client. /// The config. public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config) { _logger = logger; _httpClient = httpClient; _config = config; } /// /// Runs the specified progress. /// /// The progress. /// The cancellation token. /// Task. public async Task Run(IProgress progress, CancellationToken cancellationToken) { if (!_config.Configuration.EnableInternetProviders) { progress.Report(100); return; } var path = RemoteSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths); var timestampFile = Path.Combine(path, "time.txt"); var timestampFileInfo = new FileInfo(timestampFile); // Don't check for tvdb updates anymore frequently than 24 hours if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1) { return; } // Find out the last time we queried tvdb for updates var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty; string newUpdateTime; var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList(); // If this is our first time, update all series if (string.IsNullOrEmpty(lastUpdateTime)) { // First get tvdb server time using (var stream = await _httpClient.Get(new HttpRequestOptions { Url = ServerTimeUrl, CancellationToken = cancellationToken, EnableHttpCompression = true, ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool }).ConfigureAwait(false)) { var doc = new XmlDocument(); doc.Load(stream); newUpdateTime = doc.SafeGetString("//Time"); } await UpdateSeries(existingDirectories, path, progress, cancellationToken).ConfigureAwait(false); } else { var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false); newUpdateTime = seriesToUpdate.Item2; await UpdateSeries(seriesToUpdate.Item1, path, progress, cancellationToken).ConfigureAwait(false); } File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8); progress.Report(100); } /// /// Gets the series ids to update. /// /// The existing series ids. /// The last update time. /// The cancellation token. /// Task{IEnumerable{System.String}}. private async Task, string>> GetSeriesIdsToUpdate(IEnumerable existingSeriesIds, string lastUpdateTime, CancellationToken cancellationToken) { // First get last time using (var stream = await _httpClient.Get(new HttpRequestOptions { Url = string.Format(UpdatesUrl, lastUpdateTime), CancellationToken = cancellationToken, EnableHttpCompression = true, ResourcePool = RemoteSeriesProvider.Current.TvDbResourcePool }).ConfigureAwait(false)) { var doc = new XmlDocument(); doc.Load(stream); var newUpdateTime = doc.SafeGetString("//Time"); var seriesNodes = doc.SelectNodes("//Series"); var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); var seriesList = seriesNodes == null ? new string[] { } : seriesNodes.Cast() .Select(i => i.InnerText) .Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i)); return new Tuple, string>(seriesList, newUpdateTime); } } /// /// Updates the series. /// /// The series ids. /// The series data path. /// The progress. /// The cancellation token. /// Task. private async Task UpdateSeries(IEnumerable seriesIds, string seriesDataPath, IProgress progress, CancellationToken cancellationToken) { var list = seriesIds.ToList(); var numComplete = 0; foreach (var seriesId in list) { try { await UpdateSeries(seriesId, seriesDataPath, cancellationToken).ConfigureAwait(false); } catch (HttpException ex) { // Already logged at lower levels, but don't fail the whole operation, unless timed out // We have to fail this to make it run again otherwise new episode data could potentially be missing if (ex.IsTimedOut) { throw; } } numComplete++; double percent = numComplete; percent /= list.Count; percent *= 100; progress.Report(percent); } } /// /// Updates the series. /// /// The id. /// The series data path. /// The cancellation token. /// Task. private Task UpdateSeries(string id, string seriesDataPath, CancellationToken cancellationToken) { _logger.Info("Updating series " + id); seriesDataPath = Path.Combine(seriesDataPath, id); if (!Directory.Exists(seriesDataPath)) { Directory.CreateDirectory(seriesDataPath); } return RemoteSeriesProvider.Current.DownloadSeriesZip(id, seriesDataPath, cancellationToken); } } }