// Creates the URI for comments for the specified repo, with a specific page size and offset.
 private static Uri CreateCommentsPageUri(ListParameters p, int pageOffset, int pageSize)
 {
     // create URI for GitHub.com or GitHub Enterprise
     string baseUri = p.Server == "api.github.com" ? @"https://{0}/repos/" : @"http://{0}/api/v3/repos/";
     string uriTemplate = baseUri + @"{1}/{2}/comments?page={3}&per_page={4}";
     return new Uri(string.Format(CultureInfo.InvariantCulture, uriTemplate, Uri.EscapeDataString(p.Server),
         Uri.EscapeDataString(p.User), Uri.EscapeDataString(p.Repo), pageOffset, pageSize));
 }
        public void ListAsync(ListParameters p)
        {
            // save the client's ETag, which will be used to determine if a "Not Modified" result should be returned
            m_requestETag = Request.Headers["If-None-Match"];

            // start the asynchronous processing pipeline
            Start(p, GetFirstComment)
                .Then(GetCommentCount)
                .Then(GetCommentPages)
                .Then(GetComments)
                .Then(GetCommits)
                .Then(CreateFeed)
                .Finish();
        }
        // Returns an array of tasks that will download the last 50 comments for a particular repo.
        private static Task<HttpWebResponse>[] GetCommentPages(ListParameters p, int commentCount)
        {
            // determine the offset of the last page of comments
            const int c_pageSize = 100;
            int lastPageOffset = (commentCount - 1) / c_pageSize + 1;

            // request one or two pages (as necessary) to get at least 50 comments
            List<Uri> uris = new List<Uri>();
            if (commentCount > 50 && (commentCount % c_pageSize < 50))
            {
                // there are at least 50 items, but the last page doesn't contain at least 50
                uris.Add(CreateCommentsPageUri(p, lastPageOffset - 1, c_pageSize));
            }

            // get the last page of comments
            uris.Add(CreateCommentsPageUri(p, lastPageOffset, c_pageSize));

            // return a task for each URI
            return uris.Select(u => CreateRequest(p, u).GetHttpResponseAsync()).ToArray();
        }
 // Starts a WebRequest to retrieve the first comment for a repo.
 private static Task<HttpWebResponse> GetFirstComment(ListParameters p)
 {
     Uri uri = CreateCommentsPageUri(p, 1, 1);
     HttpWebRequest request = CreateRequest(p, uri);
     return request.GetHttpResponseAsync();
 }
 // Creates a HTTP request to access the specified URI.
 private static HttpWebRequest CreateRequest(ListParameters p, Uri uri)
 {
     HttpWebRequest request = GitHubApi.CreateRequest(uri, p.UserName, p.Password);
     request.Accept = "application/vnd.github-commitcomment.html+json";
     return request;
 }
        private Task<List<GitHubComment>> GetCommits(ListParameters p, List<GitHubComment> comments)
        {
            List<Task> tasks = new List<Task>();

            if (p.Version == 2)
            {
                object lockObject = new object();
                m_commits = new Dictionary<string, GitHubCommit>();

                foreach (var commitId in comments.Select(c => c.commit_id).Distinct())
                {
                    // look up commit in cache
                    var commit = (GitHubCommit) HttpContext.Cache.Get("commit:" + commitId);

                    if (commit != null)
                    {
                        // if found, store it locally (in case it gets evicted from cache)
                        lock (lockObject)
                            m_commits.Add(commitId, commit);
                    }
                    else
                    {
                        // if not found, request it
                        string baseUri = p.Server == "api.github.com" ? @"https://{0}/repos/" : @"http://{0}/api/v3/repos/";
                        string uriTemplate = baseUri + @"{1}/{2}/commits/{3}";
                        Uri uri = new Uri(string.Format(CultureInfo.InvariantCulture, uriTemplate, Uri.EscapeDataString(p.Server),
                            Uri.EscapeDataString(p.User), Uri.EscapeDataString(p.Repo), Uri.EscapeDataString(commitId)));

                        var request = GitHubApi.CreateRequest(uri, p.UserName, p.Password);
                        tasks.Add(request.GetHttpResponseAsync().ContinueWith(t =>
                        {
                            // parse the commit JSON
                            GitHubCommit downloadedCommit;
                            using (HttpWebResponse response = t.Result)
                            using (Stream stream = response.GetResponseStream())
                            using (TextReader reader = new StreamReader(stream, Encoding.UTF8))
                                downloadedCommit = JsonSerializer.DeserializeFromReader<GitHubCommit>(reader);

                            // store in cache
                            string downloadedCommitId = downloadedCommit.sha;
                            HttpContext.Cache.Insert("commit:" + downloadedCommitId, downloadedCommit);

                            // also store it locally (in case it gets evicted from cache)
                            lock (lockObject)
                                m_commits.Add(downloadedCommitId, downloadedCommit);
                        }));
                    }
                }
            }

            return TaskUtility.ContinueWhenAll(tasks.ToArray(), t => comments);
        }
        // Merges the comments returned from multiple HTTP requests and returns the last 50.
        private List<GitHubComment> GetComments(ListParameters p, Task<HttpWebResponse>[] tasks)
        {
            // concatenate all the response URIs and ETags; we will use this to build our own ETag
            var responseETags = new List<string>();

            // download comments as JSON and deserialize them
            List<GitHubComment> comments = new List<GitHubComment>();
            foreach (Task<HttpWebResponse> task in tasks)
            {
                using (HttpWebResponse response = task.Result)
                {
                    if (response.StatusCode != HttpStatusCode.OK)
                        throw new ApplicationException("GitHub server returned " + response.StatusCode);

                    // if the response has an ETag, add it to the list of all ETags
                    string eTag = response.Headers[HttpResponseHeader.ETag];
                    if (!string.IsNullOrEmpty(eTag))
                        responseETags.Add(response.ResponseUri.AbsoluteUri + ":" + eTag);

                    // TODO: Use asynchronous reads on this asynchronous stream
                    // TODO: Read encoding from Content-Type header; don't assume UTF-8
                    using (Stream stream = response.GetResponseStream())
                    using (TextReader reader = new StreamReader(stream, Encoding.UTF8))
                        comments.AddRange(JsonSerializer.DeserializeFromReader<List<GitHubComment>>(reader));
                }
            }

            // if each response had an ETag, build our own ETag from that data
            if (responseETags.Count == tasks.Length)
            {
                // concatenate all the ETag data
                string eTagData = p.Version + "\n" + responseETags.Join("\n");

                // hash it
                byte[] md5;
                using (MD5 hash = MD5.Create())
                    md5 = hash.ComputeHash(Encoding.UTF8.GetBytes(eTagData));

                // the ETag is the quoted MD5 hash
                string responseETag = "\"" + string.Join("", md5.Select(by => by.ToString("x2", CultureInfo.InvariantCulture))) + "\"";
                Response.AppendHeader("ETag", responseETag);

                if (m_requestETag == responseETag)
                {
                    SetResult(new HttpStatusCodeResult((int) HttpStatusCode.NotModified));
                    return null;
                }
            }

            return comments
                .OrderByDescending(c => c.created_at)
                .Take(50)
                .ToList();
        }
        // Gets the number of comments for a repo.
        private int GetCommentCount(ListParameters p, Task<HttpWebResponse> responseTask)
        {
            string linkHeader;
            using (HttpWebResponse response = responseTask.Result)
            {
                if (response.StatusCode == HttpStatusCode.NotFound)
                {
                    SetResult(HttpNotFound());
                    return 0;
                }
                else if (response.StatusCode != HttpStatusCode.OK)
                {
                    throw new ApplicationException("GitHub server returned " + response.StatusCode);
                }

                linkHeader = response.Headers["Link"];
            }

            int commentCount = 0;

            if (!string.IsNullOrWhiteSpace(linkHeader))
            {
                foreach (HeaderElement element in HeaderValueParser.ParseHeaderElements(linkHeader))
                {
                    string rel;
                    if (element.TryGetParameterByName("rel", out rel) && rel == "last")
                    {
                        // HACK: parse the page number out of the URL that links to the last page; this will be the total number of comments
                        Match match = Regex.Match(element.Name, @"[?&]page=(\d+)");
                        Debug.Assert(match.Success, "match.Success", "Page number could not be parsed from URL.");
                        commentCount = int.Parse(match.Groups[1].Value);
                    }
                }
            }

            return commentCount;
        }
        // Creates an ATOM feed from a list of comments.
        private bool CreateFeed(ListParameters p, List<GitHubComment> comments)
        {
            // build a feed from the comments (in reverse chronological order)
            SyndicationFeed feed = new SyndicationFeed(comments.Select(c => CreateCommentItem(p, c)))
            {
                Id = "urn:x-feed:" + Uri.EscapeDataString(p.Server) + "/" + Uri.EscapeDataString(p.User) + "/" + Uri.EscapeDataString(p.Repo),
                LastUpdatedTime = comments.Count == 0 ? DateTimeOffset.Now : comments.Max(c => c.updated_at),
                Title = new TextSyndicationContent(string.Format("Comments for {0}/{1}", p.User, p.Repo)),
            };

            SetResult(new SyndicationFeedAtomResult(feed));
            return true;
        }
Esempio n. 10
0
        private SyndicationItem CreateCommentItem(ListParameters p, GitHubComment comment)
        {
            GitHubCommentModel model = CreateCommentModel(p.Version, comment);
            string title = p.Version == 1 ? "{0} commented on {1}/{2}".FormatWith(model.Commenter, p.User, p.Repo) :
                "{0}’s commit: {1}".FormatWith(model.Author, RenderCommitForSubject(model));

            return new SyndicationItem
            {
                Authors = { new SyndicationPerson(null, comment.user.login, null) },
                Content = new TextSyndicationContent(CreateCommentHtml(p.Version, model), TextSyndicationContentKind.Html),
                Id = comment.url.AbsoluteUri,
                LastUpdatedTime = comment.updated_at,
                Links =
                    {
                        SyndicationLink.CreateAlternateLink(comment.html_url),
                        new SyndicationLink(new Uri(model.CommitUrl)) { RelationshipType = "related", Title = "CommitUrl" }
                    },
                PublishDate = comment.created_at,
                Title = new TextSyndicationContent(HttpUtility.HtmlEncode(title), TextSyndicationContentKind.Html),
            };
        }