From ce0b38d53276c6f17087e64d67fcf2ef3907d903 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Sun, 12 Feb 2023 17:33:17 -0600 Subject: [PATCH] Started re-implementing file uploader --- .../Controllers/Api/UploadController.cs | 51 +++++++++ .../Pages/Games/Archives/Upload.razor | 104 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 LANCommander/Controllers/Api/UploadController.cs create mode 100644 LANCommander/Pages/Games/Archives/Upload.razor diff --git a/LANCommander/Controllers/Api/UploadController.cs b/LANCommander/Controllers/Api/UploadController.cs new file mode 100644 index 0000000..2469a97 --- /dev/null +++ b/LANCommander/Controllers/Api/UploadController.cs @@ -0,0 +1,51 @@ +using LANCommander.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace LANCommander.Controllers.Api +{ + [Route("api/[controller]")] + [ApiController] + public class UploadController : ControllerBase + { + private const string UploadDirectory = "Upload"; + + [HttpPost("Init")] + public string Init() + { + var key = Guid.NewGuid().ToString(); + + if (!Directory.Exists(UploadDirectory)) + Directory.CreateDirectory(UploadDirectory); + + if (!System.IO.File.Exists(Path.Combine(UploadDirectory, key))) + System.IO.File.Create(Path.Combine(UploadDirectory, key)).Close(); + + return key; + } + + [HttpPost("Chunk")] + public async Task Chunk([FromForm] ChunkUpload chunk) + { + var filePath = Path.Combine(UploadDirectory, chunk.Key.ToString()); + + if (!System.IO.File.Exists(filePath)) + throw new Exception("Destination file not initialized."); + + Request.EnableBuffering(); + + using (var ms = new MemoryStream()) + { + await chunk.File.CopyToAsync(ms); + + var data = ms.ToArray(); + + using (var fs = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None)) + { + fs.Position = chunk.Start; + fs.Write(data, 0, data.Length); + } + } + } + } +} diff --git a/LANCommander/Pages/Games/Archives/Upload.razor b/LANCommander/Pages/Games/Archives/Upload.razor new file mode 100644 index 0000000..e4f482d --- /dev/null +++ b/LANCommander/Pages/Games/Archives/Upload.razor @@ -0,0 +1,104 @@ +@page "/Games/{id:guid}/Archives/Upload" +@using System.Net; +@inject HttpClient HttpClient +@inject NavigationManager Navigator + + + + + Upload Archive + + + + +Upload + + + +@code { + [Parameter] public Guid Id { get; set; } + + IBrowserFile File { get; set; } + + const int ChunkSize = 1024 * 1024 * 1; + + int Progress = 0; + bool Uploading = false; + + protected override async Task OnInitializedAsync() + { + HttpClient.BaseAddress = new Uri(Navigator.BaseUri); + } + + private void FileSelected(IBrowserFile file) + { + File = file; + } + + private async Task UploadArchive() + { + var initResponse = await HttpClient.PostAsync("api/Upload/Init", null); + + Guid objectKey = Guid.Empty; + + if (initResponse.StatusCode == HttpStatusCode.OK) + { + var responseKey = await initResponse.Content.ReadAsStringAsync(); + + Guid.TryParse(responseKey, out objectKey); + } + + long uploadedBytes = 0; + long totalBytes = File.Size; + + using (var stream = File.OpenReadStream(long.MaxValue)) + { + Uploading = true; + + while (Uploading) + { + byte[] chunk; + + if (totalBytes - uploadedBytes < ChunkSize) + chunk = new byte[totalBytes - uploadedBytes]; + else + chunk = new byte[ChunkSize]; + + await stream.ReadAsync(chunk, 0, chunk.Length); + + using (var formFile = new MultipartFormDataContent()) + { + var content = new StreamContent(new MemoryStream(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; + + Progress = (int)(uploadedBytes * 100 / totalBytes); + + if (Progress >= 100) + Uploading = false; + } + else + { + Uploading = false; + // Error condition + } + } + + await InvokeAsync(StateHasChanged); + } + } + } +}