From 69bc06685fe0df5c7dc5f442070280eee7932673 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 12 Sep 2024 14:04:35 -0400 Subject: [PATCH] fix #4598 - user experience improvements for file upload --- .../Modules/Controls/FileManager.razor | 2 +- Oqtane.Server/Controllers/FileController.cs | 8 ++- Oqtane.Server/wwwroot/js/interop.js | 70 ++++++++++++------- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor index b7c603ff..f75d8f29 100644 --- a/Oqtane.Client/Modules/Controls/FileManager.razor +++ b/Oqtane.Client/Modules/Controls/FileManager.razor @@ -387,7 +387,7 @@ var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes var megabits = (size / 1048576.0) * 8; // binary conversion - var uploadspeed = 2; // 2 Mbps (3G ranges from 300Kbps to 3Mbps) + var uploadspeed = (PageState.Alias.Name.Contains("localhost")) ? 100 : 3; // 3 Mbps is FCC minimum for broadband upload var uploadtime = (megabits / uploadspeed); // seconds var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds) var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs index 030005f5..5aa8a66e 100644 --- a/Oqtane.Server/Controllers/FileController.cs +++ b/Oqtane.Server/Controllers/FileController.cs @@ -425,11 +425,11 @@ namespace Oqtane.Controllers // POST api//upload [EnableCors(Constants.MauiCorsPolicy)] [HttpPost("upload")] - public async Task UploadFile(string folder, IFormFile formfile) + public async Task UploadFile(string folder, IFormFile formfile) { if (formfile == null || formfile.Length <= 0) { - return; + return NoContent(); } // ensure filename is valid @@ -437,7 +437,7 @@ namespace Oqtane.Controllers if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token) || !HasValidFileExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token)))) { _logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName); - return; + return NoContent(); } string folderPath = ""; @@ -492,6 +492,8 @@ namespace Oqtane.Controllers _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; } + + return NoContent(); } private async Task MergeFile(string folder, string filename) diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index b457ad1d..07a761b8 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -293,41 +293,49 @@ Oqtane.Interop = { }, uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt) { var fileinput = document.getElementById('FileInput_' + id); - var files = fileinput.files; var progressinfo = document.getElementById('ProgressInfo_' + id); var progressbar = document.getElementById('ProgressBar_' + id); if (progressinfo !== null && progressbar !== null) { progressinfo.setAttribute("style", "display: inline;"); + progressinfo.innerHTML = ''; progressbar.setAttribute("style", "width: 100%; display: inline;"); + progressbar.value = 0; } + var files = fileinput.files; + var totalSize = 0; for (var i = 0; i < files.length; i++) { - var FileChunk = []; - var file = files[i]; - var MaxFileSizeMB = 1; - var BufferChunkSize = MaxFileSizeMB * (1024 * 1024); - var FileStreamPos = 0; - var EndPos = BufferChunkSize; - var Size = file.size; + totalSize = totalSize + files[i].size; + } - while (FileStreamPos < Size) { - FileChunk.push(file.slice(FileStreamPos, EndPos)); - FileStreamPos = EndPos; - EndPos = FileStreamPos + BufferChunkSize; + var maxChunkSizeMB = 1; + var bufferChunkSize = maxChunkSizeMB * (1024 * 1024); + var uploadedSize = 0; + + for (var i = 0; i < files.length; i++) { + var fileChunk = []; + var file = files[i]; + var fileStreamPos = 0; + var endPos = bufferChunkSize; + + while (fileStreamPos < file.size) { + fileChunk.push(file.slice(fileStreamPos, endPos)); + fileStreamPos = endPos; + endPos = fileStreamPos + bufferChunkSize; } - var TotalParts = FileChunk.length; - var PartCount = 0; + var totalParts = fileChunk.length; + var partCount = 0; - while (Chunk = FileChunk.shift()) { - PartCount++; - var FileName = file.name + ".part_" + PartCount.toString().padStart(3, '0') + "_" + TotalParts.toString().padStart(3, '0'); + while (chunk = fileChunk.shift()) { + partCount++; + var fileName = file.name + ".part_" + partCount.toString().padStart(3, '0') + "_" + totalParts.toString().padStart(3, '0'); var data = new FormData(); data.append('__RequestVerificationToken', antiforgerytoken); data.append('folder', folder); - data.append('formfile', Chunk, FileName); + data.append('formfile', chunk, fileName); var request = new XMLHttpRequest(); request.open('POST', posturl, true); if (jwt !== "") { @@ -335,28 +343,36 @@ Oqtane.Interop = { request.withCredentials = true; } request.upload.onloadstart = function (e) { - if (progressinfo !== null && progressbar !== null) { - progressinfo.innerHTML = file.name + ' 0%'; - progressbar.value = 0; + if (progressinfo !== null && progressbar !== null && progressinfo.innerHTML === '') { + if (files.length === 1) { + progressinfo.innerHTML = file.name; + } + else { + progressinfo.innerHTML = file.name + ", ..."; + } } }; request.upload.onprogress = function (e) { if (progressinfo !== null && progressbar !== null) { - var percent = Math.ceil((e.loaded / e.total) * 100); - progressinfo.innerHTML = file.name + '[' + PartCount + '] ' + percent + '%'; + var percent = Math.ceil(((uploadedSize + e.loaded) / totalSize) * 100); progressbar.value = (percent / 100); } }; request.upload.onloadend = function (e) { if (progressinfo !== null && progressbar !== null) { - progressinfo.innerHTML = file.name + ' 100%'; - progressbar.value = 1; + uploadedSize = uploadedSize + e.total; + var percent = Math.ceil((uploadedSize / totalSize) * 100); + progressbar.value = (percent / 100); } }; request.upload.onerror = function() { if (progressinfo !== null && progressbar !== null) { - progressinfo.innerHTML = file.name + ' Error: ' + request.statusText; - progressbar.value = 0; + if (files.length === 1) { + progressinfo.innerHTML = file.name + ' Error: ' + request.statusText; + } + else { + progressinfo.innerHTML = ' Error: ' + request.statusText; + } } }; request.send(data);