OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/google_apis/drive_uploader.h" | 5 #include "chrome/browser/google_apis/drive_uploader.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/callback.h" | 10 #include "base/callback.h" |
11 #include "base/message_loop.h" | 11 #include "base/message_loop.h" |
| 12 #include "base/rand_util.h" |
12 #include "base/string_number_conversions.h" | 13 #include "base/string_number_conversions.h" |
| 14 #include "base/time.h" |
13 #include "chrome/browser/google_apis/drive_service_interface.h" | 15 #include "chrome/browser/google_apis/drive_service_interface.h" |
14 #include "chrome/browser/google_apis/drive_upload_mode.h" | 16 #include "chrome/browser/google_apis/drive_upload_mode.h" |
15 #include "chrome/browser/google_apis/gdata_wapi_parser.h" | 17 #include "chrome/browser/google_apis/gdata_wapi_parser.h" |
16 #include "content/public/browser/browser_thread.h" | 18 #include "content/public/browser/browser_thread.h" |
17 #include "net/base/file_stream.h" | 19 #include "net/base/file_stream.h" |
18 #include "net/base/net_errors.h" | 20 #include "net/base/net_errors.h" |
19 | 21 |
20 using content::BrowserThread; | 22 using content::BrowserThread; |
21 | 23 |
22 namespace { | 24 namespace { |
23 | 25 |
24 // Google Documents List API requires uploading in chunks of 512kB. | 26 // Google Documents List API requires uploading in chunks of 512kB. |
25 const int64 kUploadChunkSize = 512 * 1024; | 27 const int64 kUploadChunkSize = 512 * 1024; |
26 | 28 |
27 // Opens |path| with |file_stream| and returns the file size. | 29 // Opens |path| with |file_stream| and returns the file size. |
28 // If failed, returns an error code in a negative value. | 30 // If failed, returns an error code in a negative value. |
29 int64 OpenFileStreamAndGetSizeOnBlockingPool(net::FileStream* file_stream, | 31 int64 OpenFileStreamAndGetSizeOnBlockingPool(net::FileStream* file_stream, |
30 const FilePath& path) { | 32 const FilePath& path) { |
31 int result = file_stream->OpenSync( | 33 int result = file_stream->OpenSync( |
32 path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ); | 34 path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ); |
33 if (result != net::OK) | 35 if (result != net::OK) |
34 return result; | 36 return result; |
35 return file_stream->Available(); | 37 return file_stream->Available(); |
36 } | 38 } |
37 | 39 |
| 40 // Returns true if the status code is 5xx (SERVER ERROR). |
| 41 bool IsHttp5xxStatusCode(int status_code) { |
| 42 return status_code >= 500 && status_code < 600; |
| 43 } |
| 44 |
38 } // namespace | 45 } // namespace |
39 | 46 |
40 namespace google_apis { | 47 namespace google_apis { |
41 | 48 |
42 // Structure containing current upload information of file, passed between | 49 // Structure containing current upload information of file, passed between |
43 // DriveServiceInterface methods and callbacks. | 50 // DriveServiceInterface methods and callbacks. |
44 struct DriveUploader::UploadFileInfo { | 51 struct DriveUploader::UploadFileInfo { |
45 UploadFileInfo(scoped_refptr<base::SequencedTaskRunner> task_runner, | 52 UploadFileInfo(scoped_refptr<base::SequencedTaskRunner> task_runner, |
46 UploadMode upload_mode, | 53 UploadMode upload_mode, |
47 const GURL& initial_upload_location, | 54 const GURL& initial_upload_location, |
48 const FilePath& drive_path, | 55 const FilePath& drive_path, |
49 const FilePath& local_path, | 56 const FilePath& local_path, |
50 const std::string& title, | 57 const std::string& title, |
51 const std::string& content_type, | 58 const std::string& content_type, |
52 const std::string& etag, | 59 const std::string& etag, |
53 const UploadCompletionCallback& callback) | 60 const UploadCompletionCallback& callback) |
54 : upload_mode(upload_mode), | 61 : upload_mode(upload_mode), |
55 initial_upload_location(initial_upload_location), | 62 initial_upload_location(initial_upload_location), |
56 drive_path(drive_path), | 63 drive_path(drive_path), |
57 file_path(local_path), | 64 file_path(local_path), |
58 title(title), | 65 title(title), |
59 content_type(content_type), | 66 content_type(content_type), |
60 etag(etag), | 67 etag(etag), |
61 completion_callback(callback), | 68 completion_callback(callback), |
62 content_length(0), | 69 content_length(0), |
63 next_send_position(0), | |
64 file_stream(new net::FileStream(NULL)), | 70 file_stream(new net::FileStream(NULL)), |
65 buf(new net::IOBuffer(kUploadChunkSize)), | 71 buf(new net::IOBuffer(kUploadChunkSize)), |
| 72 buffer_position(0), |
| 73 buffer_size(0), |
| 74 trial_count(0), |
66 blocking_task_runner(task_runner) { | 75 blocking_task_runner(task_runner) { |
67 } | 76 } |
68 | 77 |
69 ~UploadFileInfo() { | 78 ~UploadFileInfo() { |
70 blocking_task_runner->DeleteSoon(FROM_HERE, file_stream.release()); | 79 blocking_task_runner->DeleteSoon(FROM_HERE, file_stream.release()); |
71 } | 80 } |
72 | 81 |
73 // Bytes left to upload. | 82 // Bytes left to upload. |
74 int64 SizeRemaining() const { | 83 int64 SizeRemaining() const { |
75 DCHECK(content_length >= next_send_position); | 84 DCHECK(content_length >= buffer_position + buffer_size); |
76 return content_length - next_send_position; | 85 return content_length - (buffer_position + buffer_size); |
77 } | 86 } |
78 | 87 |
79 // Useful for printf debugging. | 88 // Useful for printf debugging. |
80 std::string DebugString() const { | 89 std::string DebugString() const { |
81 return "title=[" + title + | 90 return "title=[" + title + |
82 "], file_path=[" + file_path.AsUTF8Unsafe() + | 91 "], file_path=[" + file_path.AsUTF8Unsafe() + |
83 "], content_type=[" + content_type + | 92 "], content_type=[" + content_type + |
84 "], content_length=[" + base::UintToString(content_length) + | 93 "], content_length=[" + base::UintToString(content_length) + |
85 "], drive_path=[" + drive_path.AsUTF8Unsafe() + | 94 "], drive_path=[" + drive_path.AsUTF8Unsafe() + |
86 "]"; | 95 "]"; |
(...skipping 22 matching lines...) Expand all Loading... |
109 // Callback to be invoked once the upload has finished. | 118 // Callback to be invoked once the upload has finished. |
110 const UploadCompletionCallback completion_callback; | 119 const UploadCompletionCallback completion_callback; |
111 | 120 |
112 // Location URL where file is to be uploaded to, returned from | 121 // Location URL where file is to be uploaded to, returned from |
113 // InitiateUpload. Used for the subsequent ResumeUpload requests. | 122 // InitiateUpload. Used for the subsequent ResumeUpload requests. |
114 GURL upload_location; | 123 GURL upload_location; |
115 | 124 |
116 // Header content-Length. | 125 // Header content-Length. |
117 int64 content_length; | 126 int64 content_length; |
118 | 127 |
119 // The start position of the contents to be sent as the next upload chunk. | |
120 int64 next_send_position; | |
121 | |
122 // For opening and reading from physical file. | 128 // For opening and reading from physical file. |
123 // | 129 // |
124 // File operations are posted to |blocking_task_runner|, while the ownership | 130 // File operations are posted to |blocking_task_runner|, while the ownership |
125 // of the stream is held in UI thread. At the point when this UploadFileInfo | 131 // of the stream is held in UI thread. At the point when this UploadFileInfo |
126 // is destroyed, the ownership of the stream is passed to the worker pool. | 132 // is destroyed, the ownership of the stream is passed to the worker pool. |
127 // TODO(kinaba): We should switch to async API of FileStream once | 133 // TODO(kinaba): We should switch to async API of FileStream once |
128 // crbug.com/164312 is fixed. | 134 // crbug.com/164312 is fixed. |
129 scoped_ptr<net::FileStream> file_stream; | 135 scoped_ptr<net::FileStream> file_stream; |
130 | 136 |
131 // Holds current content to be uploaded. | 137 // Holds current content to be uploaded. |
132 const scoped_refptr<net::IOBuffer> buf; | 138 const scoped_refptr<net::IOBuffer> buf; |
133 | 139 |
| 140 // The current |buf|'s head position in the file. |
| 141 int64 buffer_position; |
| 142 |
| 143 // The size of available data in the |buf|. |
| 144 int64 buffer_size; |
| 145 |
| 146 // The number of current trial to send the current |buf| content. |
| 147 // This is used to calculate waiting duration. Please see also |
| 148 // ResumeInterruptedUpload. |
| 149 int trial_count; |
| 150 |
134 // Runner for net::FileStream tasks. | 151 // Runner for net::FileStream tasks. |
135 const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner; | 152 const scoped_refptr<base::SequencedTaskRunner> blocking_task_runner; |
136 }; | 153 }; |
137 | 154 |
138 DriveUploader::DriveUploader(DriveServiceInterface* drive_service) | 155 DriveUploader::DriveUploader(DriveServiceInterface* drive_service) |
139 : drive_service_(drive_service), | 156 : drive_service_(drive_service), |
140 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { | 157 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { |
141 base::SequencedWorkerPool* blocking_pool = BrowserThread::GetBlockingPool(); | 158 base::SequencedWorkerPool* blocking_pool = BrowserThread::GetBlockingPool(); |
142 blocking_task_runner_ = blocking_pool->GetSequencedTaskRunner( | 159 blocking_task_runner_ = blocking_pool->GetSequencedTaskRunner( |
143 blocking_pool->GetSequenceToken()); | 160 blocking_pool->GetSequenceToken()); |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
263 UploadFailed(upload_file_info.Pass(), DRIVE_UPLOAD_ERROR_ABORT); | 280 UploadFailed(upload_file_info.Pass(), DRIVE_UPLOAD_ERROR_ABORT); |
264 return; | 281 return; |
265 } | 282 } |
266 | 283 |
267 upload_file_info->upload_location = upload_location; | 284 upload_file_info->upload_location = upload_location; |
268 | 285 |
269 // Start the upload from the beginning of the file. | 286 // Start the upload from the beginning of the file. |
270 UploadNextChunk(upload_file_info.Pass()); | 287 UploadNextChunk(upload_file_info.Pass()); |
271 } | 288 } |
272 | 289 |
| 290 void DriveUploader::UploadCurrentChunk( |
| 291 scoped_ptr<UploadFileInfo> upload_file_info, |
| 292 int64 restart_position) { |
| 293 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 294 |
| 295 int64 buffer_offset = |
| 296 restart_position - upload_file_info->buffer_position; |
| 297 DCHECK_GE(buffer_offset, 0); |
| 298 |
| 299 UploadFileInfo* info_ptr = upload_file_info.get(); |
| 300 drive_service_->ResumeUpload( |
| 301 ResumeUploadParams(info_ptr->upload_mode, |
| 302 restart_position, |
| 303 info_ptr->buffer_position + info_ptr->buffer_size, |
| 304 info_ptr->content_length, |
| 305 info_ptr->content_type, |
| 306 info_ptr->buf, |
| 307 buffer_offset, |
| 308 info_ptr->upload_location, |
| 309 info_ptr->drive_path), |
| 310 base::Bind(&DriveUploader::OnResumeUploadResponseReceived, |
| 311 weak_ptr_factory_.GetWeakPtr(), |
| 312 base::Passed(&upload_file_info))); |
| 313 } |
| 314 |
273 void DriveUploader::UploadNextChunk( | 315 void DriveUploader::UploadNextChunk( |
274 scoped_ptr<UploadFileInfo> upload_file_info) { | 316 scoped_ptr<UploadFileInfo> upload_file_info) { |
275 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
276 | 318 |
277 // Determine number of bytes to read for this upload iteration. | 319 // Determine number of bytes to read for this upload iteration. |
278 const int bytes_to_read = std::min(upload_file_info->SizeRemaining(), | 320 const int bytes_to_read = std::min(upload_file_info->SizeRemaining(), |
279 kUploadChunkSize); | 321 kUploadChunkSize); |
280 | 322 |
281 if (bytes_to_read == 0) { | 323 if (bytes_to_read == 0) { |
282 // net::FileStream doesn't allow to read 0 bytes, so directly proceed to the | 324 // net::FileStream doesn't allow to read 0 bytes, so directly proceed to the |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
315 DCHECK_EQ(bytes_to_read, bytes_read); | 357 DCHECK_EQ(bytes_to_read, bytes_read); |
316 DVLOG(1) << "ReadCompletionCallback bytes read=" << bytes_read; | 358 DVLOG(1) << "ReadCompletionCallback bytes read=" << bytes_read; |
317 | 359 |
318 if (bytes_read < 0) { | 360 if (bytes_read < 0) { |
319 LOG(ERROR) << "Error reading from file " | 361 LOG(ERROR) << "Error reading from file " |
320 << upload_file_info->file_path.value(); | 362 << upload_file_info->file_path.value(); |
321 UploadFailed(upload_file_info.Pass(), DRIVE_UPLOAD_ERROR_ABORT); | 363 UploadFailed(upload_file_info.Pass(), DRIVE_UPLOAD_ERROR_ABORT); |
322 return; | 364 return; |
323 } | 365 } |
324 | 366 |
325 int64 start_position = upload_file_info->next_send_position; | 367 // Update the buffer's position. |
326 upload_file_info->next_send_position += bytes_read; | 368 upload_file_info->buffer_position += upload_file_info->buffer_size; |
327 int64 end_position = upload_file_info->next_send_position; | 369 upload_file_info->buffer_size = bytes_read; |
| 370 |
| 371 // Reset the trial count. |
| 372 upload_file_info->trial_count = 0; |
328 | 373 |
329 UploadFileInfo* info_ptr = upload_file_info.get(); | 374 UploadFileInfo* info_ptr = upload_file_info.get(); |
330 drive_service_->ResumeUpload( | 375 drive_service_->ResumeUpload( |
331 ResumeUploadParams(info_ptr->upload_mode, | 376 ResumeUploadParams(info_ptr->upload_mode, |
332 start_position, | 377 info_ptr->buffer_position, |
333 end_position, | 378 info_ptr->buffer_position + info_ptr->buffer_size, |
334 info_ptr->content_length, | 379 info_ptr->content_length, |
335 info_ptr->content_type, | 380 info_ptr->content_type, |
336 info_ptr->buf, | 381 info_ptr->buf, |
| 382 0, // buffer's offset. |
337 info_ptr->upload_location, | 383 info_ptr->upload_location, |
338 info_ptr->drive_path), | 384 info_ptr->drive_path), |
339 base::Bind(&DriveUploader::OnResumeUploadResponseReceived, | 385 base::Bind(&DriveUploader::OnResumeUploadResponseReceived, |
340 weak_ptr_factory_.GetWeakPtr(), | 386 weak_ptr_factory_.GetWeakPtr(), |
341 base::Passed(&upload_file_info))); | 387 base::Passed(&upload_file_info))); |
342 } | 388 } |
343 | 389 |
344 void DriveUploader::OnResumeUploadResponseReceived( | 390 void DriveUploader::OnResumeUploadResponseReceived( |
345 scoped_ptr<UploadFileInfo> upload_file_info, | 391 scoped_ptr<UploadFileInfo> upload_file_info, |
346 const ResumeUploadResponse& response, | 392 const ResumeUploadResponse& response, |
(...skipping 13 matching lines...) Expand all Loading... |
360 entry.Pass()); | 406 entry.Pass()); |
361 return; | 407 return; |
362 } | 408 } |
363 | 409 |
364 // ETag mismatch. | 410 // ETag mismatch. |
365 if (response.code == HTTP_PRECONDITION) { | 411 if (response.code == HTTP_PRECONDITION) { |
366 UploadFailed(upload_file_info.Pass(), DRIVE_UPLOAD_ERROR_CONFLICT); | 412 UploadFailed(upload_file_info.Pass(), DRIVE_UPLOAD_ERROR_CONFLICT); |
367 return; | 413 return; |
368 } | 414 } |
369 | 415 |
| 416 // The uploading is interrupted. Move to resuming flow. |
| 417 if (IsHttp5xxStatusCode(response.code)) { |
| 418 ResumeInterruptedUpload(upload_file_info.Pass()); |
| 419 return; |
| 420 } |
| 421 |
370 // If code is 308 (RESUME_INCOMPLETE) and range_received is what has been | 422 // If code is 308 (RESUME_INCOMPLETE) and range_received is what has been |
371 // previously uploaded (i.e. = upload_file_info->end_position), proceed to | 423 // previously uploaded (i.e. = upload_file_info->end_position), proceed to |
372 // upload the next chunk. | 424 // upload the next chunk. |
373 if (response.code != HTTP_RESUME_INCOMPLETE || | 425 if (response.code != HTTP_RESUME_INCOMPLETE || |
374 response.start_position_received != 0 || | 426 response.start_position_received != 0 || |
375 response.end_position_received != upload_file_info->next_send_position) { | 427 response.end_position_received < upload_file_info->buffer_position || |
| 428 response.end_position_received > |
| 429 upload_file_info->buffer_position + upload_file_info->buffer_size) { |
376 // TODO(achuith): Handle error cases, e.g. | 430 // TODO(achuith): Handle error cases, e.g. |
377 // - when previously uploaded data wasn't received by Google Docs server, | 431 // - when previously uploaded data wasn't received by Google Docs server, |
378 // i.e. when end_position_received < upload_file_info->end_position | 432 // i.e. when end_position_received < upload_file_info->end_position |
379 LOG(ERROR) << "UploadNextChunk http code=" << response.code | 433 LOG(ERROR) << "UploadNextChunk http code=" << response.code |
380 << ", start_position_received=" << response.start_position_received | 434 << ", start_position_received=" << response.start_position_received |
381 << ", end_position_received=" << response.end_position_received | 435 << ", end_position_received=" << response.end_position_received; |
382 << ", expected end range=" << upload_file_info->next_send_position; | 436 // TODO |
383 UploadFailed(upload_file_info.Pass(), | 437 UploadFailed(upload_file_info.Pass(), |
384 response.code == HTTP_FORBIDDEN ? | 438 response.code == HTTP_FORBIDDEN ? |
385 DRIVE_UPLOAD_ERROR_NO_SPACE : DRIVE_UPLOAD_ERROR_ABORT); | 439 DRIVE_UPLOAD_ERROR_NO_SPACE : DRIVE_UPLOAD_ERROR_ABORT); |
386 return; | 440 return; |
387 } | 441 } |
388 | 442 |
389 DVLOG(1) << "Received range " << response.start_position_received | 443 DVLOG(1) << "Received range " << response.start_position_received |
390 << "-" << response.end_position_received | 444 << "-" << response.end_position_received |
391 << " for [" << upload_file_info->title << "]"; | 445 << " for [" << upload_file_info->title << "]"; |
392 | 446 |
393 // Continue uploading. | 447 // Continue uploading. |
394 UploadNextChunk(upload_file_info.Pass()); | 448 if (upload_file_info->buffer_position + upload_file_info->buffer_size == |
| 449 response.end_position_received) { |
| 450 // The current chunk has been sent successuflly, so send the next chunk. |
| 451 UploadNextChunk(upload_file_info.Pass()); |
| 452 } else { |
| 453 // There is remaining data in the current chunk. Re-send it. |
| 454 // |
| 455 // Note that PostTask is necessary here because we have to finish an |
| 456 // uploading callback before calling ResumeUpload again due to the |
| 457 // implementation of OperationRegistry (http://crbug.com/134814). |
| 458 MessageLoop::current()->PostTask( |
| 459 FROM_HERE, |
| 460 base::Bind(&DriveUploader::UploadCurrentChunk, |
| 461 weak_ptr_factory_.GetWeakPtr(), |
| 462 base::Passed(&upload_file_info), |
| 463 response.end_position_received)); |
| 464 } |
| 465 } |
| 466 |
| 467 void DriveUploader::ResumeInterruptedUpload( |
| 468 scoped_ptr<UploadFileInfo> upload_file_info) { |
| 469 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 470 |
| 471 // When the upload is interrupted, we'll wait some period and restart |
| 472 // the upload, following the document: |
| 473 // https://developers.google.com/drive/manage-uploads#exp-backoff |
| 474 // The waiting duration is calculated as follows: |
| 475 // First trial: 1sec + random millisecs. |
| 476 // Second trial: 2secs + random millisecs. |
| 477 // Third trial: 4secs + random millisecs. |
| 478 // Forth trial: 8secs + random millisecs. |
| 479 // : |
| 480 // n-th trial: 2^(n-1)secs + random millisecs. |
| 481 base::TimeDelta wait_duration = |
| 482 base::TimeDelta::FromSeconds(1 << upload_file_info->trial_count) + |
| 483 base::TimeDelta::FromMilliseconds(base::RandInt(0, 9)); |
| 484 ++upload_file_info->trial_count; |
| 485 |
| 486 MessageLoop::current()->PostDelayedTask( |
| 487 FROM_HERE, |
| 488 base::Bind(&DriveUploader::ResumeInterruptedUploadAfterWaiting, |
| 489 weak_ptr_factory_.GetWeakPtr(), |
| 490 base::Passed(&upload_file_info)), |
| 491 wait_duration); |
| 492 } |
| 493 |
| 494 void DriveUploader::ResumeInterruptedUploadAfterWaiting( |
| 495 scoped_ptr<UploadFileInfo> upload_file_info) { |
| 496 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 497 |
| 498 UploadFileInfo* info_ptr = upload_file_info.get(); |
| 499 drive_service_->GetUploadState( |
| 500 info_ptr->upload_mode, |
| 501 info_ptr->drive_path, |
| 502 info_ptr->upload_location, |
| 503 info_ptr->content_length, |
| 504 base::Bind(&DriveUploader::OnResumeUploadResponseReceived, |
| 505 weak_ptr_factory_.GetWeakPtr(), |
| 506 base::Passed(&upload_file_info))); |
395 } | 507 } |
396 | 508 |
397 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info, | 509 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info, |
398 DriveUploadError error) { | 510 DriveUploadError error) { |
399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 511 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
400 | 512 |
401 LOG(ERROR) << "Upload failed " << upload_file_info->DebugString(); | 513 LOG(ERROR) << "Upload failed " << upload_file_info->DebugString(); |
402 | 514 |
403 upload_file_info->completion_callback.Run(error, | 515 upload_file_info->completion_callback.Run(error, |
404 upload_file_info->drive_path, | 516 upload_file_info->drive_path, |
405 upload_file_info->file_path, | 517 upload_file_info->file_path, |
406 scoped_ptr<ResourceEntry>()); | 518 scoped_ptr<ResourceEntry>()); |
407 } | 519 } |
408 | 520 |
409 } // namespace google_apis | 521 } // namespace google_apis |
OLD | NEW |