From a1d690bc2acc91d92732de6cae1ec396cf399b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Fri, 13 Mar 2020 00:46:35 +0100 Subject: [PATCH] Add support to get raw file content as byte[] This allows to spare a little over 100ms in my tests on small files vs using the `GetAllContentsByRef` API. Fixes #1651 --- .../IObservableRepositoryContentsClient.cs | 23 ++++++++ .../ObservableRepositoryContentsClient.cs | 39 ++++++++++++++ Octokit/Clients/IRepositoryContentsClient.cs | 25 +++++++++ Octokit/Clients/RepositoryContentsClient.cs | 53 +++++++++++++++++-- Octokit/Helpers/AcceptHeaders.cs | 6 +++ Octokit/Http/ApiConnection.cs | 15 ++++++ Octokit/Http/Connection.cs | 26 +++++++++ Octokit/Http/HttpClientAdapter.cs | 1 + Octokit/Http/IApiConnection.cs | 9 ++++ Octokit/Http/IConnection.cs | 9 ++++ 10 files changed, 203 insertions(+), 3 deletions(-) diff --git a/Octokit.Reactive/Clients/IObservableRepositoryContentsClient.cs b/Octokit.Reactive/Clients/IObservableRepositoryContentsClient.cs index e08c7128c6..b55afc633c 100644 --- a/Octokit.Reactive/Clients/IObservableRepositoryContentsClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepositoryContentsClient.cs @@ -137,6 +137,17 @@ public interface IObservableRepositoryContentsClient /// The name of the repository IObservable GetAllContents(string owner, string name); + /// + /// Returns the raw content of the file at the given or null if the path is a directory. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The content path + IObservable GetRawContent(string owner, string name, string path); + /// /// Returns the contents of the root directory in a repository. /// @@ -156,6 +167,18 @@ public interface IObservableRepositoryContentsClient /// The content path IObservable GetAllContentsByRef(string owner, string name, string reference, string path); + /// + /// Returns the raw content of the file at the given or null if the path is a directory. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The content path + /// The name of the commit/branch/tag. + IObservable GetRawContentByRef(string owner, string name, string path, string reference); + /// /// Returns the contents of a file or directory in a repository. /// diff --git a/Octokit.Reactive/Clients/ObservableRepositoryContentsClient.cs b/Octokit.Reactive/Clients/ObservableRepositoryContentsClient.cs index f70804c824..7d55e88939 100644 --- a/Octokit.Reactive/Clients/ObservableRepositoryContentsClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepositoryContentsClient.cs @@ -238,6 +238,25 @@ public IObservable GetAllContents(string owner, string name) .GetAndFlattenAllPages(ApiUrls.RepositoryContent(owner, name, string.Empty)); } + /// + /// Returns the raw content of the file at the given or null if the path is a directory. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The content path + public IObservable GetRawContent(string owner, string name, string path) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(name, nameof(name)); + + return _client + .Connection + .GetAndFlattenAllPages(ApiUrls.RepositoryContent(owner, name, string.Empty)); + } + /// /// Returns the contents of the root directory in a repository. /// @@ -270,6 +289,26 @@ public IObservable GetAllContentsByRef(string owner, string n return _client.Connection.GetAndFlattenAllPages(ApiUrls.RepositoryContent(owner, name, path, reference)); } + /// + /// Returns the raw content of the file at the given or null if the path is a directory. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The content path + /// The name of the commit/branch/tag. + public IObservable GetRawContentByRef(string owner, string name, string path, string reference) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(name, nameof(name)); + Ensure.ArgumentNotNullOrEmptyString(reference, nameof(reference)); + Ensure.ArgumentNotNullOrEmptyString(path, nameof(path)); + + return _client.Connection.GetAndFlattenAllPages(ApiUrls.RepositoryContent(owner, name, path, reference)); + } + /// /// Returns the contents of a file or directory in a repository. /// diff --git a/Octokit/Clients/IRepositoryContentsClient.cs b/Octokit/Clients/IRepositoryContentsClient.cs index 08f986b1a8..2967b09cb7 100644 --- a/Octokit/Clients/IRepositoryContentsClient.cs +++ b/Octokit/Clients/IRepositoryContentsClient.cs @@ -25,6 +25,18 @@ public interface IRepositoryContentsClient [ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 29/08/2017)")] Task> GetAllContents(string owner, string name, string path); + /// + /// Returns the raw content of the file at the given or null if the path is a directory. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The content path + [ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 29/08/2017)")] + Task GetRawContent(string owner, string name, string path); + /// /// Returns the contents of a file or directory in a repository. /// @@ -70,6 +82,19 @@ public interface IRepositoryContentsClient [ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 29/08/2017)")] Task> GetAllContentsByRef(string owner, string name, string path, string reference); + /// + /// Returns the raw content of the file at the given or null if the path is a directory. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The content path + /// The name of the commit/branch/tag. + [ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 29/08/2017)")] + Task GetRawContentByRef(string owner, string name, string path, string reference); + /// /// Returns the contents of a file or directory in a repository. /// diff --git a/Octokit/Clients/RepositoryContentsClient.cs b/Octokit/Clients/RepositoryContentsClient.cs index 2259433e80..b8385d373d 100644 --- a/Octokit/Clients/RepositoryContentsClient.cs +++ b/Octokit/Clients/RepositoryContentsClient.cs @@ -41,6 +41,27 @@ public Task> GetAllContents(string owner, strin return ApiConnection.GetAll(url); } + /// + /// Returns the raw content of the file at the given or null if the path is a directory. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The content path + [ManualRoute("GET", "repos/{owner}/{repo}/contents/{path}")] + public Task GetRawContent(string owner, string name, string path) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(name, nameof(name)); + Ensure.ArgumentNotNullOrEmptyString(path, nameof(path)); + + var url = ApiUrls.RepositoryContent(owner, name, path); + + return ApiConnection.GetRaw(url, null); + } + /// /// Returns the contents of a file or directory in a repository. /// @@ -112,11 +133,37 @@ public Task> GetAllContentsByRef(string owner, Ensure.ArgumentNotNullOrEmptyString(path, nameof(path)); Ensure.ArgumentNotNullOrEmptyString(reference, nameof(reference)); - var url = (path == "/") + var url = GetContentsUrl(owner, name, path, reference); + return ApiConnection.GetAll(url); + } + + /// + /// Returns the raw content of the file at the given or null if the path is a directory. + /// + /// + /// See the API documentation for more information. + /// + /// The owner of the repository + /// The name of the repository + /// The content path + /// The name of the commit/branch/tag. + [ManualRoute("GET", "repos/{owner}/{repo}/contents/{path}?ref={ref}")] + public Task GetRawContentByRef(string owner, string name, string path, string reference) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(name, nameof(name)); + Ensure.ArgumentNotNullOrEmptyString(path, nameof(path)); + Ensure.ArgumentNotNullOrEmptyString(reference, nameof(reference)); + + var url = GetContentsUrl(owner, name, path, reference); + return ApiConnection.GetRaw(url, null); + } + + private Uri GetContentsUrl(string owner, string name, string path, string reference) + { + return path == "/" ? ApiUrls.RepositoryContent(owner, name, "", reference) : ApiUrls.RepositoryContent(owner, name, path, reference); - - return ApiConnection.GetAll(url); } /// diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs index 6e9f38bf95..50714971b5 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -12,6 +12,12 @@ public static class AcceptHeaders public const string CommitReferenceSha1MediaType = "application/vnd.github.v3.sha"; + /// + /// Support for retrieving raw file content with the method. + /// + /// https://developer.github.com/v3/repos/contents/#custom-media-types + public const string RawContentMediaType = "application/vnd.github.v3.raw"; + public const string OrganizationPermissionsPreview = "application/vnd.github.ironman-preview+json"; /// diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index aa8c514c09..dd3cc04102 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -106,6 +106,21 @@ public async Task GetHtml(Uri uri, IDictionary parameter return response.Body; } + /// + /// Gets the raw content of the API resource at the specified URI. + /// + /// URI of the API resource to get + /// Parameters to add to the API request + /// The API resource's raw content or null if the points to a directory. + /// Thrown when an API error occurs. + public async Task GetRaw(Uri uri, IDictionary parameters) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + + var response = await Connection.GetRaw(uri, parameters).ConfigureAwait(false); + return response.Body; + } + /// /// Gets all API resources in the list at the specified URI. /// diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 106cac7066..6c2c7560cd 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -211,6 +211,25 @@ public Task> GetHtml(Uri uri, IDictionary p }); } + /// + /// Performs an asynchronous HTTP GET request that expects a containing raw data. + /// + /// URI endpoint to send request to + /// Querystring parameters for the request + /// representing the received HTTP response + /// The property will be null if the points to a directory instead of a file + public Task> GetRaw(Uri uri, IDictionary parameters) + { + Ensure.ArgumentNotNull(uri, nameof(uri)); + + return GetRaw(new Request + { + Method = HttpMethod.Get, + BaseAddress = BaseAddress, + Endpoint = uri.ApplyParameters(parameters) + }); + } + public Task> Patch(Uri uri, object body) { Ensure.ArgumentNotNull(uri, nameof(uri)); @@ -620,6 +639,13 @@ async Task> GetHtml(IRequest request) return new ApiResponse(response, response.Body as string); } + async Task> GetRaw(IRequest request) + { + request.Headers.Add("Accept", AcceptHeaders.RawContentMediaType); + var response = await RunRequest(request, CancellationToken.None).ConfigureAwait(false); + return new ApiResponse(response, response.Body as byte[]); + } + async Task> Run(IRequest request, CancellationToken cancellationToken) { _jsonPipeline.SerializeRequest(request); diff --git a/Octokit/Http/HttpClientAdapter.cs b/Octokit/Http/HttpClientAdapter.cs index 8b997a50ab..2c780342f5 100644 --- a/Octokit/Http/HttpClientAdapter.cs +++ b/Octokit/Http/HttpClientAdapter.cs @@ -100,6 +100,7 @@ protected virtual async Task BuildResponse(HttpResponseMessage respon // We added support for downloading images,zip-files and application/octet-stream. // Let's constrain this appropriately. var binaryContentTypes = new[] { + AcceptHeaders.RawContentMediaType, "application/zip" , "application/x-gzip" , "application/octet-stream"}; diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index 616dbfead0..976d6c9b61 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -62,6 +62,15 @@ public interface IApiConnection /// Thrown when an API error occurs. Task GetHtml(Uri uri, IDictionary parameters); + /// + /// Gets the raw content of the API resource at the specified URI. + /// + /// URI of the API resource to get + /// Parameters to add to the API request + /// The API resource's raw content or null if the points to a directory. + /// Thrown when an API error occurs. + Task GetRaw(Uri uri, IDictionary parameters); + /// /// Gets all API resources in the list at the specified URI. /// diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 08755cb0ee..920b871d9b 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -20,6 +20,15 @@ public interface IConnection : IApiInfoProvider /// representing the received HTTP response Task> GetHtml(Uri uri, IDictionary parameters); + /// + /// Performs an asynchronous HTTP GET request that expects a containing raw data. + /// + /// URI endpoint to send request to + /// Querystring parameters for the request + /// representing the received HTTP response + /// The property will be null if the points to a directory instead of a file + Task> GetRaw(Uri uri, IDictionary parameters); + /// /// Performs an asynchronous HTTP GET request. /// Attempts to map the response to an object of type