Roll src/third_party/WebKit 9f7fb92:f103b33 (svn 202621:202622)
[chromium-blink-merge.git] / components / drive / drive_uploader.cc
blob210d1b1d052b21ca4dadd4703c544a74d60cfa4a
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
3 // found in the LICENSE file.
5 #include "components/drive/drive_uploader.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/files/file_util.h"
12 #include "base/metrics/histogram_macros.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/task_runner_util.h"
15 #include "components/drive/service/drive_service_interface.h"
16 #include "content/public/browser/power_save_blocker.h"
17 #include "google_apis/drive/drive_api_parser.h"
19 using google_apis::CancelCallback;
20 using google_apis::FileResource;
21 using google_apis::DRIVE_CANCELLED;
22 using google_apis::DriveApiErrorCode;
23 using google_apis::DRIVE_NO_SPACE;
24 using google_apis::HTTP_CONFLICT;
25 using google_apis::HTTP_CREATED;
26 using google_apis::HTTP_FORBIDDEN;
27 using google_apis::HTTP_NOT_FOUND;
28 using google_apis::HTTP_PRECONDITION;
29 using google_apis::HTTP_RESUME_INCOMPLETE;
30 using google_apis::HTTP_SUCCESS;
31 using google_apis::ProgressCallback;
32 using google_apis::UploadRangeResponse;
34 namespace drive {
36 namespace {
37 // Upload data is split to multiple HTTP request each conveying kUploadChunkSize
38 // bytes (except the request for uploading the last chunk of data).
39 // The value must be a multiple of 512KB according to the spec of GData WAPI and
40 // Drive API v2. It is set to a smaller value than 2^31 for working around
41 // server side error (crbug.com/264089).
42 const int64 kUploadChunkSize = (1LL << 30); // 1GB
43 // Maximum file size to be uploaded by multipart requests. The file that is
44 // larger than the size is processed by resumable upload.
45 const int64 kMaxMultipartUploadSize = (1LL << 20); // 1MB
47 // Drive upload protocol. This is used to back a histogram. Sync this with UMA
48 // enum "DriveUploadProtocol" and treat this as append-only.
49 enum DriveUploadProtocol {
50 UPLOAD_METHOD_RESUMABLE,
51 UPLOAD_METHOD_MULTIPART,
52 UPLOAD_METHOD_BATCH,
53 UPLOAD_METHOD_MAX_VALUE
56 void RecordDriveUploadProtocol(DriveUploadProtocol protocol) {
57 UMA_HISTOGRAM_ENUMERATION(
58 "Drive.UploadProtocol", protocol, UPLOAD_METHOD_MAX_VALUE);
60 } // namespace
62 // Refcounted helper class to manage batch request. DriveUploader uses the class
63 // for keeping the BatchRequestConfigurator instance while it prepares upload
64 // file information asynchronously. DriveUploader discard the reference after
65 // getting file information and the instance will be destroyed after all
66 // preparations complete. At that time, the helper instance commits owned batch
67 // request at the destrutor.
68 class DriveUploader::RefCountedBatchRequest
69 : public base::RefCounted<RefCountedBatchRequest> {
70 public:
71 RefCountedBatchRequest(
72 scoped_ptr<BatchRequestConfiguratorInterface> configurator)
73 : configurator_(configurator.Pass()) {}
75 // Gets pointer of BatchRequestConfiguratorInterface owned by the instance.
76 BatchRequestConfiguratorInterface* configurator() const {
77 return configurator_.get();
80 private:
81 friend class base::RefCounted<RefCountedBatchRequest>;
82 ~RefCountedBatchRequest() { configurator_->Commit(); }
83 scoped_ptr<BatchRequestConfiguratorInterface> configurator_;
86 // Structure containing current upload information of file, passed between
87 // DriveServiceInterface methods and callbacks.
88 struct DriveUploader::UploadFileInfo {
89 UploadFileInfo(const base::FilePath& local_path,
90 const std::string& content_type,
91 const UploadCompletionCallback& callback,
92 const ProgressCallback& progress_callback)
93 : file_path(local_path),
94 content_type(content_type),
95 completion_callback(callback),
96 progress_callback(progress_callback),
97 content_length(0),
98 next_start_position(-1),
99 power_save_blocker(content::PowerSaveBlocker::Create(
100 content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
101 content::PowerSaveBlocker::kReasonOther,
102 "Upload in progress")),
103 cancelled(false),
104 weak_ptr_factory_(this) {}
106 ~UploadFileInfo() {
109 // Useful for printf debugging.
110 std::string DebugString() const {
111 return "file_path=[" + file_path.AsUTF8Unsafe() +
112 "], content_type=[" + content_type +
113 "], content_length=[" + base::UintToString(content_length) +
114 "]";
117 // Returns the callback to cancel the upload represented by this struct.
118 CancelCallback GetCancelCallback() {
119 return base::Bind(&UploadFileInfo::Cancel, weak_ptr_factory_.GetWeakPtr());
122 // The local file path of the file to be uploaded.
123 const base::FilePath file_path;
125 // Content-Type of file.
126 const std::string content_type;
128 // Callback to be invoked once the upload has finished.
129 const UploadCompletionCallback completion_callback;
131 // Callback to periodically notify the upload progress.
132 const ProgressCallback progress_callback;
134 // Location URL where file is to be uploaded to, returned from
135 // InitiateUpload. Used for the subsequent ResumeUpload requests.
136 GURL upload_location;
138 // Header content-Length.
139 int64 content_length;
141 int64 next_start_position;
143 // Blocks system suspend while upload is in progress.
144 scoped_ptr<content::PowerSaveBlocker> power_save_blocker;
146 // Fields for implementing cancellation. |cancel_callback| is non-null if
147 // there is an in-flight HTTP request. In that case, |cancell_callback| will
148 // cancel the operation. |cancelled| is initially false and turns to true
149 // once Cancel() is called. DriveUploader will check this field before after
150 // an async task other than HTTP requests and cancels the subsequent requests
151 // if this is flagged to true.
152 CancelCallback cancel_callback;
153 bool cancelled;
155 private:
156 // Cancels the upload represented by this struct.
157 void Cancel() {
158 cancelled = true;
159 if (!cancel_callback.is_null())
160 cancel_callback.Run();
163 base::WeakPtrFactory<UploadFileInfo> weak_ptr_factory_;
164 DISALLOW_COPY_AND_ASSIGN(UploadFileInfo);
167 DriveUploader::DriveUploader(
168 DriveServiceInterface* drive_service,
169 const scoped_refptr<base::TaskRunner>& blocking_task_runner)
170 : drive_service_(drive_service),
171 blocking_task_runner_(blocking_task_runner),
172 weak_ptr_factory_(this) {
175 DriveUploader::~DriveUploader() {}
177 CancelCallback DriveUploader::UploadNewFile(
178 const std::string& parent_resource_id,
179 const base::FilePath& local_file_path,
180 const std::string& title,
181 const std::string& content_type,
182 const UploadNewFileOptions& options,
183 const UploadCompletionCallback& callback,
184 const ProgressCallback& progress_callback) {
185 DCHECK(thread_checker_.CalledOnValidThread());
186 DCHECK(!parent_resource_id.empty());
187 DCHECK(!local_file_path.empty());
188 DCHECK(!title.empty());
189 DCHECK(!content_type.empty());
190 DCHECK(!callback.is_null());
192 return StartUploadFile(
193 scoped_ptr<UploadFileInfo>(new UploadFileInfo(
194 local_file_path, content_type, callback, progress_callback)),
195 base::Bind(&DriveUploader::CallUploadServiceAPINewFile,
196 weak_ptr_factory_.GetWeakPtr(), parent_resource_id, title,
197 options, current_batch_request_));
200 void DriveUploader::StartBatchProcessing() {
201 DCHECK(current_batch_request_ == nullptr);
202 current_batch_request_ =
203 new RefCountedBatchRequest(drive_service_->StartBatchRequest().Pass());
206 void DriveUploader::StopBatchProcessing() {
207 current_batch_request_ = nullptr;
210 CancelCallback DriveUploader::UploadExistingFile(
211 const std::string& resource_id,
212 const base::FilePath& local_file_path,
213 const std::string& content_type,
214 const UploadExistingFileOptions& options,
215 const UploadCompletionCallback& callback,
216 const ProgressCallback& progress_callback) {
217 DCHECK(thread_checker_.CalledOnValidThread());
218 DCHECK(!resource_id.empty());
219 DCHECK(!local_file_path.empty());
220 DCHECK(!content_type.empty());
221 DCHECK(!callback.is_null());
223 return StartUploadFile(
224 scoped_ptr<UploadFileInfo>(new UploadFileInfo(
225 local_file_path, content_type, callback, progress_callback)),
226 base::Bind(&DriveUploader::CallUploadServiceAPIExistingFile,
227 weak_ptr_factory_.GetWeakPtr(), resource_id, options,
228 current_batch_request_));
231 CancelCallback DriveUploader::ResumeUploadFile(
232 const GURL& upload_location,
233 const base::FilePath& local_file_path,
234 const std::string& content_type,
235 const UploadCompletionCallback& callback,
236 const ProgressCallback& progress_callback) {
237 DCHECK(thread_checker_.CalledOnValidThread());
238 DCHECK(!local_file_path.empty());
239 DCHECK(!content_type.empty());
240 DCHECK(!callback.is_null());
242 scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo(
243 local_file_path, content_type, callback, progress_callback));
244 upload_file_info->upload_location = upload_location;
246 return StartUploadFile(
247 upload_file_info.Pass(),
248 base::Bind(&DriveUploader::StartGetUploadStatus,
249 weak_ptr_factory_.GetWeakPtr()));
252 CancelCallback DriveUploader::StartUploadFile(
253 scoped_ptr<UploadFileInfo> upload_file_info,
254 const StartInitiateUploadCallback& start_initiate_upload_callback) {
255 DCHECK(thread_checker_.CalledOnValidThread());
256 DVLOG(1) << "Uploading file: " << upload_file_info->DebugString();
258 UploadFileInfo* info_ptr = upload_file_info.get();
259 base::PostTaskAndReplyWithResult(
260 blocking_task_runner_.get(),
261 FROM_HERE,
262 base::Bind(&base::GetFileSize,
263 info_ptr->file_path,
264 &info_ptr->content_length),
265 base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize,
266 weak_ptr_factory_.GetWeakPtr(),
267 base::Passed(&upload_file_info),
268 start_initiate_upload_callback));
269 return info_ptr->GetCancelCallback();
272 void DriveUploader::StartUploadFileAfterGetFileSize(
273 scoped_ptr<UploadFileInfo> upload_file_info,
274 const StartInitiateUploadCallback& start_initiate_upload_callback,
275 bool get_file_size_result) {
276 DCHECK(thread_checker_.CalledOnValidThread());
278 if (!get_file_size_result) {
279 UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND);
280 return;
282 DCHECK_GE(upload_file_info->content_length, 0);
284 if (upload_file_info->cancelled) {
285 UploadFailed(upload_file_info.Pass(), DRIVE_CANCELLED);
286 return;
288 start_initiate_upload_callback.Run(upload_file_info.Pass());
291 void DriveUploader::CallUploadServiceAPINewFile(
292 const std::string& parent_resource_id,
293 const std::string& title,
294 const UploadNewFileOptions& options,
295 const scoped_refptr<RefCountedBatchRequest>& batch_request,
296 scoped_ptr<UploadFileInfo> upload_file_info) {
297 DCHECK(thread_checker_.CalledOnValidThread());
299 UploadFileInfo* const info_ptr = upload_file_info.get();
300 if (info_ptr->content_length <= kMaxMultipartUploadSize) {
301 DriveServiceBatchOperationsInterface* service;
302 // If this is a batched request, calls the API on the request instead.
303 if (batch_request.get()) {
304 service = batch_request->configurator();
305 RecordDriveUploadProtocol(UPLOAD_METHOD_BATCH);
306 } else {
307 service = drive_service_;
308 RecordDriveUploadProtocol(UPLOAD_METHOD_MULTIPART);
310 info_ptr->cancel_callback = service->MultipartUploadNewFile(
311 info_ptr->content_type, info_ptr->content_length, parent_resource_id,
312 title, info_ptr->file_path, options,
313 base::Bind(&DriveUploader::OnMultipartUploadComplete,
314 weak_ptr_factory_.GetWeakPtr(),
315 base::Passed(&upload_file_info)),
316 info_ptr->progress_callback);
317 } else {
318 RecordDriveUploadProtocol(UPLOAD_METHOD_RESUMABLE);
319 info_ptr->cancel_callback = drive_service_->InitiateUploadNewFile(
320 info_ptr->content_type, info_ptr->content_length, parent_resource_id,
321 title, options, base::Bind(&DriveUploader::OnUploadLocationReceived,
322 weak_ptr_factory_.GetWeakPtr(),
323 base::Passed(&upload_file_info)));
327 void DriveUploader::CallUploadServiceAPIExistingFile(
328 const std::string& resource_id,
329 const UploadExistingFileOptions& options,
330 const scoped_refptr<RefCountedBatchRequest>& batch_request,
331 scoped_ptr<UploadFileInfo> upload_file_info) {
332 DCHECK(thread_checker_.CalledOnValidThread());
334 UploadFileInfo* const info_ptr = upload_file_info.get();
335 if (info_ptr->content_length <= kMaxMultipartUploadSize) {
336 DriveServiceBatchOperationsInterface* service;
337 // If this is a batched request, calls the API on the request instead.
338 if (batch_request.get()) {
339 service = batch_request->configurator();
340 RecordDriveUploadProtocol(UPLOAD_METHOD_BATCH);
341 } else {
342 service = drive_service_;
343 RecordDriveUploadProtocol(UPLOAD_METHOD_MULTIPART);
345 info_ptr->cancel_callback = service->MultipartUploadExistingFile(
346 info_ptr->content_type, info_ptr->content_length, resource_id,
347 info_ptr->file_path, options,
348 base::Bind(&DriveUploader::OnMultipartUploadComplete,
349 weak_ptr_factory_.GetWeakPtr(),
350 base::Passed(&upload_file_info)),
351 info_ptr->progress_callback);
352 } else {
353 RecordDriveUploadProtocol(UPLOAD_METHOD_RESUMABLE);
354 info_ptr->cancel_callback = drive_service_->InitiateUploadExistingFile(
355 info_ptr->content_type, info_ptr->content_length, resource_id, options,
356 base::Bind(&DriveUploader::OnUploadLocationReceived,
357 weak_ptr_factory_.GetWeakPtr(),
358 base::Passed(&upload_file_info)));
362 void DriveUploader::OnUploadLocationReceived(
363 scoped_ptr<UploadFileInfo> upload_file_info,
364 DriveApiErrorCode code,
365 const GURL& upload_location) {
366 DCHECK(thread_checker_.CalledOnValidThread());
368 DVLOG(1) << "Got upload location [" << upload_location.spec()
369 << "] for [" << upload_file_info->file_path.value() << "]";
371 if (code != HTTP_SUCCESS) {
372 if (code == HTTP_PRECONDITION)
373 code = HTTP_CONFLICT; // ETag mismatch.
374 UploadFailed(upload_file_info.Pass(), code);
375 return;
378 upload_file_info->upload_location = upload_location;
379 upload_file_info->next_start_position = 0;
380 UploadNextChunk(upload_file_info.Pass());
383 void DriveUploader::StartGetUploadStatus(
384 scoped_ptr<UploadFileInfo> upload_file_info) {
385 DCHECK(thread_checker_.CalledOnValidThread());
386 DCHECK(upload_file_info);
388 UploadFileInfo* info_ptr = upload_file_info.get();
389 info_ptr->cancel_callback = drive_service_->GetUploadStatus(
390 info_ptr->upload_location,
391 info_ptr->content_length,
392 base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
393 weak_ptr_factory_.GetWeakPtr(),
394 base::Passed(&upload_file_info)));
397 void DriveUploader::UploadNextChunk(
398 scoped_ptr<UploadFileInfo> upload_file_info) {
399 DCHECK(thread_checker_.CalledOnValidThread());
400 DCHECK(upload_file_info);
401 DCHECK_GE(upload_file_info->next_start_position, 0);
402 DCHECK_LE(upload_file_info->next_start_position,
403 upload_file_info->content_length);
405 if (upload_file_info->cancelled) {
406 UploadFailed(upload_file_info.Pass(), DRIVE_CANCELLED);
407 return;
410 // Limit the size of data uploaded per each request by kUploadChunkSize.
411 const int64 end_position = std::min(
412 upload_file_info->content_length,
413 upload_file_info->next_start_position + kUploadChunkSize);
415 UploadFileInfo* info_ptr = upload_file_info.get();
416 info_ptr->cancel_callback = drive_service_->ResumeUpload(
417 info_ptr->upload_location,
418 info_ptr->next_start_position,
419 end_position,
420 info_ptr->content_length,
421 info_ptr->content_type,
422 info_ptr->file_path,
423 base::Bind(&DriveUploader::OnUploadRangeResponseReceived,
424 weak_ptr_factory_.GetWeakPtr(),
425 base::Passed(&upload_file_info)),
426 base::Bind(&DriveUploader::OnUploadProgress,
427 weak_ptr_factory_.GetWeakPtr(),
428 info_ptr->progress_callback,
429 info_ptr->next_start_position,
430 info_ptr->content_length));
433 void DriveUploader::OnUploadRangeResponseReceived(
434 scoped_ptr<UploadFileInfo> upload_file_info,
435 const UploadRangeResponse& response,
436 scoped_ptr<FileResource> entry) {
437 DCHECK(thread_checker_.CalledOnValidThread());
439 if (response.code == HTTP_CREATED || response.code == HTTP_SUCCESS) {
440 // When uploading a new file, we expect HTTP_CREATED, and when uploading
441 // an existing file (to overwrite), we expect HTTP_SUCCESS.
442 // There is an exception: if we uploading an empty file, uploading a new
443 // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the
444 // fix should be uploading the metadata only. However, to keep the
445 // compatibility with GData WAPI during the migration period, we just
446 // relax the condition here.
447 // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI
448 // code is gone.
449 DVLOG(1) << "Successfully created uploaded file=["
450 << upload_file_info->file_path.value() << "]";
452 // Done uploading.
453 upload_file_info->completion_callback.Run(
454 HTTP_SUCCESS, GURL(), entry.Pass());
455 return;
458 // ETag mismatch.
459 if (response.code == HTTP_PRECONDITION) {
460 UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT);
461 return;
464 // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0
465 // (meaning that the data is uploaded from the beginning of the file),
466 // proceed to upload the next chunk.
467 if (response.code != HTTP_RESUME_INCOMPLETE ||
468 response.start_position_received != 0) {
469 DVLOG(1)
470 << "UploadNextChunk http code=" << response.code
471 << ", start_position_received=" << response.start_position_received
472 << ", end_position_received=" << response.end_position_received;
473 UploadFailed(
474 upload_file_info.Pass(),
475 response.code == HTTP_FORBIDDEN ? DRIVE_NO_SPACE : response.code);
476 return;
479 DVLOG(1) << "Received range " << response.start_position_received
480 << "-" << response.end_position_received
481 << " for [" << upload_file_info->file_path.value() << "]";
483 upload_file_info->next_start_position = response.end_position_received;
484 UploadNextChunk(upload_file_info.Pass());
487 void DriveUploader::OnUploadProgress(const ProgressCallback& callback,
488 int64 start_position,
489 int64 total_size,
490 int64 progress_of_chunk,
491 int64 total_of_chunk) {
492 if (!callback.is_null())
493 callback.Run(start_position + progress_of_chunk, total_size);
496 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info,
497 DriveApiErrorCode error) {
498 DCHECK(thread_checker_.CalledOnValidThread());
500 DVLOG(1) << "Upload failed " << upload_file_info->DebugString();
502 if (upload_file_info->next_start_position < 0) {
503 // Discard the upload location because no request could succeed with it.
504 // Maybe it's obsolete.
505 upload_file_info->upload_location = GURL();
508 upload_file_info->completion_callback.Run(
509 error, upload_file_info->upload_location, scoped_ptr<FileResource>());
512 void DriveUploader::OnMultipartUploadComplete(
513 scoped_ptr<UploadFileInfo> upload_file_info,
514 google_apis::DriveApiErrorCode error,
515 scoped_ptr<FileResource> entry) {
516 DCHECK(thread_checker_.CalledOnValidThread());
518 if (error == HTTP_CREATED || error == HTTP_SUCCESS) {
519 DVLOG(1) << "Successfully created uploaded file=["
520 << upload_file_info->file_path.value() << "]";
521 // Done uploading.
522 upload_file_info->completion_callback.Run(
523 HTTP_SUCCESS, upload_file_info->upload_location, entry.Pass());
524 } else {
525 DVLOG(1) << "Upload failed " << upload_file_info->DebugString();
526 if (error == HTTP_PRECONDITION)
527 error = HTTP_CONFLICT; // ETag mismatch.
528 upload_file_info->completion_callback.Run(
529 error, upload_file_info->upload_location, scoped_ptr<FileResource>());
533 } // namespace drive