diff --git a/Oqtane.Client/Modules/Admin/Site/Index.razor b/Oqtane.Client/Modules/Admin/Site/Index.razor
index 1db3bc96..87ccba9b 100644
--- a/Oqtane.Client/Modules/Admin/Site/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Site/Index.razor
@@ -156,6 +156,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -432,6 +444,8 @@
private string _textEditor = "";
private string _imageFiles = string.Empty;
private string _uploadableFiles = string.Empty;
+ private int _maxChunkSizeMB = 1;
+ private int _maxConcurrentChunkUploads = 0;
private string _headcontent = string.Empty;
private string _bodycontent = string.Empty;
@@ -530,6 +544,8 @@
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
+ _maxChunkSizeMB = int.Parse(SettingService.GetSetting(settings, "MaxChunkSizeMB", "1"));
+ _maxConcurrentChunkUploads = int.Parse(SettingService.GetSetting(settings, "MaxConcurrentChunkUploads", "0"));
// page content
_headcontent = site.HeadContent;
@@ -736,6 +752,8 @@
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
+ settings = SettingService.SetSetting(settings, "MaxChunkSizeMB", _maxChunkSizeMB.ToString(), false);
+ settings = SettingService.SetSetting(settings, "MaxConcurrentChunkUploads", _maxConcurrentChunkUploads.ToString(), false);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
diff --git a/Oqtane.Client/Modules/Controls/FileManager.razor b/Oqtane.Client/Modules/Controls/FileManager.razor
index 509cef2d..f28559c0 100644
--- a/Oqtane.Client/Modules/Controls/FileManager.razor
+++ b/Oqtane.Client/Modules/Controls/FileManager.razor
@@ -121,6 +121,9 @@
private MessageType _messagetype;
private bool _uploading = false;
+ private int _maxChunkSizeMB = 1;
+ private int _maxConcurrentUploads = 0;
+
[Parameter]
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
@@ -173,6 +176,9 @@
_fileinputid = "FileInput_" + _guid;
_progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = "ProgressBar_" + _guid;
+
+ int.TryParse(SettingService.GetSetting(PageState.Site.Settings, "MaxChunkSizeMB", "1"), out _maxChunkSizeMB);
+ int.TryParse(SettingService.GetSetting(PageState.Site.Settings, "MaxConcurrentChunkUploads", "0"), out _maxConcurrentUploads);
}
protected override async Task OnParametersSetAsync()
@@ -383,7 +389,7 @@
StateHasChanged();
}
- await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt);
+ await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, _maxChunkSizeMB, _maxConcurrentUploads);
// uploading is asynchronous so we need to poll to determine if uploads are completed
var success = true;
diff --git a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx
index 670a4cba..f0718d8d 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Site/Index.resx
@@ -438,4 +438,16 @@
System
+
+ Enter the maximum size in MB of a chunk for file uploads
+
+
+ Maximum File Chunk Size (MB):
+
+
+ Enter the maximum concurrent number of file chunk uploads. If set to 0, no concurrency limit is applied
+
+
+ Maximum Concurrent File Chunk Uploads:
+
\ No newline at end of file
diff --git a/Oqtane.Client/UI/Interop.cs b/Oqtane.Client/UI/Interop.cs
index 8183aff5..50d52824 100644
--- a/Oqtane.Client/UI/Interop.cs
+++ b/Oqtane.Client/UI/Interop.cs
@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using System.Text.Json;
using System.Collections.Generic;
using System.Linq;
+using System;
namespace Oqtane.UI
{
@@ -208,13 +209,19 @@ namespace Oqtane.UI
}
}
+ [Obsolete("This function is deprecated. Use UploadFiles with MaxChunkSize and MaxConcurrentUploads parameters instead.", false)]
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt)
+ {
+ return UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1, 0);
+ }
+
+ public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int maxChunkSizeMB, int maxConcurrentUploads)
{
try
{
_jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.uploadFiles",
- posturl, folder, id, antiforgerytoken, jwt);
+ posturl, folder, id, antiforgerytoken, jwt, maxChunkSizeMB, maxConcurrentUploads);
return Task.CompletedTask;
}
catch
diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js
index ee81109c..61deeb49 100644
--- a/Oqtane.Server/wwwroot/js/interop.js
+++ b/Oqtane.Server/wwwroot/js/interop.js
@@ -308,7 +308,7 @@ Oqtane.Interop = {
}
return files;
},
- uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt) {
+ uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt, maxChunkSizeMB, maxConcurrentUploads) {
var fileinput = document.getElementById('FileInput_' + id);
var progressinfo = document.getElementById('ProgressInfo_' + id);
var progressbar = document.getElementById('ProgressBar_' + id);
@@ -326,10 +326,22 @@ Oqtane.Interop = {
totalSize = totalSize + files[i].size;
}
- var maxChunkSizeMB = 1;
+ maxChunkSizeMB = Math.ceil(maxChunkSizeMB);
+ if (maxChunkSizeMB < 1) {
+ maxChunkSizeMB = 1;
+ }
+ else if (maxChunkSizeMB > 50) {
+ maxChunkSizeMB = 50;
+ }
+
var bufferChunkSize = maxChunkSizeMB * (1024 * 1024);
var uploadedSize = 0;
+ maxConcurrentUploads = Math.ceil(maxConcurrentUploads);
+ var hasConcurrencyLimit = maxConcurrentUploads > 0;
+ var uploadQueue = [];
+ var activeUploads = 0;
+
for (var i = 0; i < files.length; i++) {
var fileChunk = [];
var file = files[i];
@@ -376,13 +388,23 @@ Oqtane.Interop = {
}
};
request.upload.onloadend = function (e) {
+ if (hasConcurrencyLimit) {
+ activeUploads--;
+ processUploads();
+ }
+
if (progressinfo !== null && progressbar !== null) {
uploadedSize = uploadedSize + e.total;
var percent = Math.ceil((uploadedSize / totalSize) * 100);
progressbar.value = (percent / 100);
}
};
- request.upload.onerror = function() {
+ request.upload.onerror = function () {
+ if (hasConcurrencyLimit) {
+ activeUploads--;
+ processUploads();
+ }
+
if (progressinfo !== null && progressbar !== null) {
if (files.length === 1) {
progressinfo.innerHTML = file.name + ' Error: ' + request.statusText;
@@ -392,13 +414,33 @@ Oqtane.Interop = {
}
}
};
- request.send(data);
+
+ if (hasConcurrencyLimit) {
+ uploadQueue.push({ data, request });
+ processUploads();
+ }
+ else {
+ request.send(data);
+ }
}
if (i === files.length - 1) {
fileinput.value = '';
}
}
+
+ function processUploads() {
+ if (uploadQueue.length === 0 || activeUploads >= maxConcurrentUploads) {
+ return;
+ }
+
+ while (activeUploads < maxConcurrentUploads && uploadQueue.length > 0) {
+ activeUploads++;
+
+ let { data, request } = uploadQueue.shift();
+ request.send(data);
+ }
+ }
},
refreshBrowser: function (verify, wait) {
async function attemptReload (verify) {