Fixed race condition between calculating size of source directory and performing a move operation with the files/move endpoint. Tests added.
This commit is contained in:
Родитель
ead9b21658
Коммит
2c8b828187
|
@ -127,7 +127,12 @@ namespace Microsoft.IIS.Administration.Files
|
|||
|
||||
public long Size {
|
||||
get {
|
||||
return _info.Length;
|
||||
try {
|
||||
return _info.Length;
|
||||
}
|
||||
catch (FileNotFoundException) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -154,21 +154,19 @@ namespace Microsoft.IIS.Administration.Files
|
|||
|
||||
private MoveOperation MoveDirectory(IFileInfo source, IFileInfo destination, bool copy)
|
||||
{
|
||||
Task t;
|
||||
MoveOperation op = null;
|
||||
var op = new MoveOperation(source, destination, null, _fileService);
|
||||
|
||||
if (copy) {
|
||||
t = CopyDirectory(source, destination, (s, d) => SafeMoveFile(s, d, PathUtil.GetTempFilePath(d.Path), true).ContinueWith(t2 => {
|
||||
op.Task = CopyDirectory(source, destination, (s, d) => SafeMoveFile(s, d, PathUtil.GetTempFilePath(d.Path), true).ContinueWith(t2 => {
|
||||
if (op != null) {
|
||||
op.CurrentSize += _fileService.GetFile(d.Path).Size;
|
||||
}
|
||||
}));
|
||||
}
|
||||
else {
|
||||
t = Task.Run(() => _fileService.Move(source, destination));
|
||||
op.Task = Task.Run(() => _fileService.Move(source, destination));
|
||||
}
|
||||
|
||||
op = new MoveOperation(t, source, destination, null, _fileService);
|
||||
return op;
|
||||
}
|
||||
|
||||
|
@ -176,9 +174,11 @@ namespace Microsoft.IIS.Administration.Files
|
|||
{
|
||||
string temp = PathUtil.GetTempFilePath(destination.Path);
|
||||
|
||||
Task t = SafeMoveFile(source, destination, temp, copy);
|
||||
var op = new MoveOperation(source, destination, temp, _fileService);
|
||||
|
||||
return new MoveOperation(t, source, destination, temp, _fileService);
|
||||
op.Task = SafeMoveFile(source, destination, temp, copy);
|
||||
|
||||
return op;
|
||||
}
|
||||
|
||||
private async Task SafeMoveFile(IFileInfo source, IFileInfo destination, string temp, bool copy)
|
||||
|
|
|
@ -13,11 +13,10 @@ namespace Microsoft.IIS.Administration.Files
|
|||
|
||||
class MoveOperation
|
||||
{
|
||||
private long _totalSize = -1;
|
||||
private long _currentSize = -1;
|
||||
private IFileProvider _fileProvider;
|
||||
|
||||
public MoveOperation(Task task, IFileInfo source, IFileInfo destination, string tempPath, IFileProvider fileProvider)
|
||||
public MoveOperation(IFileInfo source, IFileInfo destination, string tempPath, IFileProvider fileProvider)
|
||||
{
|
||||
var bytes = new byte[32];
|
||||
using (var rng = RandomNumberGenerator.Create()) {
|
||||
|
@ -25,38 +24,22 @@ namespace Microsoft.IIS.Administration.Files
|
|||
this.Id = Base64.Encode(bytes);
|
||||
}
|
||||
|
||||
this.Task = task;
|
||||
this.Source = source;
|
||||
this.Destination = destination;
|
||||
this.Created = DateTime.UtcNow;
|
||||
this.TempPath = tempPath;
|
||||
_fileProvider = fileProvider;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public string Id { get; private set; }
|
||||
public Task Task { get; private set; }
|
||||
public Task Task { get; set; }
|
||||
public FileType Type { get; private set; }
|
||||
public string TempPath { get; private set; }
|
||||
public DateTime Created { get; private set; }
|
||||
public IFileInfo Destination { get; private set; }
|
||||
public IFileInfo Source { get; private set; }
|
||||
|
||||
public long TotalSize {
|
||||
get {
|
||||
if (_totalSize != -1) {
|
||||
return _totalSize;
|
||||
}
|
||||
|
||||
if (Source.Type == FileType.File) {
|
||||
_totalSize = Source.Exists ? Source.Size : 0;
|
||||
}
|
||||
else {
|
||||
_totalSize = Source.Exists ? _fileProvider.GetFiles(Source, "*", SearchOption.AllDirectories).Aggregate(0L, (prev, f) => prev + (f.Exists ? f.Size : 0)) : 0;
|
||||
}
|
||||
|
||||
return _totalSize;
|
||||
}
|
||||
}
|
||||
public long TotalSize { get; private set; }
|
||||
|
||||
public long CurrentSize {
|
||||
get {
|
||||
|
@ -77,5 +60,20 @@ namespace Microsoft.IIS.Administration.Files
|
|||
_currentSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (Source.Type == FileType.File) {
|
||||
TotalSize = Source.Exists ? Source.Size : 0;
|
||||
}
|
||||
else {
|
||||
try {
|
||||
TotalSize = Source.Exists ? _fileProvider.GetFiles(Source, "*", SearchOption.AllDirectories).Aggregate(0L, (prev, f) => prev + f.Size) : 0;
|
||||
}
|
||||
catch (DirectoryNotFoundException) {
|
||||
TotalSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,8 @@ namespace Microsoft.IIS.Administration.Files
|
|||
hal.ProvideLink(Defines.CopyResource.Guid, "self", copy => new { href = MoveHelper.GetLocation(copy.id, true) });
|
||||
|
||||
hal.ProvideLink(Defines.FilesResource.Guid, Defines.CopyResource.Name, file => new { href = $"/{Defines.COPY_PATH}" });
|
||||
|
||||
hal.ProvideLink(Defines.DirectoriesResource.Guid, Defines.CopyResource.Name, dir => new { href = $"/{Defines.COPY_PATH}" });
|
||||
}
|
||||
|
||||
private void ConfigureMove()
|
||||
|
@ -104,6 +106,8 @@ namespace Microsoft.IIS.Administration.Files
|
|||
hal.ProvideLink(Defines.MoveResource.Guid, "self", move => new { href = MoveHelper.GetLocation(move.id, false) });
|
||||
|
||||
hal.ProvideLink(Defines.FilesResource.Guid, Defines.MoveResource.Name, file => new { href = $"/{Defines.MOVE_PATH}" });
|
||||
|
||||
hal.ProvideLink(Defines.DirectoriesResource.Guid, Defines.MoveResource.Name, dir => new { href = $"/{Defines.MOVE_PATH}" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace Microsoft.IIS.Administration.Tests
|
|||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Web.Administration;
|
||||
using Xunit;
|
||||
|
@ -324,6 +325,108 @@ namespace Microsoft.IIS.Administration.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveDirectory()
|
||||
{
|
||||
string startName = "move_dir_test";
|
||||
string destName = "move_dir_dest";
|
||||
var physicalPath = Path.Combine(Configuration.TEST_ROOT_PATH, startName);
|
||||
var destPhysicalPath = Path.Combine(Configuration.TEST_ROOT_PATH, destName);
|
||||
|
||||
CreateTestDirectory(physicalPath);
|
||||
|
||||
JObject site = null;
|
||||
using (HttpClient client = ApiHttpClient.Create()) {
|
||||
Sites.EnsureNoSite(client, FILE_TEST_SITE_NAME);
|
||||
site = Sites.CreateSite(client, FILE_TEST_SITE_NAME, Utils.GetAvailablePort(), physicalPath);
|
||||
|
||||
try {
|
||||
var rootDir = Utils.FollowLink(client, site, "files");
|
||||
var rootDirFileInfo = Utils.FollowLink(client, rootDir.Value<JObject>("file_info"), "self");
|
||||
|
||||
object move = new {
|
||||
name = destName,
|
||||
parent = rootDirFileInfo.Value<JObject>("parent"),
|
||||
file = rootDirFileInfo
|
||||
};
|
||||
|
||||
var moveInfo = client.Post(Utils.GetLink(rootDirFileInfo, "move"), move);
|
||||
|
||||
// Wait for move to finish
|
||||
HttpResponseMessage res = null;
|
||||
while (res == null || res.StatusCode == HttpStatusCode.OK) {
|
||||
res = client.GetAsync(Utils.Self(moveInfo)).Result;
|
||||
Thread.Sleep(25);
|
||||
}
|
||||
|
||||
Assert.True(!Directory.Exists(physicalPath));
|
||||
Assert.True(VerifyTestDirectory(destPhysicalPath));
|
||||
}
|
||||
finally {
|
||||
if (site != null) {
|
||||
Sites.DeleteSite(client, Utils.Self(site));
|
||||
}
|
||||
|
||||
if (Directory.Exists(destPhysicalPath)) {
|
||||
Directory.Delete(destPhysicalPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyDirectory()
|
||||
{
|
||||
string startName = "copy_dir_test";
|
||||
string destName = "copy_dir_dest";
|
||||
var physicalPath = Path.Combine(Configuration.TEST_ROOT_PATH, startName);
|
||||
var destPhysicalPath = Path.Combine(Configuration.TEST_ROOT_PATH, destName);
|
||||
|
||||
CreateTestDirectory(physicalPath);
|
||||
|
||||
JObject site = null;
|
||||
using (HttpClient client = ApiHttpClient.Create()) {
|
||||
Sites.EnsureNoSite(client, FILE_TEST_SITE_NAME);
|
||||
site = Sites.CreateSite(client, FILE_TEST_SITE_NAME, Utils.GetAvailablePort(), physicalPath);
|
||||
|
||||
try {
|
||||
var rootDir = Utils.FollowLink(client, site, "files");
|
||||
var rootDirFileInfo = Utils.FollowLink(client, rootDir.Value<JObject>("file_info"), "self");
|
||||
|
||||
object copy = new {
|
||||
name = destName,
|
||||
parent = rootDirFileInfo.Value<JObject>("parent"),
|
||||
file = rootDirFileInfo
|
||||
};
|
||||
|
||||
var copyInfo = client.Post(Utils.GetLink(rootDirFileInfo, "copy"), copy);
|
||||
|
||||
//
|
||||
// Wait for copy to finish
|
||||
HttpResponseMessage res = null;
|
||||
do {
|
||||
res = client.GetAsync(Utils.Self(copyInfo)).Result;
|
||||
} while (res.StatusCode == HttpStatusCode.OK);
|
||||
|
||||
Assert.True(VerifyTestDirectory(physicalPath));
|
||||
Assert.True(VerifyTestDirectory(destPhysicalPath));
|
||||
}
|
||||
finally {
|
||||
if (site != null) {
|
||||
Sites.DeleteSite(client, Utils.Self(site));
|
||||
}
|
||||
|
||||
if (Directory.Exists(physicalPath)) {
|
||||
Directory.Delete(physicalPath, true);
|
||||
}
|
||||
|
||||
if (Directory.Exists(destPhysicalPath)) {
|
||||
Directory.Delete(destPhysicalPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RangeUploadDownload()
|
||||
{
|
||||
|
@ -742,5 +845,63 @@ namespace Microsoft.IIS.Administration.Tests
|
|||
|
||||
return Utils.FollowLink(client, file, "self");
|
||||
}
|
||||
|
||||
private void CreateTestDirectory(string path)
|
||||
{
|
||||
if (Directory.Exists(path)) {
|
||||
Directory.Delete(path, true);
|
||||
Directory.CreateDirectory(path);
|
||||
}
|
||||
|
||||
//
|
||||
// Create directory with large amount of files to move
|
||||
var tasks = new List<Task>();
|
||||
var kb = GetFileSlice(0, 1000);
|
||||
for (var i = 0; i < 50; i++) {
|
||||
string dir = Path.Combine(path, i.ToString());
|
||||
|
||||
tasks.Add(Task.Run(() => {
|
||||
Directory.CreateDirectory(dir);
|
||||
for (var j = 0; j < 50; j++) {
|
||||
using (var stream = File.Create(Path.Combine(dir, j.ToString()))) {
|
||||
for (var k = 0; k < 10; k++) {
|
||||
stream.Write(kb, 0, kb.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
Task.WaitAll(tasks.ToArray());
|
||||
}
|
||||
|
||||
private bool VerifyTestDirectory(string path)
|
||||
{
|
||||
if (!Directory.Exists(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var kb = GetFileSlice(0, 1000);
|
||||
for (var i = 0; i < 50; i++) {
|
||||
string dir = Path.Combine(path, i.ToString());
|
||||
|
||||
if (!Directory.Exists(dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var j = 0; j < 50; j++) {
|
||||
var bytes = File.ReadAllBytes(Path.Combine(dir, j.ToString()));
|
||||
|
||||
for (var k = 0; k < 10; k++) {
|
||||
for (var l = 0; l < kb.Length; l++) {
|
||||
if (bytes[(k * kb.Length) + l] != kb[l]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче