fix #4598 - user experience improvements for file upload

This commit is contained in:
sbwalker 2024-09-12 14:04:35 -04:00
parent 044cee30a5
commit 69bc06685f
3 changed files with 49 additions and 31 deletions

View File

@ -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

View File

@ -425,11 +425,11 @@ namespace Oqtane.Controllers
// POST api/<controller>/upload
[EnableCors(Constants.MauiCorsPolicy)]
[HttpPost("upload")]
public async Task UploadFile(string folder, IFormFile formfile)
public async Task<IActionResult> 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<string> MergeFile(string folder, string filename)

View File

@ -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);