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"
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
;
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
,
53 UPLOAD_METHOD_MAX_VALUE
56 void RecordDriveUploadProtocol(DriveUploadProtocol protocol
) {
57 UMA_HISTOGRAM_ENUMERATION(
58 "Drive.UploadProtocol", protocol
, UPLOAD_METHOD_MAX_VALUE
);
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
> {
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();
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
),
98 next_start_position(-1),
99 power_save_blocker(content::PowerSaveBlocker::Create(
100 content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension
,
101 content::PowerSaveBlocker::kReasonOther
,
102 "Upload in progress")),
104 weak_ptr_factory_(this) {}
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
) +
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
;
156 // Cancels the upload represented by this struct.
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(),
262 base::Bind(&base::GetFileSize
,
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
);
282 DCHECK_GE(upload_file_info
->content_length
, 0);
284 if (upload_file_info
->cancelled
) {
285 UploadFailed(upload_file_info
.Pass(), DRIVE_CANCELLED
);
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
);
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
);
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
);
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
);
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
);
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
);
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
,
420 info_ptr
->content_length
,
421 info_ptr
->content_type
,
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
449 DVLOG(1) << "Successfully created uploaded file=["
450 << upload_file_info
->file_path
.value() << "]";
453 upload_file_info
->completion_callback
.Run(
454 HTTP_SUCCESS
, GURL(), entry
.Pass());
459 if (response
.code
== HTTP_PRECONDITION
) {
460 UploadFailed(upload_file_info
.Pass(), HTTP_CONFLICT
);
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) {
470 << "UploadNextChunk http code=" << response
.code
471 << ", start_position_received=" << response
.start_position_received
472 << ", end_position_received=" << response
.end_position_received
;
474 upload_file_info
.Pass(),
475 response
.code
== HTTP_FORBIDDEN
? DRIVE_NO_SPACE
: response
.code
);
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
,
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() << "]";
522 upload_file_info
->completion_callback
.Run(
523 HTTP_SUCCESS
, upload_file_info
->upload_location
, entry
.Pass());
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
>());