PageRenderTime 42ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/src/MarkPad/DocumentSources/MetaWeblog/Service/Rsd/RsdService.cs

https://github.com/bcott/DownmarkerWPF
C# | 189 lines | 156 code | 26 blank | 7 comment | 16 complexity | 3eefbe44c353e62f9eb8e4b1ab0a4622 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Text.RegularExpressions;
  8. using System.Threading.Tasks;
  9. using System.Xml.Linq;
  10. using MarkPad.Infrastructure.Abstractions;
  11. namespace MarkPad.DocumentSources.MetaWeblog.Service.Rsd
  12. {
  13. public class RsdService : IRsdService
  14. {
  15. const string RsdNamespace = "http://archipelago.phrasewise.com/rsd";
  16. readonly IWebRequestFactory webRequestFactory;
  17. public RsdService(IWebRequestFactory webRequestFactory)
  18. {
  19. this.webRequestFactory = webRequestFactory;
  20. }
  21. public Task<DiscoveryResult> DiscoverAddress(string webAPI)
  22. {
  23. var completionSource = new TaskCompletionSource<DiscoveryResult>();
  24. var baseUri = new Uri(webAPI, UriKind.Absolute);
  25. var requestUri = new Uri(baseUri, "rsd.xml");
  26. var rsdFileRequest = webRequestFactory.Create(requestUri);
  27. // Kick off the async discovery workflow
  28. rsdFileRequest.GetResponseAsync()
  29. .ContinueWith<DiscoveryResult>(ProcessRsdResponse)
  30. .ContinueWith(c =>
  31. {
  32. if (c.Result.Success)
  33. completionSource.SetResult(c.Result);
  34. else
  35. {
  36. Trace.WriteLine(string.Format(
  37. "Rsd.xml does not exist, trying to discover via link. Error was {0}", c.Result.FailMessage), "INFO");
  38. DiscoverRsdLink(webAPI)
  39. .ContinueWith(t => completionSource.SetResult(t.Result));
  40. }
  41. });
  42. return completionSource.Task;
  43. }
  44. Task<DiscoveryResult> DiscoverRsdLink(string webAPI)
  45. {
  46. var taskCompletionSource = new TaskCompletionSource<DiscoveryResult>();
  47. // Build a request to retrieve the contents of the specified URL directly
  48. var requestUri = new Uri(webAPI, UriKind.Absolute);
  49. var directWebAPIRequest = webRequestFactory.Create(requestUri);
  50. // Add a continuation that will only execute if the request succeeds and proceses the response to look for a <link> to the RSD
  51. directWebAPIRequest.GetResponseAsync()
  52. .ContinueWith(webAPIRequestAntecedent =>
  53. {
  54. if (webAPIRequestAntecedent.IsFaulted)
  55. {
  56. taskCompletionSource.SetResult(new DiscoveryResult(webAPIRequestAntecedent.Exception));
  57. return;
  58. }
  59. using (var webAPIResponse = webAPIRequestAntecedent.Result)
  60. using (var streamReader = new StreamReader(GetResponseStream(webAPIResponse)))
  61. {
  62. DiscoverRsdOnPage(webAPI, streamReader, taskCompletionSource);
  63. }
  64. });
  65. return taskCompletionSource.Task;
  66. }
  67. void DiscoverRsdOnPage(string webAPI, TextReader streamReader, TaskCompletionSource<DiscoveryResult> taskCompletionSource)
  68. {
  69. const string linkTagRegex = "(?<link>\\<link .*?type=\"application/rsd\\+xml\".*?/\\>)";
  70. var response = streamReader.ReadToEnd();
  71. var link = Regex.Match(response, linkTagRegex, RegexOptions.IgnoreCase);
  72. var rsdLinkMatch = link.Groups["link"];
  73. if (!rsdLinkMatch.Success)
  74. {
  75. taskCompletionSource.SetResult(DiscoveryResult.Failed("Unable to resolve link to rsd file from url"));
  76. return;
  77. }
  78. var rsdLocationMatch = Regex.Match(rsdLinkMatch.Value, "href=(?:\"|')(?<link>.*?)(?:\"|')");
  79. if (!rsdLocationMatch.Groups["link"].Success)
  80. {
  81. taskCompletionSource.SetResult(DiscoveryResult.Failed("Unable to parse rsd link tag"));
  82. return;
  83. }
  84. var rsdUri = new Uri(rsdLocationMatch.Groups["link"].Value, UriKind.RelativeOrAbsolute);
  85. if (!rsdUri.IsAbsoluteUri)
  86. rsdUri = new Uri(new Uri(webAPI, UriKind.Absolute), rsdUri);
  87. var rdsWebRequest = webRequestFactory.Create(rsdUri);
  88. var rdsWebRequestTask = rdsWebRequest.GetResponseAsync();
  89. // Add a continuation that will only execute if the request succeeds and continues processing the RSD
  90. rdsWebRequestTask.ContinueWith(rdsWebRequestAntecedent =>
  91. taskCompletionSource.SetResult(ProcessRsdResponse(rdsWebRequestAntecedent)),
  92. TaskContinuationOptions.NotOnFaulted);
  93. // Add a continuation that will only execute if the request faults and propagates the exception via the TCS
  94. rdsWebRequestTask.ContinueWith(rdsWebRequestAntecdent =>
  95. taskCompletionSource.SetResult(new DiscoveryResult(rdsWebRequestAntecdent.Exception)),
  96. TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted);
  97. }
  98. private static Stream GetResponseStream(WebResponse webAPIResponse)
  99. {
  100. return webAPIResponse.GetResponseStream();
  101. }
  102. static DiscoveryResult ProcessRsdResponse(Task<WebResponse> webResponseTask)
  103. {
  104. if (webResponseTask.IsFaulted)
  105. return new DiscoveryResult(webResponseTask.Exception);
  106. try
  107. {
  108. using (var webResponse = webResponseTask.Result)
  109. {
  110. using (var responseStream = webResponse.GetResponseStream())
  111. {
  112. var document = XDocument.Load(responseStream);
  113. var apiElement = GetMetaWebLogElement(document);
  114. if (apiElement == null)
  115. return DiscoveryResult.Failed("Unable to get metaweblog api address from rds.xml");
  116. var xAttribute = apiElement.Attribute("apiLink");
  117. if (xAttribute == null)
  118. return DiscoveryResult.Failed("apiLink attribute not present for metaweblog api reference");
  119. var webApiLink = xAttribute.Value;
  120. return new DiscoveryResult(webApiLink);
  121. }
  122. }
  123. }
  124. catch (Exception ex)
  125. {
  126. return new DiscoveryResult(ex);
  127. }
  128. }
  129. private static XElement GetMetaWebLogElement(XDocument document)
  130. {
  131. // ReSharper disable PossibleNullReferenceException
  132. try
  133. {
  134. IEnumerable<XElement> apiElements;
  135. if (document.Root.Attributes().Any(x => x.IsNamespaceDeclaration && x.Value == RsdNamespace))
  136. {
  137. var xElement = document.Element(XName.Get("rsd", RsdNamespace));
  138. var element = xElement.Element(XName.Get("service", RsdNamespace));
  139. var xElement1 = element.Element(XName.Get("apis", RsdNamespace));
  140. apiElements = xElement1.Elements(XName.Get("api", RsdNamespace));
  141. }
  142. else
  143. {
  144. apiElements = document
  145. .Element(XName.Get("rsd"))
  146. .Element(XName.Get("service"))
  147. .Element(XName.Get("apis"))
  148. .Elements(XName.Get("api"));
  149. }
  150. return apiElements.SingleOrDefault(e =>
  151. {
  152. var apiName = e.Attribute("name").Value.ToLower();
  153. return apiName == "metaweblog" || apiName == "blogger";
  154. });
  155. }
  156. catch (NullReferenceException)
  157. {
  158. return null;
  159. }
  160. // ReSharper restore PossibleNullReferenceException
  161. }
  162. }
  163. }