Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion LearningHub.Nhs.WebUI/Interfaces/IFileService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Threading.Tasks;
using Azure.Storage.Files.Shares.Models;
using LearningHub.Nhs.Models.Resource;
using LearningHub.Nhs.WebUI.Models;

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

/// <summary>
/// The StreamFileAsync.
Expand Down
25 changes: 25 additions & 0 deletions LearningHub.Nhs.WebUI/Models/FileDownloadResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace LearningHub.Nhs.WebUI.Models
{
using System.IO;

/// <summary>
/// Defines the <see cref="FileDownloadResponse" />.
/// </summary>
public class FileDownloadResponse
{
/// <summary>
/// Gets or sets the Content.
/// </summary>
public Stream Content { get; set; }

/// <summary>
/// Gets or sets the ContentType.
/// </summary>
public string ContentType { get; set; }

/// <summary>
/// Gets or sets the ContentType.
/// </summary>
public long ContentLength { get; set; }
}
}
107 changes: 91 additions & 16 deletions LearningHub.Nhs.WebUI/Services/FileService.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
namespace LearningHub.Nhs.WebUI.Services
{
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Azure;
using Azure.Storage.Files.Shares;
using Azure.Storage.Files.Shares.Models;
using LearningHub.Nhs.Models.Resource;
using LearningHub.Nhs.WebUI.Configuration;
using LearningHub.Nhs.WebUI.Interfaces;
using LearningHub.Nhs.WebUI.Models;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Options;

Expand Down Expand Up @@ -125,32 +130,79 @@ public async Task DeleteChunkDirectory(string directoryRef, int chunks)
/// </summary>
/// <param name="filePath">The filePath.</param>
/// <param name="fileName">The fileName.</param>
/// <returns>The <see cref="Task{CloudFile}"/>.</returns>
public async Task<ShareFileDownloadInfo> DownloadFileAsync(string filePath, string fileName)
/// <returns>The <see cref="Task{FileDownloadResponse}"/>.</returns>
public async Task<FileDownloadResponse> DownloadFileAsync(string filePath, string fileName)
{
var directory = this.ShareClient.GetDirectoryClient(filePath);
var sourceDirectory = this.InputArchiveShareClient.GetDirectoryClient(filePath);
const int ChunkSizeInMB = 100;
const int MaxParallelDownloads = 8;
int chunkSize = ChunkSizeInMB * 1024 * 1024;

if (await directory.ExistsAsync())
var file = await this.FindFileAsync(filePath, fileName);
if (file == null)
{
var file = directory.GetFileClient(fileName);
return null;
}

if (await file.ExistsAsync())
var properties = await file.GetPropertiesAsync();
long fileSize = properties.Value.ContentLength;

// If the file size is less than 500 MB, download it directly
if (fileSize <= 500 * 1024 * 1024)
{
var response = await file.DownloadAsync();
return new FileDownloadResponse
{
return await file.DownloadAsync();
}
Content = response.Value.Content,
ContentType = properties.Value.ContentType,
ContentLength = fileSize,
};
}
else if (await sourceDirectory.ExistsAsync())
{
var file = sourceDirectory.GetFileClient(fileName);

if (await file.ExistsAsync())
var pipe = new System.IO.Pipelines.Pipe();
var semaphore = new SemaphoreSlim(1, 1);

var downloadBlock = new ActionBlock<long>(
async offset =>
{
return await file.DownloadAsync();
}
long rangeSize = Math.Min(chunkSize, fileSize - offset);
try
{
var response = await file.DownloadAsync(new HttpRange(offset, rangeSize));
var buffer = new byte[rangeSize];
await response.Value.Content.ReadAsync(buffer, 0, (int)rangeSize);

await semaphore.WaitAsync();
try
{
pipe.Writer.Write(buffer);
}
finally
{
semaphore.Release();
}
}
catch (Exception ex)
{
throw new Exception($"Error downloading chunk at offset {offset}: {ex.Message}");
}
},
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = MaxParallelDownloads, EnsureOrdered = false });

for (long offset = 0; offset < fileSize; offset += chunkSize)
{
downloadBlock.Post(offset);
}

return null;
downloadBlock.Complete();
await downloadBlock.Completion;
await pipe.Writer.CompleteAsync();

return new FileDownloadResponse
{
Content = pipe.Reader.AsStream(),
ContentType = properties.Value.ContentType,
ContentLength = fileSize,
};
}

/// <summary>
Expand Down Expand Up @@ -418,5 +470,28 @@ private async Task MoveInPutDirectoryToArchive(List<string> allDirectoryRef)
}
}
}

private async Task<ShareFileClient> FindFileAsync(string filePath, string fileName)
{
var directories = new[]
{
this.ShareClient.GetDirectoryClient(filePath),
this.InputArchiveShareClient.GetDirectoryClient(filePath),
};

foreach (var directory in directories)
{
if (await directory.ExistsAsync())
{
var file = directory.GetFileClient(fileName);
if (await file.ExistsAsync())
{
return file;
}
}
}

return null;
}
}
}
Loading