Working file uploader with chunking

This commit is contained in:
Pat Hartl 2023-02-13 00:30:10 -06:00
parent ce0b38d532
commit 0a2f5ee2a5
2 changed files with 38 additions and 40 deletions

View file

@ -1,9 +1,10 @@
@page "/Games/{id:guid}/Archives/Upload" @page "/Games/{id:guid}/Archives/Upload"
@using System.Net; @using System.Net;
@using System.Diagnostics;
@inject HttpClient HttpClient @inject HttpClient HttpClient
@inject NavigationManager Navigator @inject NavigationManager Navigator
<MudFileUpload T="IBrowserFile" FilesChanged="FileSelected"> <MudFileUpload T="IBrowserFile" OnFilesChanged="FileSelected">
<ButtonTemplate> <ButtonTemplate>
<MudButton HtmlTag="label" <MudButton HtmlTag="label"
Variant="Variant.Filled" Variant="Variant.Filled"
@ -19,38 +20,32 @@
<MudProgressLinear Color="Color.Primary" Striped="Uploading" Size="Size.Large" Value="Progress" /> <MudProgressLinear Color="Color.Primary" Striped="Uploading" Size="Size.Large" Value="Progress" />
<MudText>@ByteSizeLib.ByteSize.FromBytes(Speed)/s</MudText>
@code { @code {
[Parameter] public Guid Id { get; set; } [Parameter] public Guid Id { get; set; }
IBrowserFile File { get; set; } IBrowserFile File { get; set; }
const int ChunkSize = 1024 * 1024 * 1; const int ChunkSize = 1024 * 1024 * 10;
int Progress = 0; int Progress = 0;
bool Uploading = false; bool Uploading = false;
double Speed = 0;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
HttpClient.BaseAddress = new Uri(Navigator.BaseUri); HttpClient.BaseAddress = new Uri(Navigator.BaseUri);
} }
private void FileSelected(IBrowserFile file) private void FileSelected(InputFileChangeEventArgs args)
{ {
File = file; File = args.File;
} }
private async Task UploadArchive() private async Task UploadArchive()
{ {
var initResponse = await HttpClient.PostAsync("api/Upload/Init", null); var archiveId = Guid.NewGuid();
Guid objectKey = Guid.Empty;
if (initResponse.StatusCode == HttpStatusCode.OK)
{
var responseKey = await initResponse.Content.ReadAsStringAsync();
Guid.TryParse(responseKey, out objectKey);
}
long uploadedBytes = 0; long uploadedBytes = 0;
long totalBytes = File.Size; long totalBytes = File.Size;
@ -59,6 +54,10 @@
{ {
Uploading = true; Uploading = true;
var watch = new Stopwatch();
watch.Start();
while (Uploading) while (Uploading)
{ {
byte[] chunk; byte[] chunk;
@ -68,34 +67,30 @@
else else
chunk = new byte[ChunkSize]; chunk = new byte[ChunkSize];
await stream.ReadAsync(chunk, 0, chunk.Length); int bytesRead = 0;
using (var formFile = new MultipartFormDataContent()) // This feels hacky, why do we need to do this?
// Only 32256 bytes of the file get read unless we
// loop through like this. Probably kills performance.
while (bytesRead < chunk.Length) {
bytesRead += await stream.ReadAsync(chunk, bytesRead, chunk.Length - bytesRead);
}
using (FileStream fs = new FileStream(Path.Combine("Upload", archiveId.ToString()), FileMode.Append))
{ {
var content = new StreamContent(new MemoryStream(chunk)); await fs.WriteAsync(chunk);
}
formFile.Add(content, "File", File.Name);
formFile.Add(new StringContent(uploadedBytes.ToString()), "Start");
formFile.Add(new StringContent((uploadedBytes + chunk.Length).ToString()), "End");
formFile.Add(new StringContent(objectKey.ToString()), "Key");
formFile.Add(new StringContent(totalBytes.ToString()), "Total");
var response = await HttpClient.PostAsync("api/Upload/Chunk", formFile);
if (response.StatusCode == HttpStatusCode.OK) {
uploadedBytes += chunk.Length; uploadedBytes += chunk.Length;
Progress = (int)(uploadedBytes * 100 / totalBytes); Progress = (int)(uploadedBytes * 100 / totalBytes);
if (Progress >= 100) if (Progress >= 100)
Uploading = false; Uploading = false;
}
else Speed = chunk.Length * (1 / watch.Elapsed.TotalSeconds);
{
Uploading = false; watch.Restart();
// Error condition
}
}
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
} }

View file

@ -24,6 +24,9 @@ builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor().AddCircuitOptions(option => builder.Services.AddServerSideBlazor().AddCircuitOptions(option =>
{ {
option.DetailedErrors = true; option.DetailedErrors = true;
}).AddHubOptions(option =>
{
option.MaximumReceiveMessageSize = 1024 * 1024 * 11;
}); });
builder.WebHost.ConfigureKestrel(options => builder.WebHost.ConfigureKestrel(options =>