我正在尝试使用以下Microsoft Graph调用从OneDrive下载文件:
using (var strm = await client.Drives[RemoteDriveId].Items[Id].Content.Request().GetAsync())
{
byte[] byteBuffer = new byte[4096];
filePath = System.IO.Path.Combine(folderPath, filename);
using (System.IO.FileStream output = new FileStream(filePath, FileMode.Create))
{
int bytesRead = 0;
do
{
bytesRead = contentStream.Read(byteBuffer, 0, byteBuffer.Length);
if (bytesRead > 0)
{
output.Write(byteBuffer, 0, bytesRead);
}
}
while (bytesRead > 0);
}
}
上述代码的问题是,如果文件大小或网络速度慢,则在完全下载文件之前,SDK中会抛出请求超时异常。我想以块的形式下载文件或增加超时。如何使用Microsoft图形SDK实现此目的?
答案 0 :(得分:2)
您需要使用Range标头对下载进行分块。
// Based on question by Pavan Tiwari, 11/26/2012, and answer by Simon Mourier
// https://stackoverflow.com/questions/13566302/download-large-file-in-small-chunks-in-c-sharp
const long DefaultChunkSize = 50 * 1024; // 50 KB, TODO: change chunk size to make it realistic for a large file.
long ChunkSize = DefaultChunkSize;
long offset = 0; // cursor location for updating the Range header.
byte[] bytesInStream; // bytes in range returned by chunk download.
// Get the collection of drive items. We'll only use one.
IDriveItemChildrenCollectionPage driveItems = await graphClient.Me.Drive.Root.Children.Request().GetAsync();
foreach (var item in driveItems)
{
// Let's download the first file we get in the response.
if (item.File != null)
{
// We'll use the file metadata to determine size and the name of the downloaded file
// and to get the download URL.
var driveItemInfo = await graphClient.Me.Drive.Items[item.Id].Request().GetAsync();
// Get the download URL. This URL is preauthenticated and has a short TTL.
object downloadUrl;
driveItemInfo.AdditionalData.TryGetValue("@microsoft.graph.downloadUrl", out downloadUrl);
// Get the number of bytes to download. calculate the number of chunks and determine
// the last chunk size.
long size = (long)driveItemInfo.Size;
int numberOfChunks = Convert.ToInt32(size / DefaultChunkSize);
// We are incrementing the offset cursor after writing the response stream to a file after each chunk.
// Subtracting one since the size is 1 based, and the range is 0 base. There should be a better way to do
// this but I haven't spent the time on that.
int lastChunkSize = Convert.ToInt32(size % DefaultChunkSize) - numberOfChunks - 1;
if (lastChunkSize > 0) { numberOfChunks++; }
// Create a file stream to contain the downloaded file.
using (FileStream fileStream = System.IO.File.Create((@"C:\Temp\" + driveItemInfo.Name)))
{
for (int i = 0; i < numberOfChunks; i++)
{
// Setup the last chunk to request. This will be called at the end of this loop.
if (i == numberOfChunks - 1)
{
ChunkSize = lastChunkSize;
}
// Create the request message with the download URL and Range header.
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Get, (string)downloadUrl);
req.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(offset, ChunkSize + offset);
// We can use the the client library to send this although it does add an authentication cost.
// HttpResponseMessage response = await graphClient.HttpProvider.SendAsync(req);
// Since the download URL is preauthenticated, and we aren't deserializing objects,
// we'd be better to make the request with HttpClient.
var client = new HttpClient();
HttpResponseMessage response = await client.SendAsync(req);
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
{
bytesInStream = new byte[ChunkSize];
int read;
do
{
read = responseStream.Read(bytesInStream, 0, (int)bytesInStream.Length);
if (read > 0)
fileStream.Write(bytesInStream, 0, bytesInStream.Length);
}
while (read > 0);
}
offset += ChunkSize + 1; // Move the offset cursor to the next chunk.
}
}
return;
}
}
答案 1 :(得分:0)
上面提供的代码包含错误
if (read > 0)
fileStream.Write(bytesInStream, 0, bytesInStream.Length);
哪个不正确,应该是:
if (read > 0)
fileStream.Write(bytesInStream, 0, read);
并且有点笨拙
int lastChunkSize = Convert.ToInt32(size % DefaultChunkSize) - numberOfChunks - 1;
我重构了一下,决定在这里发表。
更正代码
private int DefaultChunkSize = 5 * 1024 * 1024;//5MB
private int BufferSize = 4096;
...
int chunkSize = DefaultChunkSize;
long offset = 0; // cursor location for updating the Range header.
byte[] buffer = new byte[BufferSize];
var driveItemInfo = await _api.Drive.Root.ItemWithPath(path).Request().GetAsync();
object downloadUrl;
driveItemInfo.AdditionalData.TryGetValue("@content.downloadUrl", out downloadUrl);
long size = (long) driveItemInfo.Size;
int numberOfChunks = Convert.ToInt32(size / DefaultChunkSize);
// We are incrementing the offset cursor after writing the response stream to a file after each chunk.
int lastChunkSize = Convert.ToInt32(size % DefaultChunkSize);
if (lastChunkSize > 0) {
numberOfChunks++;
}
for (int i = 0; i < numberOfChunks; i++) {
// Setup the last chunk to request. This will be called at the end of this loop. if (i == numberOfChunks - 1)
{
chunkSize = lastChunkSize;
}
//Create the request message with the download URL and Range header.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, (string) downloadUrl);
//-1 because range is zero based
request.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(offset, chunkSize + offset - 1);
// We can use the the client library to send this although it does add an authentication cost.
// HttpResponseMessage response = await graphClient.HttpProvider.SendAsync(req);
// Since the download URL is preauthenticated, and we aren't deserializing objects,
// we'd be better to make the request with HttpClient.
var client = new HttpClient();
HttpResponseMessage response = await client.SendAsync(request);
int totalRead = 0;
using(Stream responseStream = await response.Content.ReadAsStreamAsync()) {
int read;
while ((read = await responseStream.ReadAsync(buffer: buffer, offset: 0, count: buffer.Length)) > 0) {
stream.Write(buffer, 0, read);
totalRead += read;
}
}
offset += totalRead; // Move the offset cursor to the next chunk.
}