PageRenderTime 24ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/Espera.Core/MusicBrainzArtworkFetcher.cs

http://github.com/flagbug/Espera
C# | 163 lines | 120 code | 33 blank | 10 comment | 47 complexity | 5a38e4235964d6c56906acffc08121a1 MD5 | raw file
Possible License(s): BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Net.Http;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using System.Xml.Linq;
  9. using Newtonsoft.Json.Linq;
  10. using ReactiveMarrow;
  11. using ReactiveUI;
  12. using Splat;
  13. namespace Espera.Core
  14. {
  15. public class MusicBrainzArtworkFetcher : IArtworkFetcher, IEnableLogger
  16. {
  17. private const string ArtworkEndpoint = "http://coverartarchive.org/release/{0}/";
  18. private const string SearchEndpoint = "http://www.musicbrainz.org/ws/2/release/?query=artist:{0}+release:{1}";
  19. private readonly RateLimitedOperationQueue queue;
  20. public MusicBrainzArtworkFetcher()
  21. {
  22. // The MusicBraint search service allows us to perform onme request per second on
  23. // average, make sure we don't exceed that.
  24. this.queue = new RateLimitedOperationQueue(TimeSpan.FromSeconds(1.5), RxApp.TaskpoolScheduler);
  25. }
  26. public async Task<Uri> RetrieveAsync(string artist, string album)
  27. {
  28. // Replace special character, as MusicBrainz uses Lucene in the backend
  29. artist = Escape(artist);
  30. album = Escape(album);
  31. // Only searches are rate-limited, artwork retrievals are fine
  32. IReadOnlyList<string> releaseIds = await this.queue.EnqueueOperation(() => GetReleaseIdsAsync(artist, album));
  33. if (releaseIds == null)
  34. {
  35. return null;
  36. }
  37. return await GetArtworkLinkAsync(releaseIds);
  38. }
  39. /// <summary>
  40. /// Escapes a lucene query
  41. /// </summary>
  42. private static String Escape(String s)
  43. {
  44. var sb = new StringBuilder();
  45. foreach (char c in s)
  46. {
  47. // These characters are part of the query syntax and must be escaped
  48. if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':'
  49. || c == '^' || c == '[' || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~'
  50. || c == '*' || c == '?' || c == '/')
  51. {
  52. sb.Append(@"\");
  53. }
  54. if (c == '|' || c == '&')
  55. {
  56. sb.Append(@"\\");
  57. }
  58. sb.Append(c);
  59. }
  60. return sb.ToString();
  61. }
  62. private static async Task<IReadOnlyList<string>> GetReleaseIdsAsync(string artist, string album)
  63. {
  64. string searchRequestUrl = string.Format(SearchEndpoint, artist, album);
  65. string searchResponse;
  66. using (var client = new HttpClient())
  67. {
  68. client.DefaultRequestHeaders.Add("user-agent", "Espera/2.0 (http://getespera.com)");
  69. try
  70. {
  71. searchResponse = await client.GetStringAsync(searchRequestUrl);
  72. }
  73. catch (HttpRequestException ex)
  74. {
  75. throw new ArtworkFetchException(string.Format("Error while requesting the release id for artist {0} and album {1}", artist, album), ex);
  76. }
  77. }
  78. XNamespace ns = "http://musicbrainz.org/ns/mmd-2.0#";
  79. var releases = XDocument.Parse(searchResponse).Descendants(ns + "release");
  80. XNamespace scoreNs = "http://musicbrainz.org/ns/ext#-2.0";
  81. List<string> releaseIds = releases.Where(x => (int?)x.Attribute(scoreNs + "score") >= 95)
  82. .Select(x => x.Attribute("id").Value)
  83. .ToList();
  84. return releaseIds;
  85. }
  86. private async Task<Uri> GetArtworkLinkAsync(IReadOnlyList<string> releaseIds)
  87. {
  88. using (var client = new HttpClient())
  89. {
  90. foreach (string releaseId in releaseIds)
  91. {
  92. string artworkRequestUrl = string.Format(ArtworkEndpoint, releaseId);
  93. HttpResponseMessage response;
  94. try
  95. {
  96. response = await client.GetAsync(artworkRequestUrl);
  97. // The only valid failure status is "Not Found"
  98. if (response.StatusCode == HttpStatusCode.NotFound)
  99. {
  100. continue;
  101. }
  102. response.EnsureSuccessStatusCode();
  103. }
  104. catch (HttpRequestException ex)
  105. {
  106. string errorInfo = string.Format("Could not download artwork informations for release id {0}", releaseId);
  107. // If we can't even get the last artwork, throw
  108. if (releaseId == releaseIds.Last())
  109. {
  110. throw new ArtworkFetchException(errorInfo, ex);
  111. }
  112. if (releaseIds.Count > 1)
  113. {
  114. this.Log().Error(errorInfo + ", retrying with next in list.");
  115. }
  116. continue;
  117. }
  118. string responseContent = await response.Content.ReadAsStringAsync();
  119. JToken artworkUrlToken = JObject.Parse(responseContent).SelectToken("images[0].image");
  120. if (artworkUrlToken == null)
  121. {
  122. continue;
  123. }
  124. return new Uri(artworkUrlToken.ToObject<string>());
  125. }
  126. }
  127. return null;
  128. }
  129. }
  130. }