using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers.Music; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Providers.Music { class FanArtUpdatesPrescanTask : ILibraryPrescanTask { private const string UpdatesUrl = "http://api.fanart.tv/webservice/newmusic/{0}/{1}/"; /// /// The _HTTP client /// private readonly IHttpClient _httpClient; /// /// The _logger /// private readonly ILogger _logger; /// /// The _config /// private readonly IServerConfigurationManager _config; private readonly IJsonSerializer _jsonSerializer; private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); public FanArtUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient) { _jsonSerializer = jsonSerializer; _config = config; _logger = logger; _httpClient = httpClient; } /// /// 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 = FanArtArtistProvider.GetArtistDataPath(_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 for updates var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty; var existingDirectories = Directory.EnumerateDirectories(path).Select(Path.GetFileName).ToList(); // If this is our first time, don't do any updates and just record the timestamp if (!string.IsNullOrEmpty(lastUpdateTime)) { var artistsToUpdate = await GetArtistIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false); progress.Report(5); await UpdateArtists(artistsToUpdate, path, progress, cancellationToken).ConfigureAwait(false); } var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture); File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8); progress.Report(100); } /// /// Gets the artist ids to update. /// /// The existing series ids. /// The last update time. /// The cancellation token. /// Task{IEnumerable{System.String}}. private async Task> GetArtistIdsToUpdate(IEnumerable existingArtistIds, string lastUpdateTime, CancellationToken cancellationToken) { // First get last time using (var stream = await _httpClient.Get(new HttpRequestOptions { Url = string.Format(UpdatesUrl, FanartBaseProvider.ApiKey, lastUpdateTime), CancellationToken = cancellationToken, EnableHttpCompression = true, ResourcePool = FanartBaseProvider.FanArtResourcePool }).ConfigureAwait(false)) { // If empty fanart will return a string of "null", rather than an empty list using (var reader = new StreamReader(stream)) { var json = await reader.ReadToEndAsync().ConfigureAwait(false); if (string.Equals(json, "null", StringComparison.OrdinalIgnoreCase)) { return new List(); } var updates = _jsonSerializer.DeserializeFromString>(json); return updates.Select(i => i.id).Where(i => existingArtistIds.Contains(i, StringComparer.OrdinalIgnoreCase)); } } } /// /// Updates the artists. /// /// The id list. /// The artists data path. /// The progress. /// The cancellation token. /// Task. private async Task UpdateArtists(IEnumerable idList, string artistsDataPath, IProgress progress, CancellationToken cancellationToken) { var list = idList.ToList(); var numComplete = 0; foreach (var id in list) { try { await UpdateArtist(id, artistsDataPath, cancellationToken).ConfigureAwait(false); } catch (HttpException ex) { // Already logged at lower levels, but don't fail the whole operation, unless something other than a timeout if (!ex.IsTimedOut) { throw; } } numComplete++; double percent = numComplete; percent /= list.Count; percent *= 95; progress.Report(percent + 5); } } /// /// Updates the artist. /// /// The musicBrainzId. /// The artists data path. /// The cancellation token. /// Task. private Task UpdateArtist(string musicBrainzId, string artistsDataPath, CancellationToken cancellationToken) { _logger.Info("Updating artist " + musicBrainzId); artistsDataPath = Path.Combine(artistsDataPath, musicBrainzId); if (!Directory.Exists(artistsDataPath)) { Directory.CreateDirectory(artistsDataPath); } return FanArtArtistProvider.Current.DownloadArtistXml(artistsDataPath, musicBrainzId, cancellationToken); } /// /// Dates the time to unix timestamp. /// /// The date time. /// System.Double. private static double DateTimeToUnixTimestamp(DateTime dateTime) { return (dateTime - new DateTime(1970, 1, 1).ToUniversalTime()).TotalSeconds; } public class FanArtUpdate { public string id { get; set; } public string name { get; set; } public string new_images { get; set; } public string total_images { get; set; } } } }