diff --git a/Oqtane.Client/Modules/Admin/Files/Index.razor b/Oqtane.Client/Modules/Admin/Files/Index.razor
index f8a4ebf1..eb07e32d 100644
--- a/Oqtane.Client/Modules/Admin/Files/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Files/Index.razor
@@ -17,7 +17,7 @@ else
|
- @context |
+ @context |
|
@@ -29,10 +29,12 @@ else
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Admin; } }
List Files;
+ Uri uri;
protected override async Task OnParametersSetAsync()
{
Files = await FileService.GetFilesAsync(PageState.Site.SiteRootPath);
+ uri = new Uri(NavigationManager.Uri);
}
private async Task DeleteFile(string filename)
diff --git a/Oqtane.Client/Shared/ModuleInstance.razor b/Oqtane.Client/Shared/ModuleInstance.razor
index c980a546..69d40111 100644
--- a/Oqtane.Client/Shared/ModuleInstance.razor
+++ b/Oqtane.Client/Shared/ModuleInstance.razor
@@ -56,6 +56,7 @@
public void AddModuleMessage(string message, MessageType type)
{
progressindicator = false;
+ StateHasChanged();
modulemessage.SetModuleMessage(message, type);
}
diff --git a/Oqtane.Server/Controllers/FileController.cs b/Oqtane.Server/Controllers/FileController.cs
index 36e27102..3ce2f97a 100644
--- a/Oqtane.Server/Controllers/FileController.cs
+++ b/Oqtane.Server/Controllers/FileController.cs
@@ -6,6 +6,7 @@ using Oqtane.Shared;
using System;
using System.Collections.Generic;
using System.IO;
+using System.Threading;
using System.Threading.Tasks;
namespace Oqtane.Controllers
@@ -67,11 +68,11 @@ namespace Oqtane.Controllers
string[] fileparts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts
// if all of the file parts exist ( note that file parts can arrive out of order )
- if (fileparts.Length == totalparts)
+ if (fileparts.Length == totalparts && CanAccessFiles(fileparts))
{
// merge file parts
bool success = true;
- using (var stream = new FileStream(Path.Combine(folder, filename), FileMode.Create))
+ using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
{
foreach (string filepart in fileparts)
{
@@ -89,7 +90,7 @@ namespace Oqtane.Controllers
}
}
- // delete file parts
+ // delete file parts and rename file
if (success)
{
foreach (string filepart in fileparts)
@@ -100,13 +101,17 @@ namespace Oqtane.Controllers
// check for allowable file extensions
if (!WhiteList.Contains(Path.GetExtension(filename).Replace(".", "")))
{
- System.IO.File.Delete(Path.Combine(folder, filename));
- success = false;
+ System.IO.File.Delete(Path.Combine(folder, filename + ".tmp"));
+ }
+ else
+ {
+ // rename file now that the entire process is completed
+ System.IO.File.Move(Path.Combine(folder, filename + ".tmp"), Path.Combine(folder, filename));
}
}
}
- // clean up file parts which are more than 2 hours old ( which can happen if a file upload failed )
+ // clean up file parts which are more than 2 hours old ( which can happen if a prior file upload failed )
fileparts = Directory.GetFiles(folder, "*" + token + "*");
foreach (string filepart in fileparts)
{
@@ -118,6 +123,43 @@ namespace Oqtane.Controllers
}
}
+ private bool CanAccessFiles(string[] files)
+ {
+ // ensure files are not locked by another process ( ie. still being written to )
+ bool canaccess = true;
+ FileStream stream = null;
+ foreach (string file in files)
+ {
+ int attempts = 0;
+ bool locked = true;
+ while (attempts < 5 && locked == true)
+ {
+ try
+ {
+ stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.None);
+ locked = false;
+ }
+ catch // file is locked by another process
+ {
+ Thread.Sleep(1000); // wait 1 second
+ }
+ finally
+ {
+ if (stream != null)
+ {
+ stream.Close();
+ }
+ }
+ attempts += 1;
+ }
+ if (locked && canaccess)
+ {
+ canaccess = false;
+ }
+ }
+ return canaccess;
+ }
+
// DELETE api//?folder=x&file=y
[HttpDelete]
[Authorize(Roles = Constants.AdminRole)]