Подтвердить что ты не робот

С# - загрузка с Google Диска в байтовые фрагменты

В настоящее время я разрабатываю среду с плохой сетевой связью. Мое приложение помогает автоматически загружать необходимые файлы Google Диска для пользователей. Он работает достаточно хорошо для небольших файлов (от 40 КБ до 2 МБ), но слишком часто используется для больших файлов (9 МБ). Я знаю, что размер этих файлов может показаться небольшим, но с точки зрения сетевой среды моего клиента API Google Диска постоянно терпит неудачу с файлом 9 МБ.

Я пришел к выводу, что мне нужно загружать файлы в более мелкие куски байтов, но я не понимаю, как это сделать с помощью API Google Диска. Я читал этот снова и снова, и я пробовал следующий код:

// 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) по-прежнему является полноразмерным (то есть 40 КБ файл для файла диска 40 КБ и 500 внутренних ошибок сервера для 9 МБ файла). На стороне sidenote я также экспериментировал с ExportRequest.MediaDownloader.ChunkSize, но из того, что я наблюдаю, это только изменяет частоту, с которой вызывается обратный вызов ExportRequest.MediaDownloader.ProgressChanged (т.е. Обратный вызов будет запускать каждые 256 КБ, если ChunkSize установлено на 256 * 1024).

Как я могу продолжить?

4b9b3361

Ответ 1

Вы, казалось, направлялись в правильном направлении. Начиная с вашего последнего комментария, запрос будет обновлять прогресс, основанный на размере куска, поэтому ваше наблюдение было точным.

Заглянув в исходный код для MediaDownloader в SDK, было найдено следующее: (выделено мной)

Основная логика загрузки. Мы загружаем носители и записываем их в выходной поток ChunkSize байтов за раз, повышение ProgressChanged событие после каждого фрагмента. Поведение в целом является историческим артефакт: предыдущая реализация выдавала несколько веб-запросов, каждая для байтов 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, как указано в документе documenation разработчика