Skip to content

Commit 13bd5df

Browse files
authored
Merge pull request #792 from TechnologyEnhancedLearning/Develop/Fixes/TD-5176
TD-5176 Large file download fix.
2 parents df0d86a + 8e5af69 commit 13bd5df

File tree

3 files changed

+119
-17
lines changed

3 files changed

+119
-17
lines changed

LearningHub.Nhs.WebUI/Interfaces/IFileService.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading.Tasks;
66
using Azure.Storage.Files.Shares.Models;
77
using LearningHub.Nhs.Models.Resource;
8+
using LearningHub.Nhs.WebUI.Models;
89

910
/// <summary>
1011
/// Defines the <see cref="IFileService" />.
@@ -25,7 +26,8 @@ public interface IFileService
2526
/// <param name="filePath">File path.</param>
2627
/// <param name="fileName">File name.</param>
2728
/// <returns>A <see cref="Task{TResult}"/> representing the result of the asynchronous operation.</returns>
28-
Task<ShareFileDownloadInfo> DownloadFileAsync(string filePath, string fileName);
29+
// Task<ShareFileDownloadInfo> DownloadFileAsync(string filePath, string fileName);
30+
Task<FileDownloadResponse> DownloadFileAsync(string filePath, string fileName);
2931

3032
/// <summary>
3133
/// The StreamFileAsync.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace LearningHub.Nhs.WebUI.Models
2+
{
3+
using System.IO;
4+
5+
/// <summary>
6+
/// Defines the <see cref="FileDownloadResponse" />.
7+
/// </summary>
8+
public class FileDownloadResponse
9+
{
10+
/// <summary>
11+
/// Gets or sets the Content.
12+
/// </summary>
13+
public Stream Content { get; set; }
14+
15+
/// <summary>
16+
/// Gets or sets the ContentType.
17+
/// </summary>
18+
public string ContentType { get; set; }
19+
20+
/// <summary>
21+
/// Gets or sets the ContentType.
22+
/// </summary>
23+
public long ContentLength { get; set; }
24+
}
25+
}

LearningHub.Nhs.WebUI/Services/FileService.cs

Lines changed: 91 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
namespace LearningHub.Nhs.WebUI.Services
22
{
33
using System;
4+
using System.Buffers;
45
using System.Collections.Generic;
56
using System.IO;
67
using System.Linq;
8+
using System.Threading;
79
using System.Threading.Tasks;
10+
using System.Threading.Tasks.Dataflow;
11+
using Azure;
812
using Azure.Storage.Files.Shares;
913
using Azure.Storage.Files.Shares.Models;
1014
using LearningHub.Nhs.Models.Resource;
1115
using LearningHub.Nhs.WebUI.Configuration;
1216
using LearningHub.Nhs.WebUI.Interfaces;
17+
using LearningHub.Nhs.WebUI.Models;
1318
using Microsoft.AspNetCore.StaticFiles;
1419
using Microsoft.Extensions.Options;
1520

@@ -125,32 +130,79 @@ public async Task DeleteChunkDirectory(string directoryRef, int chunks)
125130
/// </summary>
126131
/// <param name="filePath">The filePath.</param>
127132
/// <param name="fileName">The fileName.</param>
128-
/// <returns>The <see cref="Task{CloudFile}"/>.</returns>
129-
public async Task<ShareFileDownloadInfo> DownloadFileAsync(string filePath, string fileName)
133+
/// <returns>The <see cref="Task{FileDownloadResponse}"/>.</returns>
134+
public async Task<FileDownloadResponse> DownloadFileAsync(string filePath, string fileName)
130135
{
131-
var directory = this.ShareClient.GetDirectoryClient(filePath);
132-
var sourceDirectory = this.InputArchiveShareClient.GetDirectoryClient(filePath);
136+
const int ChunkSizeInMB = 100;
137+
const int MaxParallelDownloads = 8;
138+
int chunkSize = ChunkSizeInMB * 1024 * 1024;
133139

134-
if (await directory.ExistsAsync())
140+
var file = await this.FindFileAsync(filePath, fileName);
141+
if (file == null)
135142
{
136-
var file = directory.GetFileClient(fileName);
143+
return null;
144+
}
137145

138-
if (await file.ExistsAsync())
146+
var properties = await file.GetPropertiesAsync();
147+
long fileSize = properties.Value.ContentLength;
148+
149+
// If the file size is less than 500 MB, download it directly
150+
if (fileSize <= 500 * 1024 * 1024)
151+
{
152+
var response = await file.DownloadAsync();
153+
return new FileDownloadResponse
139154
{
140-
return await file.DownloadAsync();
141-
}
155+
Content = response.Value.Content,
156+
ContentType = properties.Value.ContentType,
157+
ContentLength = fileSize,
158+
};
142159
}
143-
else if (await sourceDirectory.ExistsAsync())
144-
{
145-
var file = sourceDirectory.GetFileClient(fileName);
146160

147-
if (await file.ExistsAsync())
161+
var pipe = new System.IO.Pipelines.Pipe();
162+
var semaphore = new SemaphoreSlim(1, 1);
163+
164+
var downloadBlock = new ActionBlock<long>(
165+
async offset =>
148166
{
149-
return await file.DownloadAsync();
150-
}
167+
long rangeSize = Math.Min(chunkSize, fileSize - offset);
168+
try
169+
{
170+
var response = await file.DownloadAsync(new HttpRange(offset, rangeSize));
171+
var buffer = new byte[rangeSize];
172+
await response.Value.Content.ReadAsync(buffer, 0, (int)rangeSize);
173+
174+
await semaphore.WaitAsync();
175+
try
176+
{
177+
pipe.Writer.Write(buffer);
178+
}
179+
finally
180+
{
181+
semaphore.Release();
182+
}
183+
}
184+
catch (Exception ex)
185+
{
186+
throw new Exception($"Error downloading chunk at offset {offset}: {ex.Message}");
187+
}
188+
},
189+
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = MaxParallelDownloads, EnsureOrdered = false });
190+
191+
for (long offset = 0; offset < fileSize; offset += chunkSize)
192+
{
193+
downloadBlock.Post(offset);
151194
}
152195

153-
return null;
196+
downloadBlock.Complete();
197+
await downloadBlock.Completion;
198+
await pipe.Writer.CompleteAsync();
199+
200+
return new FileDownloadResponse
201+
{
202+
Content = pipe.Reader.AsStream(),
203+
ContentType = properties.Value.ContentType,
204+
ContentLength = fileSize,
205+
};
154206
}
155207

156208
/// <summary>
@@ -418,5 +470,28 @@ private async Task MoveInPutDirectoryToArchive(List<string> allDirectoryRef)
418470
}
419471
}
420472
}
473+
474+
private async Task<ShareFileClient> FindFileAsync(string filePath, string fileName)
475+
{
476+
var directories = new[]
477+
{
478+
this.ShareClient.GetDirectoryClient(filePath),
479+
this.InputArchiveShareClient.GetDirectoryClient(filePath),
480+
};
481+
482+
foreach (var directory in directories)
483+
{
484+
if (await directory.ExistsAsync())
485+
{
486+
var file = directory.GetFileClient(fileName);
487+
if (await file.ExistsAsync())
488+
{
489+
return file;
490+
}
491+
}
492+
}
493+
494+
return null;
495+
}
421496
}
422497
}

0 commit comments

Comments
 (0)