C# - 以字节块从Google Drive下载

时间:2016-07-19 04:30:58

标签: c# .net rest google-drive-api

我目前正在针对网络连接较差的环境进行开发。我的应用有助于为用户自动下载所需的Google云端硬盘文件。它适用于小文件(范围从40KB到2MB),但对于较大的文件(9MB)则经常失败。我知道这些文件大小可能看起来很小,但就我的客户端网络环境而言,Google Drive API常常因9MB文件而失败。

我已经得出结论,我需要以较小的字节块下载文件,但我不知道如何使用Google Drive API来实现这一点。我一遍又一遍地阅读this,我尝试了以下代码:

// with the Drive File ID, and the appropriate export MIME type, I create the export request
var request = DriveService.Files.Export(fileId, exportMimeType);

// take the message so I can modify it by hand
var message = request.CreateRequest();
var client = request.Service.HttpClient;

// I change the Range headers of both the client, and message
client.DefaultRequestHeaders.Range =
    message.Headers.Range =
    new System.Net.Http.Headers.RangeHeaderValue(100, 200);
var response = await request.Service.HttpClient.SendAsync(message);

// if status code = 200, copy to local file
if (response.IsSuccessStatusCode)
{
    using (var fileStream = new FileStream(downloadFileName, FileMode.CreateNew, FileAccess.ReadWrite))
    {
        await response.Content.CopyToAsync(fileStream);
    }
}

然而,生成的本地文件(来自fileStream)仍然是全长的(即40KB驱动器文件为40KB文件,9MB文件为500内部服务器错误)。在旁注中,我还尝试了ExportRequest.MediaDownloader.ChunkSize,但从我观察到的情况来看,它只会改变调用ExportRequest.MediaDownloader.ProgressChanged回调的频率(即如果{{1,则回调将触发每256KB)设置为256 * 1024)。

我该怎么办?

1 个答案:

答案 0 :(得分:6)

你似乎朝着正确的方向前进。从您上次的评论中,请求将根据块大小更新进度,因此您的观察结果是准确的。

查看source code for MediaDownloader in the SDK以下内容(强调我的)

  

核心下载逻辑。我们下载媒体并将其写入   一次输出流ChunkSize字节,提升ProgressChanged   每个块后的事件。分块行为在很大程度上是历史性的   工件:先前的实现发出多个Web请求,每个   对于ChunkSize字节。现在我们在一个请求中执行所有操作,但是API   保留客户端可见行为以保持兼容性。

您的示例代码只会从100到200下载一个块。使用该方法,您必须跟踪索引并手动下载每个块,将它们复制到文件流中以进行每次部分下载

const int KB = 0x400;
int ChunkSize = 256 * KB; // 256KB;
public async Task ExportFileAsync(string downloadFileName, string fileId, string exportMimeType) {

    var exportRequest = driveService.Files.Export(fileId, exportMimeType);
    var client = exportRequest.Service.HttpClient;

    //you would need to know the file size
    var size = await GetFileSize(fileId);

    using (var file = new FileStream(downloadFileName, FileMode.CreateNew, FileAccess.ReadWrite)) {

        file.SetLength(size);

        var chunks = (size / ChunkSize) + 1;
        for (long index = 0; index < chunks; index++) {

            var request = exportRequest.CreateRequest();

            var from = index * ChunkSize;
            var to = from + ChunkSize - 1;

            request.Headers.Range = new RangeHeaderValue(from, to);

            var response = await client.SendAsync(request);

            if (response.StatusCode == HttpStatusCode.PartialContent || response.IsSuccessStatusCode) {
                using (var stream = await response.Content.ReadAsStreamAsync()) {
                    file.Seek(from, SeekOrigin.Begin);
                    await stream.CopyToAsync(file);
                }
            }
        }
    }
}

private async Task<long> GetFileSize(string fileId) {
    var file = await driveService.Files.Get(fileId).ExecuteAsync();
    var size = file.size;
    return size;
}

此代码对驱动器api / server进行了一些假设。

  • 服务器将允许以块的形式下载文件所需的多个请求。不知道请求是否受到限制。
  • 服务器仍然接受开发人员文档中所述的Range标头