Reputation: 313
I want to be able to download a large file using Graph. To do so, I need to "chunk" the file, because it's too large to fit into the memory available. Unfortunately the only call I know reads the entire file into memory immediately. Is there a way to avoid this? What call should I be using?
The call I'm using is:
graphClient.Drives[driveId].Items[itemId].Content.Request().GetAsync()
I want to use "CopyToAsync" with a buffer size to send this stream to my response stream, but it blows up on a memory exception before I can. Supposedly there's a "Range" option in the actual REST call, but I haven't figured out how to use it when using the "GraphServiceClient".
Any help would be much appreciated!
Upvotes: 3
Views: 2579
Reputation: 20778
You can get the download URL of the file and chunk the download.
Get DriveItem
and read downloadUrl, size
from metadata. Use downloaUrl
and Range
header to specify range of bytes to be downloaded.
private async Task DonwloadLargeFile()
{
long defaultChunkSize = 50 * 1024;
var driveItem = await client.Drives["driveId"].Items["itemId"].Request().GetAsync();
driveItem.AdditionalData.TryGetValue("@microsoft.graph.downloadUrl", out var downloadUrl);
var size = (long)driveItem.Size;
var numberOfChunks = Convert.ToInt32(size / defaultChunkSize);
var lastChunkSize = Convert.ToInt32(size % defaultChunkSize) - numberOfChunks - 1;
if (lastChunkSize > 0)
{
numberOfChunks++;
}
using (var fs = System.IO.File.Create(Path.Combine(@"C:\Download",driveItem.Name)))
{
var chunkSize = defaultChunkSize;
long offset = 0;
byte[] buffer;
for (int i = 0; i < numberOfChunks; i++)
{
if (i == numberOfChunks - 1)
{
chunkSize = lastChunkSize;
}
var req = new HttpRequestMessage(HttpMethod.Get, (string)downloadUrl);
req.Headers.Range = new RangeHeaderValue(offset, chunkSize + offset);
var response = await client.HttpProvider.SendAsync(req);
using (var rs = await response.Content.ReadAsStreamAsync())
{
buffer = new byte[chunkSize];
int read;
do
{
read = rs.Read(buffer, 0, buffer.Length);
if (read > 0)
{
fs.Write(buffer, 0, buffer.Length);
}
}
while (read > 0);
}
offset += chunkSize + 1;
}
}
}
Upvotes: 3
Reputation: 313
The answer provided by @user2250152 is a good one and got me onto the right track, but the code itself could use some improvement. Here's what I'm using:
private async Task GetLargeFileAsync(GraphServiceClient graphClient, string driveId, string itemId, long defaultChunkSize, HttpResponse response)
{
var driveItem = await graphClient.Drives[driveId].Items[itemId].Request().GetAsync();
driveItem.AdditionalData.TryGetValue("@microsoft.graph.downloadUrl", out var downloadUrl);
var size = (long)driveItem.Size;
response.ContentLength = size;
long offset = 0;
while (offset < size)
{
var chunkSize = Math.Min(size - offset, defaultChunkSize);
var req = new HttpRequestMessage(HttpMethod.Get, downloadUrl.ToString());
req.Headers.Range = new RangeHeaderValue(offset, chunkSize + offset - 1);
var graphClientResponse = await graphClient.HttpProvider.SendAsync(req);
using (var rs = await graphClientResponse.Content.ReadAsStreamAsync())
{
await rs.CopyToAsync(response.Body);
}
offset += defaultChunkSize;
}
}
Upvotes: 2