Skip to content

Commit

Permalink
Add GetJar method with progress reporting capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
tekgator committed Dec 2, 2022
1 parent 0863443 commit 0a425a0
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 36 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
## [Unreleased]


## [1.0.1] - 2022-12-02
### Added
- New GetJar Method with possibility to pass IProgress for progress reporting

### Changed
- GetJar Method downloads header first to verify status code, then downloads rest

### Removed
- Possibility to pass own HTTP client


## [1.0.0] - 2022-11-30
### Added
- Create Nuget and publish on nuget.org
Expand Down
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,25 @@ Console.WriteLine(JsonSerializer.Serialize(latestDetails, jsonOptions));
var allDetails = await serverJar.GetAllDetails("servers", "spigot", 5u);
Console.WriteLine(JsonSerializer.Serialize(allDetails, jsonOptions));

// GetJar
using (var stream = await serverJar.GetJar("servers", "spigot"))
// GetJar Method 1 (including progress)
SetConsoleColor(ConsoleColor.White, ConsoleColor.Red);
Console.WriteLine("\nAPI call - GetJar with method 1 (with progress):\n");
ResetConsoleColor();

using var fileStream1 = File.Create("./server1.jar");
Progress<ProgressEventArgs> progress = new();
progress.ProgressChanged += (_, progress) => Console.Write($"\rProgress: {progress.ProgressPercentage}% ({progress.BytesTransferred / 1024 / 1024}MB / {progress.TotalBytes / 1024 / 1024}MB) ");
await serverJar.GetJar(fileStream1, "servers", "spigot", progress: progress);

// GetJar Method 2
SetConsoleColor(ConsoleColor.White, ConsoleColor.Red);
Console.WriteLine("\nAPI call - GetJar method 2:\n");
ResetConsoleColor();

using (var stream = await serverJar.GetJar("servers", "spigot", "1.19.1"))
{
stream.Seek(0, SeekOrigin.Begin);
Console.WriteLine($"Downloaded {stream.Length / 1024 / 1024} MB.");
using var fileStream = File.Create("./server.jar");
stream.CopyTo(fileStream);
using var fileStream2 = File.Create("./server2.jar");
await stream.CopyToAsync(fileStream2);
}
```

Expand Down
24 changes: 17 additions & 7 deletions ServerJars.Demo.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// See https://aka.ms/new-console-template for more information
using ServerJarsAPI;
using ServerJarsAPI.Events;
using System.Text.Json;

var jsonOptions = new JsonSerializerOptions
Expand Down Expand Up @@ -47,17 +48,26 @@
Console.WriteLine(JsonSerializer.Serialize(allDetails, jsonOptions));


// GetJar
// GetJar Method 1 (including progress)
SetConsoleColor(ConsoleColor.White, ConsoleColor.Red);
Console.WriteLine("\nAPI call - GetJar:\n");
Console.WriteLine("\nAPI call - GetJar with method 1 (with progress):\n");
ResetConsoleColor();

using (var stream = await serverJar.GetJar("servers", "spigot"))
using var fileStream1 = File.Create("./server1.jar");
Progress<ProgressEventArgs> progress = new();
progress.ProgressChanged += (_, progress) => Console.Write($"\rProgress: {progress.ProgressPercentage}% ({progress.BytesTransferred / 1024 / 1024}MB / {progress.TotalBytes / 1024 / 1024}MB) ");
await serverJar.GetJar(fileStream1, "servers", "spigot", progress: progress);


// GetJar Method 2
SetConsoleColor(ConsoleColor.White, ConsoleColor.Red);
Console.WriteLine("\n\nAPI call - GetJar method 2:\n");
ResetConsoleColor();

using (var stream = await serverJar.GetJar("servers", "spigot", "1.19.1"))
{
stream.Seek(0, SeekOrigin.Begin);
Console.WriteLine($"Downloaded {stream.Length / 1024 / 1024} MB.");
using var fileStream = File.Create("./server.jar");
stream.CopyTo(fileStream);
using var fileStream2 = File.Create("./server2.jar");
await stream.CopyToAsync(fileStream2);
}


Expand Down
55 changes: 54 additions & 1 deletion ServerJars.Tests/ServerJarsTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using ServerJarsAPI.Events;
using System.Text.Json;

namespace ServerJarsAPI.Tests;
Expand Down Expand Up @@ -85,7 +86,12 @@ public void GetAllDetails_InvalidTypeCategory(string type, string category)
public async Task GetJar_Success(string type, string category, string version)
{
using var stream = await _serverJars.GetJar(type, category, version);
Assert.That(stream.Length, Is.GreaterThan(0));
Assert.That(stream.CanRead, Is.True);

using var memStream = new MemoryStream();
await stream.CopyToAsync(memStream);

Assert.That(memStream.Length, Is.GreaterThan(0));
}

[TestCase("abc", "spigot", "")]
Expand All @@ -95,4 +101,51 @@ public void GetJar_InvalidTypeCategoryVersion(string type, string category, stri
{
Assert.ThrowsAsync<HttpRequestException>(async () => await _serverJars.GetJar(type, category, version));
}


[TestCase("servers", "spigot", "")]
[TestCase("servers", "spigot", "1.19.1")]
public async Task GetJarWithProgress_Success(string type, string category, string version)
{
bool progressChanged = false;
using var memStream = new MemoryStream();
Progress<ProgressEventArgs> progress = new();
progress.ProgressChanged += (_, progress) =>
{
Assert.Multiple(() =>
{
Assert.That(progress.ProgressPercentage, Is.AtLeast(0));
Assert.That(progress.ProgressPercentage, Is.AtMost(100));
});
progressChanged = true;
};

await _serverJars.GetJar(memStream, type, category, version, progress);
Assert.That(progressChanged, Is.True);
}

[TestCase("abc", "spigot", "")]
[TestCase("servers", "abc", "")]
[TestCase("servers", "spigot", "1.19.110")]
public void GetJarWithProgress_InvalidTypeCategoryVersion(string type, string category, string version)
{
bool progressChanged = false;
using var memStream = new MemoryStream();
Progress<ProgressEventArgs> progress = new();
progress.ProgressChanged += (_, progress) =>
{
Assert.Multiple(() =>
{
Assert.That(progress.ProgressPercentage, Is.AtLeast(0));
Assert.That(progress.ProgressPercentage, Is.AtMost(100));
});
progressChanged = true;
};

Assert.Multiple(() =>
{
Assert.ThrowsAsync<HttpRequestException>(async () => await _serverJars.GetJar(memStream, type, category, version, progress));
Assert.That(progressChanged, Is.True);
});
}
}
76 changes: 61 additions & 15 deletions ServerJars/ClientApi.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
using ServerJarsAPI.Converter;
using ServerJarsAPI.Events;
using ServerJarsAPI.Models;
using System.Net.Http.Handlers;
using System.Net.Http.Json;
using System.Text.Json;

namespace ServerJarsAPI;

public abstract class ClientApi : IDisposable
{
protected readonly HttpClient _httpClient;
private readonly bool _disposeClient = true;
readonly JsonSerializerOptions _jsonOptions = new();
private readonly HttpClient _httpClient;
private readonly ProgressMessageHandler _progressMessageHandler;

protected ClientApi(string baseUri, HttpClient httpClient, bool disposeClient = false)
private readonly JsonSerializerOptions _jsonOptions = new();

protected ClientApi(string baseUri)
{
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri(baseUri);
_disposeClient = disposeClient;
_progressMessageHandler = new ProgressMessageHandler(new HttpClientHandler()
{
AllowAutoRedirect = true
});

_httpClient = new HttpClient(_progressMessageHandler)
{
BaseAddress = new Uri(baseUri)
};

_jsonOptions.Converters.Add(new UnixEpochDateTimeConverter());
}

protected async Task<T> GetAsync<T>(string uri, CancellationToken cancellationToken = default)
protected async Task<T> GetAsync<T>(
string uri,
CancellationToken cancellationToken = default)
{
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
requestMessage.Headers.Add("Accept", "application/json");
Expand All @@ -43,12 +54,14 @@ protected async Task<T> GetAsync<T>(string uri, CancellationToken cancellationTo

}

protected async Task<Stream> StreamAsync(string uri, CancellationToken cancellationToken = default)
protected async Task<Stream> StreamAsync(
string uri,
CancellationToken cancellationToken = default)
{
using var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
requestMessage.Headers.Add("Accept", "application/json");

var httpResponse = await _httpClient.SendAsync(requestMessage, cancellationToken);
var httpResponse = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);

if (httpResponse.IsSuccessStatusCode)
{
Expand All @@ -59,7 +72,42 @@ protected async Task<Stream> StreamAsync(string uri, CancellationToken cancellat
throw new HttpRequestException($"{httpResponse.ReasonPhrase}: {errorMsg}", null, httpResponse.StatusCode);
}

private static async Task<string> GetErrorRepsonse(HttpResponseMessage response, CancellationToken cancellationToken = default)
protected async Task DownloadAsync(
Stream stream,
string uri,
IProgress<ProgressEventArgs>? progress,
CancellationToken cancellationToken = default)
{
void processEventHandler(object? _, HttpProgressEventArgs args)
{
progress?.Report(new ProgressEventArgs(args.ProgressPercentage, args.BytesTransferred, args.TotalBytes));
}

_progressMessageHandler.HttpReceiveProgress += processEventHandler;
progress?.Report(new ProgressEventArgs(0, 0));

using var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri);
requestMessage.Headers.Add("Accept", "application/json");

using var httpResponse = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);

if (!httpResponse.IsSuccessStatusCode)
{
string errorMsg = await GetErrorRepsonse(httpResponse, cancellationToken);
throw new HttpRequestException($"{httpResponse.ReasonPhrase}: {errorMsg}", null, httpResponse.StatusCode);
}

progress?.Report(new ProgressEventArgs(0, 0, httpResponse.Content.Headers.ContentLength));

using var httpStream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken);
await httpStream.CopyToAsync(stream, cancellationToken);

_progressMessageHandler.HttpReceiveProgress -= processEventHandler;
}

private static async Task<string> GetErrorRepsonse(
HttpResponseMessage response,
CancellationToken cancellationToken = default)
{
string errorMsg = string.Empty;
try
Expand All @@ -82,10 +130,8 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_disposeClient)
{
_httpClient?.Dispose();
}
_progressMessageHandler?.Dispose();
_httpClient?.Dispose();
}
}
}
14 changes: 14 additions & 0 deletions ServerJars/Events/ProgressEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System.Net.Http.Handlers;

namespace ServerJarsAPI.Events;

public class ProgressEventArgs : HttpProgressEventArgs
{
public ProgressEventArgs(
int progressPercentage,
long bytesTransferred,
long? totalBytes = null) : base(progressPercentage, string.Empty, bytesTransferred, totalBytes)
{

}
}
24 changes: 17 additions & 7 deletions ServerJars/ServerJars.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ServerJarsAPI.Models;
using ServerJarsAPI.Events;
using ServerJarsAPI.Models;

namespace ServerJarsAPI;

Expand All @@ -8,13 +9,10 @@ namespace ServerJarsAPI;
/// </summary>
public class ServerJars : ClientApi
{
public ServerJars() :
this(new HttpClient(), true)
{ }
public ServerJars() : base("https://serverjars.com/api/")
{

public ServerJars(HttpClient httpClient, bool disposeClient = false) :
base("https://serverjars.com/api/", httpClient, disposeClient)
{ }
}

public Task<JarTypes> GetTypes(
string type = "",
Expand Down Expand Up @@ -58,4 +56,16 @@ public Task<Stream> GetJar(
{
return StreamAsync($"fetchJar/{type}/{category}/{version}", cancellationToken);
}

public Task GetJar(
Stream stream,
string type,
string category,
string version = "",
IProgress<ProgressEventArgs>? progress = null,
CancellationToken cancellationToken = default)
{
return DownloadAsync(stream, $"fetchJar/{type}/{category}/{version}", progress, cancellationToken);
}

}

0 comments on commit 0a425a0

Please sign in to comment.