{"action":"created","comment":{"url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/comments/30219233","id":30219233,"diff_hunk":"@@ -0,0 +1,493 @@\n+﻿// --------------------------------------------------------------------------------------------------------------------\n+// <copyright file=\"AmazonS3Cache.cs\" company=\"James South\">\n+//   Copyright (c) James South.\n+//   Licensed under the Apache License, Version 2.0.\n+// </copyright>\n+// <summary>\n+//   Provides an <see cref=\"IImageCache\" /> implementation that uses Amazon S3 storage.\n+//   The cache is self healing and cleaning.\n+// </summary>\n+// --------------------------------------------------------------------------------------------------------------------\n+\n+namespace ImageProcessor.Web.Plugins.AmazonS3Cache\n+{\n+    using System;\n+    using System.Collections.Generic;\n+    using System.Globalization;\n+    using System.IO;\n+    using System.Linq;\n+    using System.Net;\n+    using System.Threading.Tasks;\n+    using System.Web;\n+\n+    using Amazon;\n+    using Amazon.S3;\n+    using Amazon.S3.Model;\n+    using Amazon.S3.Transfer;\n+\n+    using ImageProcessor.Web.Caching;\n+    using ImageProcessor.Web.Extensions;\n+    using ImageProcessor.Web.Helpers;\n+\n+    /// <summary>\n+    /// Provides an <see cref=\"IImageCache\"/> implementation that uses Amazon S3 storage.\n+    /// The cache is self healing and cleaning.\n+    /// </summary>\n+    public class AmazonS3Cache : ImageCacheBase\n+    {\n+        #region Fields\n+\n+        /// <summary>\n+        /// The Amazon S3 client.\n+        /// </summary>\n+        private readonly AmazonS3Client amazonS3ClientCache;\n+\n+        /// <summary>\n+        /// The cached root url for a content delivery network.\n+        /// </summary>\n+        private readonly string cachedCdnRoot;\n+\n+        /// <summary>\n+        /// Image Processor Cache folder in S3.\n+        /// </summary>\n+        private string imageProcessorCachePrefix = @\"imageprocessor_cache/\";\n+\n+        #endregion\n+\n+        #region Constructor\n+\n+        /// <summary>\n+        /// Initialises a new instance of the <see cref=\"AmazonS3Cache\"/> class.\n+        /// </summary>\n+        /// <param name=\"requestPath\">\n+        /// The request path for the image.\n+        /// </param>\n+        /// <param name=\"fullPath\">\n+        /// The full path for the image.\n+        /// </param>\n+        /// <param name=\"querystring\">\n+        /// The querystring containing instructions.\n+        /// </param>\n+        public AmazonS3Cache(string requestPath, string fullPath, string querystring)\n+            : base(requestPath, fullPath, querystring)\n+        {\n+            this.AwsAccessKey = this.Settings[\"awsAccessKey\"];\n+            this.AwsSecretKey = this.Settings[\"awsSecretKey\"];\n+            this.AwsBucketName = this.Settings[\"awsBucketName\"];\n+            this.AwsRegionEndpoint = GetRegionEndpoint();\n+\n+            if (AwsIsValid)\n+            {\n+                // Create S3 client from AWS Access Key and AWS Secret Access Key.\n+                this.amazonS3ClientCache = new AmazonS3Client(this.AwsAccessKey, this.AwsSecretKey, this.AwsRegionEndpoint);\n+            }\n+\n+            this.cachedCdnRoot = this.Settings.ContainsKey(\"CachedCDNRoot\")\n+                                     ? this.Settings[\"CachedCDNRoot\"]\n+                                     : string.Empty;\n+        }\n+\n+        #endregion\n+\n+        #region Properties\n+\n+        /// <summary>\n+        /// Gets or sets Amazon S3 Access Key.\n+        /// </summary>\n+        private string AwsAccessKey { get; set; }\n+\n+        /// <summary>\n+        /// Gets or sets Amazon S3 Secret Key.\n+        /// </summary>\n+        private string AwsSecretKey { get; set; }\n+\n+        /// <summary>\n+        /// Gets or sets Amazon S3 Bucket Name.\n+        /// </summary>\n+        private string AwsBucketName { get; set; }\n+\n+        /// <summary>\n+        /// Gets or sets Amazon S3 Region Endpoint.\n+        /// </summary>\n+        private RegionEndpoint AwsRegionEndpoint { get; set; }\n+\n+        /// <summary>\n+        /// Gets a value indicating whether Amazon S3 Access Key, Secret Key or Bucket Name are empty strings: i.e. whether <see cref=\"AwsIsWalid\"/>.\n+        /// </summary>\n+        private bool AwsIsValid\n+        {\n+            get\n+            {\n+                return !string.IsNullOrWhiteSpace(AwsAccessKey) && !string.IsNullOrWhiteSpace(AwsSecretKey)\n+                       && !string.IsNullOrWhiteSpace(AwsBucketName);\n+            }\n+        }\n+\n+        #endregion\n+\n+        #region Methods\n+\n+        /// <summary>\n+        /// Gets a value indicating whether the image is new or updated in an asynchronous manner.\n+        /// </summary>\n+        /// <returns>\n+        /// The asynchronous <see cref=\"Task\"/> returning the value.\n+        /// </returns>\n+        public override async Task<bool> IsNewOrUpdatedAsync()\n+        {\n+            string cachedFileName = await this.CreateCachedFileNameAsync();\n+\n+            // Collision rate of about 1 in 10000 for the folder structure.\n+            // That gives us massive scope to store millions of files.\n+            string pathFromKey = string.Join(\"\\\\\", cachedFileName.ToCharArray().Take(6));\n+            this.CachedPath =\n+                Path.Combine(this.cachedCdnRoot, this.imageProcessorCachePrefix, pathFromKey, cachedFileName)\n+                    .Replace(@\"\\\", \"/\");\n+\n+            bool isUpdated = false;\n+            CachedImage cachedImage = CacheIndexer.Get(this.CachedPath);\n+\n+            if (new Uri(this.CachedPath).IsFile)\n+            {\n+                FileInfo fileInfo = new FileInfo(this.CachedPath);\n+\n+                if (fileInfo.Exists)\n+                {\n+                    // Pull the latest info.\n+                    fileInfo.Refresh();\n+\n+                    cachedImage = new CachedImage\n+                    {\n+                        Key = Path.GetFileNameWithoutExtension(this.CachedPath),\n+                        Path = this.CachedPath,\n+                        CreationTimeUtc = fileInfo.CreationTimeUtc\n+                    };\n+\n+                    CacheIndexer.Add(cachedImage);\n+                }\n+            }\n+\n+            if (cachedImage == null)\n+            {\n+                try\n+                {\n+                    var path = GetFolderStructureForAmazon(this.CachedPath);\n+                    var filename = Path.GetFileName(this.CachedPath);\n+                    var key = GetKey(path, filename);\n+\n+                    var objectMetaDataRequest = new GetObjectMetadataRequest\n+                    {\n+                        BucketName = this.AwsBucketName,\n+                        Key = key,\n+                    };\n+\n+                    var response = await amazonS3ClientCache.GetObjectMetadataAsync(objectMetaDataRequest);\n+\n+                    if (response != null)\n+                    {\n+                        cachedImage = new CachedImage\n+                        {\n+                            Key = key,\n+                            Path = this.CachedPath,\n+                            CreationTimeUtc = response.LastModified\n+                                                      .ToUniversalTime()\n+                        };\n+\n+                        CacheIndexer.Add(cachedImage);\n+                    }\n+                }\n+                catch (Amazon.S3.AmazonS3Exception ex)\n+                {\n+                    // Nothing in S3 so we should return true.\n+                    isUpdated = true;\n+                }\n+            }\n+\n+            if (cachedImage == null)\n+            {\n+                // Nothing in the cache so we should return true.\n+                isUpdated = true;\n+            }\n+            else\n+            {\n+                // Check to see if the cached image is set to expire.\n+                if (this.IsExpired(cachedImage.CreationTimeUtc))\n+                {\n+                    CacheIndexer.Remove(this.CachedPath);\n+                    isUpdated = true;\n+                }\n+            }\n+\n+            return isUpdated;\n+        }\n+\n+        /// <summary>\n+        /// Adds the image to the cache in an asynchronous manner.\n+        /// </summary>\n+        /// <param name=\"stream\">\n+        /// The stream containing the image data.\n+        /// </param>\n+        /// <param name=\"contentType\">\n+        /// The content type of the image.\n+        /// </param>\n+        /// <returns>\n+        /// The <see cref=\"Task\"/> representing an asynchronous operation.\n+        /// </returns>\n+        public override async Task AddImageToCacheAsync(Stream stream, string contentType)\n+        {\n+            var transferUtility = new TransferUtility(this.amazonS3ClientCache);\n+\n+            var path = GetFolderStructureForAmazon(this.CachedPath);\n+            var filename = Path.GetFileName(this.CachedPath);\n+            var key = GetKey(path, filename);\n+\n+            var transferUtilityUploadRequest = new TransferUtilityUploadRequest\n+            {\n+                BucketName = this.AwsBucketName,\n+                InputStream = stream,\n+                Key = key,\n+                CannedACL = S3CannedACL.PublicRead\n+            };\n+\n+            await transferUtility.UploadAsync(transferUtilityUploadRequest);\n+        }\n+\n+        /// <summary>\n+        /// Trims the cache of any expired items in an asynchronous manner.\n+        /// </summary>\n+        /// <returns>\n+        /// The asynchronous <see cref=\"Task\"/> representing an asynchronous operation.\n+        /// </returns>\n+        public override async Task TrimCacheAsync()\n+        {\n+            // At the present time ListObjectsRequest doesn't work.\n+            // TODO: find an alternative solution? \n+            return;","path":"src/Plugins/ImageProcessor.Web/ImageProcessor.Web.Plugins.AmazonS3Cache/AmazonS3Cache.cs","position":265,"original_position":265,"commit_id":"45d572b09eebece2b6a89484325a80e2baed9ab1","original_commit_id":"45d572b09eebece2b6a89484325a80e2baed9ab1","user":{"login":"JimBobSquarePants","id":385879,"avatar_url":"https://avatars.githubusercontent.com/u/385879?v=3","gravatar_id":"","url":"https://api.github.com/users/JimBobSquarePants","html_url":"https://github.com/JimBobSquarePants","followers_url":"https://api.github.com/users/JimBobSquarePants/followers","following_url":"https://api.github.com/users/JimBobSquarePants/following{/other_user}","gists_url":"https://api.github.com/users/JimBobSquarePants/gists{/gist_id}","starred_url":"https://api.github.com/users/JimBobSquarePants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JimBobSquarePants/subscriptions","organizations_url":"https://api.github.com/users/JimBobSquarePants/orgs","repos_url":"https://api.github.com/users/JimBobSquarePants/repos","events_url":"https://api.github.com/users/JimBobSquarePants/events{/privacy}","received_events_url":"https://api.github.com/users/JimBobSquarePants/received_events","type":"User","site_admin":false},"body":"Cool. I'll try and do some reading.","created_at":"2015-05-13T10:36:08Z","updated_at":"2015-05-13T10:36:08Z","html_url":"https://github.com/JimBobSquarePants/ImageProcessor/pull/168#discussion_r30219233","pull_request_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/168","_links":{"self":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/comments/30219233"},"html":{"href":"https://github.com/JimBobSquarePants/ImageProcessor/pull/168#discussion_r30219233"},"pull_request":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/168"}}},"pull_request":{"url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/168","id":35249182,"html_url":"https://github.com/JimBobSquarePants/ImageProcessor/pull/168","diff_url":"https://github.com/JimBobSquarePants/ImageProcessor/pull/168.diff","patch_url":"https://github.com/JimBobSquarePants/ImageProcessor/pull/168.patch","issue_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/168","number":168,"state":"open","locked":false,"title":"Amazon S3 Cache Plugin","user":{"login":"rmillo8","id":8375318,"avatar_url":"https://avatars.githubusercontent.com/u/8375318?v=3","gravatar_id":"","url":"https://api.github.com/users/rmillo8","html_url":"https://github.com/rmillo8","followers_url":"https://api.github.com/users/rmillo8/followers","following_url":"https://api.github.com/users/rmillo8/following{/other_user}","gists_url":"https://api.github.com/users/rmillo8/gists{/gist_id}","starred_url":"https://api.github.com/users/rmillo8/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rmillo8/subscriptions","organizations_url":"https://api.github.com/users/rmillo8/orgs","repos_url":"https://api.github.com/users/rmillo8/repos","events_url":"https://api.github.com/users/rmillo8/events{/privacy}","received_events_url":"https://api.github.com/users/rmillo8/received_events","type":"User","site_admin":false},"body":"Hi James,\r\n  it took a while but the much awaited Amazon S3 Cache Plugin is finally ready for a pull request!\r\n\r\nLet me know if you need more info, or if you'd want me to change/add something!\r\n(The site has been running for a few weeks now, without problems, on one of the sites I'm currently working on.)\r\n\r\nRaf","created_at":"2015-05-12T17:15:05Z","updated_at":"2015-05-13T10:36:08Z","closed_at":null,"merged_at":null,"merge_commit_sha":"30132a3d9263c80acfe2caaa59003d567b34b06b","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/168/commits","review_comments_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/168/comments","review_comment_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/comments{/number}","comments_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/168/comments","statuses_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/statuses/45d572b09eebece2b6a89484325a80e2baed9ab1","head":{"label":"rmillo8:master","ref":"master","sha":"45d572b09eebece2b6a89484325a80e2baed9ab1","user":{"login":"rmillo8","id":8375318,"avatar_url":"https://avatars.githubusercontent.com/u/8375318?v=3","gravatar_id":"","url":"https://api.github.com/users/rmillo8","html_url":"https://github.com/rmillo8","followers_url":"https://api.github.com/users/rmillo8/followers","following_url":"https://api.github.com/users/rmillo8/following{/other_user}","gists_url":"https://api.github.com/users/rmillo8/gists{/gist_id}","starred_url":"https://api.github.com/users/rmillo8/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rmillo8/subscriptions","organizations_url":"https://api.github.com/users/rmillo8/orgs","repos_url":"https://api.github.com/users/rmillo8/repos","events_url":"https://api.github.com/users/rmillo8/events{/privacy}","received_events_url":"https://api.github.com/users/rmillo8/received_events","type":"User","site_admin":false},"repo":{"id":32466845,"name":"ImageProcessor","full_name":"rmillo8/ImageProcessor","owner":{"login":"rmillo8","id":8375318,"avatar_url":"https://avatars.githubusercontent.com/u/8375318?v=3","gravatar_id":"","url":"https://api.github.com/users/rmillo8","html_url":"https://github.com/rmillo8","followers_url":"https://api.github.com/users/rmillo8/followers","following_url":"https://api.github.com/users/rmillo8/following{/other_user}","gists_url":"https://api.github.com/users/rmillo8/gists{/gist_id}","starred_url":"https://api.github.com/users/rmillo8/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rmillo8/subscriptions","organizations_url":"https://api.github.com/users/rmillo8/orgs","repos_url":"https://api.github.com/users/rmillo8/repos","events_url":"https://api.github.com/users/rmillo8/events{/privacy}","received_events_url":"https://api.github.com/users/rmillo8/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rmillo8/ImageProcessor","description":"A library for on-the-fly processing of image files with Asp.NET written in C#","fork":true,"url":"https://api.github.com/repos/rmillo8/ImageProcessor","forks_url":"https://api.github.com/repos/rmillo8/ImageProcessor/forks","keys_url":"https://api.github.com/repos/rmillo8/ImageProcessor/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rmillo8/ImageProcessor/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rmillo8/ImageProcessor/teams","hooks_url":"https://api.github.com/repos/rmillo8/ImageProcessor/hooks","issue_events_url":"https://api.github.com/repos/rmillo8/ImageProcessor/issues/events{/number}","events_url":"https://api.github.com/repos/rmillo8/ImageProcessor/events","assignees_url":"https://api.github.com/repos/rmillo8/ImageProcessor/assignees{/user}","branches_url":"https://api.github.com/repos/rmillo8/ImageProcessor/branches{/branch}","tags_url":"https://api.github.com/repos/rmillo8/ImageProcessor/tags","blobs_url":"https://api.github.com/repos/rmillo8/ImageProcessor/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rmillo8/ImageProcessor/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rmillo8/ImageProcessor/git/refs{/sha}","trees_url":"https://api.github.com/repos/rmillo8/ImageProcessor/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rmillo8/ImageProcessor/statuses/{sha}","languages_url":"https://api.github.com/repos/rmillo8/ImageProcessor/languages","stargazers_url":"https://api.github.com/repos/rmillo8/ImageProcessor/stargazers","contributors_url":"https://api.github.com/repos/rmillo8/ImageProcessor/contributors","subscribers_url":"https://api.github.com/repos/rmillo8/ImageProcessor/subscribers","subscription_url":"https://api.github.com/repos/rmillo8/ImageProcessor/subscription","commits_url":"https://api.github.com/repos/rmillo8/ImageProcessor/commits{/sha}","git_commits_url":"https://api.github.com/repos/rmillo8/ImageProcessor/git/commits{/sha}","comments_url":"https://api.github.com/repos/rmillo8/ImageProcessor/comments{/number}","issue_comment_url":"https://api.github.com/repos/rmillo8/ImageProcessor/issues/comments{/number}","contents_url":"https://api.github.com/repos/rmillo8/ImageProcessor/contents/{+path}","compare_url":"https://api.github.com/repos/rmillo8/ImageProcessor/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rmillo8/ImageProcessor/merges","archive_url":"https://api.github.com/repos/rmillo8/ImageProcessor/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rmillo8/ImageProcessor/downloads","issues_url":"https://api.github.com/repos/rmillo8/ImageProcessor/issues{/number}","pulls_url":"https://api.github.com/repos/rmillo8/ImageProcessor/pulls{/number}","milestones_url":"https://api.github.com/repos/rmillo8/ImageProcessor/milestones{/number}","notifications_url":"https://api.github.com/repos/rmillo8/ImageProcessor/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rmillo8/ImageProcessor/labels{/name}","releases_url":"https://api.github.com/repos/rmillo8/ImageProcessor/releases{/id}","created_at":"2015-03-18T15:24:33Z","updated_at":"2015-03-25T16:15:04Z","pushed_at":"2015-05-12T11:18:35Z","git_url":"git://github.com/rmillo8/ImageProcessor.git","ssh_url":"git@github.com:rmillo8/ImageProcessor.git","clone_url":"https://github.com/rmillo8/ImageProcessor.git","svn_url":"https://github.com/rmillo8/ImageProcessor","homepage":"http://imageprocessor.org","size":376240,"stargazers_count":0,"watchers_count":0,"language":"C#","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"JimBobSquarePants:master","ref":"master","sha":"4649280535c23e19836bcefe2afa7c19f773902f","user":{"login":"JimBobSquarePants","id":385879,"avatar_url":"https://avatars.githubusercontent.com/u/385879?v=3","gravatar_id":"","url":"https://api.github.com/users/JimBobSquarePants","html_url":"https://github.com/JimBobSquarePants","followers_url":"https://api.github.com/users/JimBobSquarePants/followers","following_url":"https://api.github.com/users/JimBobSquarePants/following{/other_user}","gists_url":"https://api.github.com/users/JimBobSquarePants/gists{/gist_id}","starred_url":"https://api.github.com/users/JimBobSquarePants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JimBobSquarePants/subscriptions","organizations_url":"https://api.github.com/users/JimBobSquarePants/orgs","repos_url":"https://api.github.com/users/JimBobSquarePants/repos","events_url":"https://api.github.com/users/JimBobSquarePants/events{/privacy}","received_events_url":"https://api.github.com/users/JimBobSquarePants/received_events","type":"User","site_admin":false},"repo":{"id":4471160,"name":"ImageProcessor","full_name":"JimBobSquarePants/ImageProcessor","owner":{"login":"JimBobSquarePants","id":385879,"avatar_url":"https://avatars.githubusercontent.com/u/385879?v=3","gravatar_id":"","url":"https://api.github.com/users/JimBobSquarePants","html_url":"https://github.com/JimBobSquarePants","followers_url":"https://api.github.com/users/JimBobSquarePants/followers","following_url":"https://api.github.com/users/JimBobSquarePants/following{/other_user}","gists_url":"https://api.github.com/users/JimBobSquarePants/gists{/gist_id}","starred_url":"https://api.github.com/users/JimBobSquarePants/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/JimBobSquarePants/subscriptions","organizations_url":"https://api.github.com/users/JimBobSquarePants/orgs","repos_url":"https://api.github.com/users/JimBobSquarePants/repos","events_url":"https://api.github.com/users/JimBobSquarePants/events{/privacy}","received_events_url":"https://api.github.com/users/JimBobSquarePants/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/JimBobSquarePants/ImageProcessor","description":"A library for on-the-fly processing of image files written in C#","fork":false,"url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor","forks_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/forks","keys_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/keys{/key_id}","collaborators_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/teams","hooks_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/hooks","issue_events_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/events{/number}","events_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/events","assignees_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/assignees{/user}","branches_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/branches{/branch}","tags_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/tags","blobs_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/refs{/sha}","trees_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/trees{/sha}","statuses_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/statuses/{sha}","languages_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/languages","stargazers_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/stargazers","contributors_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/contributors","subscribers_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/subscribers","subscription_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/subscription","commits_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/commits{/sha}","git_commits_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/git/commits{/sha}","comments_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/comments{/number}","issue_comment_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/comments{/number}","contents_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/contents/{+path}","compare_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/compare/{base}...{head}","merges_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/merges","archive_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/downloads","issues_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues{/number}","pulls_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls{/number}","milestones_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/milestones{/number}","notifications_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/labels{/name}","releases_url":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/releases{/id}","created_at":"2012-05-28T14:11:35Z","updated_at":"2015-05-12T10:47:17Z","pushed_at":"2015-05-12T17:15:06Z","git_url":"git://github.com/JimBobSquarePants/ImageProcessor.git","ssh_url":"git@github.com:JimBobSquarePants/ImageProcessor.git","clone_url":"https://github.com/JimBobSquarePants/ImageProcessor.git","svn_url":"https://github.com/JimBobSquarePants/ImageProcessor","homepage":"http://imageprocessor.org","size":390419,"stargazers_count":469,"watchers_count":469,"language":"C#","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":109,"mirror_url":null,"open_issues_count":14,"forks":109,"open_issues":14,"watchers":469,"default_branch":"V2"}},"_links":{"self":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/168"},"html":{"href":"https://github.com/JimBobSquarePants/ImageProcessor/pull/168"},"issue":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/168"},"comments":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/issues/168/comments"},"review_comments":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/168/comments"},"review_comment":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/pulls/168/commits"},"statuses":{"href":"https://api.github.com/repos/JimBobSquarePants/ImageProcessor/statuses/45d572b09eebece2b6a89484325a80e2baed9ab1"}}}}