Skip to content

Commit

Permalink
Add support to get raw file content as byte[]
Browse files Browse the repository at this point in the history
This allows to spare a little over 100ms in my tests on small files vs using the `GetAllContentsByRef` API.

Fixes octokit#1651
  • Loading branch information
0xced committed Mar 13, 2020
1 parent 5de5258 commit a1d690b
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 3 deletions.
23 changes: 23 additions & 0 deletions Octokit.Reactive/Clients/IObservableRepositoryContentsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ public interface IObservableRepositoryContentsClient
/// <param name="name">The name of the repository</param>
IObservable<RepositoryContent> GetAllContents(string owner, string name);

/// <summary>
/// Returns the raw content of the file at the given <paramref name="path"/> or <c>null</c> if the path is a directory.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/contents/#get-contents">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="path">The content path</param>
IObservable<byte[]> GetRawContent(string owner, string name, string path);

/// <summary>
/// Returns the contents of the root directory in a repository.
/// </summary>
Expand All @@ -156,6 +167,18 @@ public interface IObservableRepositoryContentsClient
/// <param name="path">The content path</param>
IObservable<RepositoryContent> GetAllContentsByRef(string owner, string name, string reference, string path);

/// <summary>
/// Returns the raw content of the file at the given <paramref name="path"/> or <c>null</c> if the path is a directory.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/contents/#get-contents">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="path">The content path</param>
/// <param name="reference">The name of the commit/branch/tag.</param>
IObservable<byte[]> GetRawContentByRef(string owner, string name, string path, string reference);

/// <summary>
/// Returns the contents of a file or directory in a repository.
/// </summary>
Expand Down
39 changes: 39 additions & 0 deletions Octokit.Reactive/Clients/ObservableRepositoryContentsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,25 @@ public IObservable<RepositoryContent> GetAllContents(string owner, string name)
.GetAndFlattenAllPages<RepositoryContent>(ApiUrls.RepositoryContent(owner, name, string.Empty));
}

/// <summary>
/// Returns the raw content of the file at the given <paramref name="path"/> or <c>null</c> if the path is a directory.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/contents/#get-contents">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="path">The content path</param>
public IObservable<byte[]> GetRawContent(string owner, string name, string path)
{
Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner));
Ensure.ArgumentNotNullOrEmptyString(name, nameof(name));

return _client
.Connection
.GetAndFlattenAllPages<byte[]>(ApiUrls.RepositoryContent(owner, name, string.Empty));
}

/// <summary>
/// Returns the contents of the root directory in a repository.
/// </summary>
Expand Down Expand Up @@ -270,6 +289,26 @@ public IObservable<RepositoryContent> GetAllContentsByRef(string owner, string n
return _client.Connection.GetAndFlattenAllPages<RepositoryContent>(ApiUrls.RepositoryContent(owner, name, path, reference));
}

/// <summary>
/// Returns the raw content of the file at the given <paramref name="path"/> or <c>null</c> if the path is a directory.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/contents/#get-contents">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="path">The content path</param>
/// <param name="reference">The name of the commit/branch/tag.</param>
public IObservable<byte[]> 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<byte[]>(ApiUrls.RepositoryContent(owner, name, path, reference));
}

/// <summary>
/// Returns the contents of a file or directory in a repository.
/// </summary>
Expand Down
25 changes: 25 additions & 0 deletions Octokit/Clients/IRepositoryContentsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ public interface IRepositoryContentsClient
[ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 29/08/2017)")]
Task<IReadOnlyList<RepositoryContent>> GetAllContents(string owner, string name, string path);

/// <summary>
/// Returns the raw content of the file at the given <paramref name="path"/> or <c>null</c> if the path is a directory.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/contents/#get-contents">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="path">The content path</param>
[ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 29/08/2017)")]
Task<byte[]> GetRawContent(string owner, string name, string path);

/// <summary>
/// Returns the contents of a file or directory in a repository.
/// </summary>
Expand Down Expand Up @@ -70,6 +82,19 @@ public interface IRepositoryContentsClient
[ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 29/08/2017)")]
Task<IReadOnlyList<RepositoryContent>> GetAllContentsByRef(string owner, string name, string path, string reference);

/// <summary>
/// Returns the raw content of the file at the given <paramref name="path"/> or <c>null</c> if the path is a directory.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/contents/#get-contents">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="path">The content path</param>
/// <param name="reference">The name of the commit/branch/tag.</param>
[ExcludeFromPaginationApiOptionsConventionTest("Pagination not supported by GitHub API (tested 29/08/2017)")]
Task<byte[]> GetRawContentByRef(string owner, string name, string path, string reference);

/// <summary>
/// Returns the contents of a file or directory in a repository.
/// </summary>
Expand Down
53 changes: 50 additions & 3 deletions Octokit/Clients/RepositoryContentsClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ public Task<IReadOnlyList<RepositoryContent>> GetAllContents(string owner, strin
return ApiConnection.GetAll<RepositoryContent>(url);
}

/// <summary>
/// Returns the raw content of the file at the given <paramref name="path"/> or <c>null</c> if the path is a directory.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/contents/#get-contents">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="path">The content path</param>
[ManualRoute("GET", "repos/{owner}/{repo}/contents/{path}")]
public Task<byte[]> 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);
}

/// <summary>
/// Returns the contents of a file or directory in a repository.
/// </summary>
Expand Down Expand Up @@ -112,11 +133,37 @@ public Task<IReadOnlyList<RepositoryContent>> 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<RepositoryContent>(url);
}

/// <summary>
/// Returns the raw content of the file at the given <paramref name="path"/> or <c>null</c> if the path is a directory.
/// </summary>
/// <remarks>
/// See the <a href="https://developer.github.com/v3/repos/contents/#get-contents">API documentation</a> for more information.
/// </remarks>
/// <param name="owner">The owner of the repository</param>
/// <param name="name">The name of the repository</param>
/// <param name="path">The content path</param>
/// <param name="reference">The name of the commit/branch/tag.</param>
[ManualRoute("GET", "repos/{owner}/{repo}/contents/{path}?ref={ref}")]
public Task<byte[]> 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<RepositoryContent>(url);
}

/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions Octokit/Helpers/AcceptHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public static class AcceptHeaders

public const string CommitReferenceSha1MediaType = "application/vnd.github.v3.sha";

/// <summary>
/// Support for retrieving raw file content with the <see cref="IConnection.GetRaw"/> method.
/// </summary>
/// <remarks>https://developer.github.com/v3/repos/contents/#custom-media-types</remarks>
public const string RawContentMediaType = "application/vnd.github.v3.raw";

public const string OrganizationPermissionsPreview = "application/vnd.github.ironman-preview+json";

/// <summary>
Expand Down
15 changes: 15 additions & 0 deletions Octokit/Http/ApiConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ public async Task<string> GetHtml(Uri uri, IDictionary<string, string> parameter
return response.Body;
}

/// <summary>
/// Gets the raw content of the API resource at the specified URI.
/// </summary>
/// <param name="uri">URI of the API resource to get</param>
/// <param name="parameters">Parameters to add to the API request</param>
/// <returns>The API resource's raw content or <c>null</c> if the <paramref name="uri"/> points to a directory.</returns>
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
public async Task<byte[]> GetRaw(Uri uri, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(uri, nameof(uri));

var response = await Connection.GetRaw(uri, parameters).ConfigureAwait(false);
return response.Body;
}

/// <summary>
/// Gets all API resources in the list at the specified URI.
/// </summary>
Expand Down
26 changes: 26 additions & 0 deletions Octokit/Http/Connection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,25 @@ public Task<IApiResponse<string>> GetHtml(Uri uri, IDictionary<string, string> p
});
}

/// <summary>
/// Performs an asynchronous HTTP GET request that expects a <seealso cref="IResponse"/> containing raw data.
/// </summary>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="parameters">Querystring parameters for the request</param>
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
/// <remarks>The <see cref="IResponse.Body"/> property will be <c>null</c> if the <paramref name="uri"/> points to a directory instead of a file</remarks>
public Task<IApiResponse<byte[]>> GetRaw(Uri uri, IDictionary<string, string> parameters)
{
Ensure.ArgumentNotNull(uri, nameof(uri));

return GetRaw(new Request
{
Method = HttpMethod.Get,
BaseAddress = BaseAddress,
Endpoint = uri.ApplyParameters(parameters)
});
}

public Task<IApiResponse<T>> Patch<T>(Uri uri, object body)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
Expand Down Expand Up @@ -620,6 +639,13 @@ async Task<IApiResponse<string>> GetHtml(IRequest request)
return new ApiResponse<string>(response, response.Body as string);
}

async Task<IApiResponse<byte[]>> GetRaw(IRequest request)
{
request.Headers.Add("Accept", AcceptHeaders.RawContentMediaType);
var response = await RunRequest(request, CancellationToken.None).ConfigureAwait(false);
return new ApiResponse<byte[]>(response, response.Body as byte[]);
}

async Task<IApiResponse<T>> Run<T>(IRequest request, CancellationToken cancellationToken)
{
_jsonPipeline.SerializeRequest(request);
Expand Down
1 change: 1 addition & 0 deletions Octokit/Http/HttpClientAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ protected virtual async Task<IResponse> 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"};
Expand Down
9 changes: 9 additions & 0 deletions Octokit/Http/IApiConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@ public interface IApiConnection
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
Task<string> GetHtml(Uri uri, IDictionary<string, string> parameters);

/// <summary>
/// Gets the raw content of the API resource at the specified URI.
/// </summary>
/// <param name="uri">URI of the API resource to get</param>
/// <param name="parameters">Parameters to add to the API request</param>
/// <returns>The API resource's raw content or <c>null</c> if the <paramref name="uri"/> points to a directory.</returns>
/// <exception cref="ApiException">Thrown when an API error occurs.</exception>
Task<byte[]> GetRaw(Uri uri, IDictionary<string, string> parameters);

/// <summary>
/// Gets all API resources in the list at the specified URI.
/// </summary>
Expand Down
9 changes: 9 additions & 0 deletions Octokit/Http/IConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ public interface IConnection : IApiInfoProvider
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
Task<IApiResponse<string>> GetHtml(Uri uri, IDictionary<string, string> parameters);

/// <summary>
/// Performs an asynchronous HTTP GET request that expects a <seealso cref="IResponse"/> containing raw data.
/// </summary>
/// <param name="uri">URI endpoint to send request to</param>
/// <param name="parameters">Querystring parameters for the request</param>
/// <returns><seealso cref="IResponse"/> representing the received HTTP response</returns>
/// <remarks>The <see cref="IResponse.Body"/> property will be <c>null</c> if the <paramref name="uri"/> points to a directory instead of a file</remarks>
Task<IApiResponse<byte[]>> GetRaw(Uri uri, IDictionary<string, string> parameters);

/// <summary>
/// Performs an asynchronous HTTP GET request.
/// Attempts to map the response to an object of type <typeparamref name="T"/>
Expand Down

0 comments on commit a1d690b

Please sign in to comment.