using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Logging; 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; using CommonIO; using MediaBrowser.Providers.TV; namespace MediaBrowser.Providers.Music { class FanartUpdatesPostScanTask : ILibraryPostScanTask { 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 readonly IFileSystem _fileSystem; private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); public FanartUpdatesPostScanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem) { _jsonSerializer = jsonSerializer; _config = config; _logger = logger; _httpClient = httpClient; _fileSystem = fileSystem; } /// /// Runs the specified progress. /// /// The progress. /// The cancellation token. /// Task. public async Task Run(IProgress progress, CancellationToken cancellationToken) { var options = FanartSeriesProvider.Current.GetFanartOptions(); if (!options.EnableAutomaticUpdates) { progress.Report(100); return; } var path = FanartArtistProvider.GetArtistDataPath(_config.CommonApplicationPaths); _fileSystem.CreateDirectory(path); var timestampFile = Path.Combine(path, "time.txt"); var timestampFileInfo = _fileSystem.GetFileInfo(timestampFile); // Don't check for updates every single time if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 3) { return; } // Find out the last time we queried for updates var lastUpdateTime = timestampFileInfo.Exists ? _fileSystem.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, options, cancellationToken).ConfigureAwait(false); progress.Report(5); await UpdateArtists(artistsToUpdate, progress, cancellationToken).ConfigureAwait(false); } var newUpdateTime = Convert.ToInt64(DateTimeToUnixTimestamp(DateTime.UtcNow)).ToString(UsCulture); _fileSystem.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, FanartOptions options, CancellationToken cancellationToken) { var url = string.Format(UpdatesUrl, FanartArtistProvider.ApiKey, lastUpdateTime); if (!string.IsNullOrWhiteSpace(options.UserApiKey)) { url += "&client_key=" + options.UserApiKey; } // First get last time using (var stream = await _httpClient.Get(new HttpRequestOptions { Url = url, CancellationToken = cancellationToken, EnableHttpCompression = true, ResourcePool = FanartArtistProvider.Current.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 existingDictionary = existingArtistIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); var updates = _jsonSerializer.DeserializeFromString>(json); return updates.Select(i => i.id).Where(existingDictionary.ContainsKey); } } } /// /// Updates the artists. /// /// The id list. /// The progress. /// The cancellation token. /// Task. private async Task UpdateArtists(IEnumerable idList, IProgress progress, CancellationToken cancellationToken) { var list = idList.ToList(); var numComplete = 0; foreach (var id in list) { await UpdateArtist(id, cancellationToken).ConfigureAwait(false); numComplete++; double percent = numComplete; percent /= list.Count; percent *= 95; progress.Report(percent + 5); } } /// /// Updates the artist. /// /// The musicBrainzId. /// The cancellation token. /// Task. private Task UpdateArtist(string musicBrainzId, CancellationToken cancellationToken) { _logger.Info("Updating artist " + musicBrainzId); return FanartArtistProvider.Current.DownloadArtistXml(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; } } } }