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