| Index: chrome/browser/google_apis/drive_uploader.cc
|
| diff --git a/chrome/browser/google_apis/drive_uploader.cc b/chrome/browser/google_apis/drive_uploader.cc
|
| index f575d42f2ea689d1b56be885dacd230b524a2511..fb3e5f7cafb337bb2c02d3a32907785a99761d20 100644
|
| --- a/chrome/browser/google_apis/drive_uploader.cc
|
| +++ b/chrome/browser/google_apis/drive_uploader.cc
|
| @@ -9,7 +9,9 @@
|
| #include "base/bind.h"
|
| #include "base/callback.h"
|
| #include "base/message_loop.h"
|
| +#include "base/rand_util.h"
|
| #include "base/string_number_conversions.h"
|
| +#include "base/time.h"
|
| #include "chrome/browser/google_apis/drive_service_interface.h"
|
| #include "chrome/browser/google_apis/drive_upload_mode.h"
|
| #include "chrome/browser/google_apis/gdata_wapi_parser.h"
|
| @@ -35,6 +37,11 @@ int64 OpenFileStreamAndGetSizeOnBlockingPool(net::FileStream* file_stream,
|
| return file_stream->Available();
|
| }
|
|
|
| +// Returns true if the status code is 5xx (SERVER ERROR).
|
| +bool IsHttp5xxStatusCode(int status_code) {
|
| + return status_code >= 500 && status_code < 600;
|
| +}
|
| +
|
| } // namespace
|
|
|
| namespace google_apis {
|
| @@ -60,9 +67,11 @@ struct DriveUploader::UploadFileInfo {
|
| etag(etag),
|
| completion_callback(callback),
|
| content_length(0),
|
| - next_send_position(0),
|
| file_stream(new net::FileStream(NULL)),
|
| buf(new net::IOBuffer(kUploadChunkSize)),
|
| + buffer_position(0),
|
| + buffer_size(0),
|
| + trial_count(0),
|
| blocking_task_runner(task_runner) {
|
| }
|
|
|
| @@ -72,8 +81,8 @@ struct DriveUploader::UploadFileInfo {
|
|
|
| // Bytes left to upload.
|
| int64 SizeRemaining() const {
|
| - DCHECK(content_length >= next_send_position);
|
| - return content_length - next_send_position;
|
| + DCHECK(content_length >= buffer_position + buffer_size);
|
| + return content_length - (buffer_position + buffer_size);
|
| }
|
|
|
| // Useful for printf debugging.
|
| @@ -116,9 +125,6 @@ struct DriveUploader::UploadFileInfo {
|
| // Header content-Length.
|
| int64 content_length;
|
|
|
| - // The start position of the contents to be sent as the next upload chunk.
|
| - int64 next_send_position;
|
| -
|
| // For opening and reading from physical file.
|
| //
|
| // File operations are posted to |blocking_task_runner|, while the ownership
|
| @@ -131,6 +137,17 @@ struct DriveUploader::UploadFileInfo {
|
| // Holds current content to be uploaded.
|
| const scoped_refptr<net::IOBuffer> buf;
|
|
|
| + // The current |buf|'s head position in the file.
|
| + int64 buffer_position;
|
| +
|
| + // The size of available data in the |buf|.
|
| + int64 buffer_size;
|
| +
|
| + // The number of current trial to send the current |buf| content.
|
| + // This is used to calculate waiting duration. Please see also
|
| + // ResumeInterruptedUpload.
|
| + int trial_count;
|
| +
|
| // Runner for net::FileStream tasks.
|
| const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner;
|
| };
|
| @@ -270,6 +287,31 @@ void DriveUploader::OnUploadLocationReceived(
|
| UploadNextChunk(upload_file_info.Pass());
|
| }
|
|
|
| +void DriveUploader::UploadCurrentChunk(
|
| + scoped_ptr<UploadFileInfo> upload_file_info,
|
| + int64 restart_position) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| +
|
| + int64 buffer_offset =
|
| + restart_position - upload_file_info->buffer_position;
|
| + DCHECK_GE(buffer_offset, 0);
|
| +
|
| + UploadFileInfo* info_ptr = upload_file_info.get();
|
| + drive_service_->ResumeUpload(
|
| + ResumeUploadParams(info_ptr->upload_mode,
|
| + restart_position,
|
| + info_ptr->buffer_position + info_ptr->buffer_size,
|
| + info_ptr->content_length,
|
| + info_ptr->content_type,
|
| + info_ptr->buf,
|
| + buffer_offset,
|
| + info_ptr->upload_location,
|
| + info_ptr->drive_path),
|
| + base::Bind(&DriveUploader::OnResumeUploadResponseReceived,
|
| + weak_ptr_factory_.GetWeakPtr(),
|
| + base::Passed(&upload_file_info)));
|
| +}
|
| +
|
| void DriveUploader::UploadNextChunk(
|
| scoped_ptr<UploadFileInfo> upload_file_info) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| @@ -322,18 +364,22 @@ void DriveUploader::ReadCompletionCallback(
|
| return;
|
| }
|
|
|
| - int64 start_position = upload_file_info->next_send_position;
|
| - upload_file_info->next_send_position += bytes_read;
|
| - int64 end_position = upload_file_info->next_send_position;
|
| + // Update the buffer's position.
|
| + upload_file_info->buffer_position += upload_file_info->buffer_size;
|
| + upload_file_info->buffer_size = bytes_read;
|
| +
|
| + // Reset the trial count.
|
| + upload_file_info->trial_count = 0;
|
|
|
| UploadFileInfo* info_ptr = upload_file_info.get();
|
| drive_service_->ResumeUpload(
|
| ResumeUploadParams(info_ptr->upload_mode,
|
| - start_position,
|
| - end_position,
|
| + info_ptr->buffer_position,
|
| + info_ptr->buffer_position + info_ptr->buffer_size,
|
| info_ptr->content_length,
|
| info_ptr->content_type,
|
| info_ptr->buf,
|
| + 0, // buffer's offset.
|
| info_ptr->upload_location,
|
| info_ptr->drive_path),
|
| base::Bind(&DriveUploader::OnResumeUploadResponseReceived,
|
| @@ -367,19 +413,27 @@ void DriveUploader::OnResumeUploadResponseReceived(
|
| return;
|
| }
|
|
|
| + // The uploading is interrupted. Move to resuming flow.
|
| + if (IsHttp5xxStatusCode(response.code)) {
|
| + ResumeInterruptedUpload(upload_file_info.Pass());
|
| + return;
|
| + }
|
| +
|
| // If code is 308 (RESUME_INCOMPLETE) and range_received is what has been
|
| // previously uploaded (i.e. = upload_file_info->end_position), proceed to
|
| // upload the next chunk.
|
| if (response.code != HTTP_RESUME_INCOMPLETE ||
|
| response.start_position_received != 0 ||
|
| - response.end_position_received != upload_file_info->next_send_position) {
|
| + response.end_position_received < upload_file_info->buffer_position ||
|
| + response.end_position_received >
|
| + upload_file_info->buffer_position + upload_file_info->buffer_size) {
|
| // TODO(achuith): Handle error cases, e.g.
|
| // - when previously uploaded data wasn't received by Google Docs server,
|
| // i.e. when end_position_received < upload_file_info->end_position
|
| LOG(ERROR) << "UploadNextChunk http code=" << response.code
|
| << ", start_position_received=" << response.start_position_received
|
| - << ", end_position_received=" << response.end_position_received
|
| - << ", expected end range=" << upload_file_info->next_send_position;
|
| + << ", end_position_received=" << response.end_position_received;
|
| + // TODO
|
| UploadFailed(upload_file_info.Pass(),
|
| response.code == HTTP_FORBIDDEN ?
|
| DRIVE_UPLOAD_ERROR_NO_SPACE : DRIVE_UPLOAD_ERROR_ABORT);
|
| @@ -391,7 +445,65 @@ void DriveUploader::OnResumeUploadResponseReceived(
|
| << " for [" << upload_file_info->title << "]";
|
|
|
| // Continue uploading.
|
| - UploadNextChunk(upload_file_info.Pass());
|
| + if (upload_file_info->buffer_position + upload_file_info->buffer_size ==
|
| + response.end_position_received) {
|
| + // The current chunk has been sent successuflly, so send the next chunk.
|
| + UploadNextChunk(upload_file_info.Pass());
|
| + } else {
|
| + // There is remaining data in the current chunk. Re-send it.
|
| + //
|
| + // Note that PostTask is necessary here because we have to finish an
|
| + // uploading callback before calling ResumeUpload again due to the
|
| + // implementation of OperationRegistry (http://crbug.com/134814).
|
| + MessageLoop::current()->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&DriveUploader::UploadCurrentChunk,
|
| + weak_ptr_factory_.GetWeakPtr(),
|
| + base::Passed(&upload_file_info),
|
| + response.end_position_received));
|
| + }
|
| +}
|
| +
|
| +void DriveUploader::ResumeInterruptedUpload(
|
| + scoped_ptr<UploadFileInfo> upload_file_info) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| +
|
| + // When the upload is interrupted, we'll wait some period and restart
|
| + // the upload, following the document:
|
| + // https://developers.google.com/drive/manage-uploads#exp-backoff
|
| + // The waiting duration is calculated as follows:
|
| + // First trial: 1sec + random millisecs.
|
| + // Second trial: 2secs + random millisecs.
|
| + // Third trial: 4secs + random millisecs.
|
| + // Forth trial: 8secs + random millisecs.
|
| + // :
|
| + // n-th trial: 2^(n-1)secs + random millisecs.
|
| + base::TimeDelta wait_duration =
|
| + base::TimeDelta::FromSeconds(1 << upload_file_info->trial_count) +
|
| + base::TimeDelta::FromMilliseconds(base::RandInt(0, 9));
|
| + ++upload_file_info->trial_count;
|
| +
|
| + MessageLoop::current()->PostDelayedTask(
|
| + FROM_HERE,
|
| + base::Bind(&DriveUploader::ResumeInterruptedUploadAfterWaiting,
|
| + weak_ptr_factory_.GetWeakPtr(),
|
| + base::Passed(&upload_file_info)),
|
| + wait_duration);
|
| +}
|
| +
|
| +void DriveUploader::ResumeInterruptedUploadAfterWaiting(
|
| + scoped_ptr<UploadFileInfo> upload_file_info) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| +
|
| + UploadFileInfo* info_ptr = upload_file_info.get();
|
| + drive_service_->GetUploadState(
|
| + info_ptr->upload_mode,
|
| + info_ptr->drive_path,
|
| + info_ptr->upload_location,
|
| + info_ptr->content_length,
|
| + base::Bind(&DriveUploader::OnResumeUploadResponseReceived,
|
| + weak_ptr_factory_.GetWeakPtr(),
|
| + base::Passed(&upload_file_info)));
|
| }
|
|
|
| void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
|
|
|