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 "chrome/browser/drive/drive_uploader.h"
10 #include "base/callback.h"
11 #include "base/files/file_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/task_runner_util.h"
14 #include "chrome/browser/drive/drive_service_interface.h"
15 #include "content/public/browser/power_save_blocker.h"
16 #include "google_apis/drive/drive_api_parser.h"
18 using google_apis::CancelCallback
;
19 using google_apis::FileResource
;
20 using google_apis::DRIVE_CANCELLED
;
21 using google_apis::DriveApiErrorCode
;
22 using google_apis::DRIVE_NO_SPACE
;
23 using google_apis::HTTP_CONFLICT
;
24 using google_apis::HTTP_CREATED
;
25 using google_apis::HTTP_FORBIDDEN
;
26 using google_apis::HTTP_NOT_FOUND
;
27 using google_apis::HTTP_PRECONDITION
;
28 using google_apis::HTTP_RESUME_INCOMPLETE
;
29 using google_apis::HTTP_SUCCESS
;
30 using google_apis::ProgressCallback
;
31 using google_apis::UploadRangeResponse
;
36 // Upload data is split to multiple HTTP request each conveying kUploadChunkSize
37 // bytes (except the request for uploading the last chunk of data).
38 // The value must be a multiple of 512KB according to the spec of GData WAPI and
39 // Drive API v2. It is set to a smaller value than 2^31 for working around
40 // server side error (crbug.com/264089).
41 const int64 kUploadChunkSize
= (1LL << 30); // 1GB
42 // Maximum file size to be uploaded by multipart requests. The file that is
43 // larger than the size is processed by resumable upload.
44 const int64 kMaxMultipartUploadSize
= (1LL << 20); // 1MB
47 // Structure containing current upload information of file, passed between
48 // DriveServiceInterface methods and callbacks.
49 struct DriveUploader::UploadFileInfo
{
50 UploadFileInfo(const base::FilePath
& local_path
,
51 const std::string
& content_type
,
52 const UploadCompletionCallback
& callback
,
53 const ProgressCallback
& progress_callback
)
54 : file_path(local_path
),
55 content_type(content_type
),
56 completion_callback(callback
),
57 progress_callback(progress_callback
),
59 next_start_position(-1),
60 power_save_blocker(content::PowerSaveBlocker::Create(
61 content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension
,
62 content::PowerSaveBlocker::kReasonOther
,
63 "Upload in progress")),
65 weak_ptr_factory_(this) {}
70 // Useful for printf debugging.
71 std::string
DebugString() const {
72 return "file_path=[" + file_path
.AsUTF8Unsafe() +
73 "], content_type=[" + content_type
+
74 "], content_length=[" + base::UintToString(content_length
) +
78 // Returns the callback to cancel the upload represented by this struct.
79 CancelCallback
GetCancelCallback() {
80 return base::Bind(&UploadFileInfo::Cancel
, weak_ptr_factory_
.GetWeakPtr());
83 // The local file path of the file to be uploaded.
84 const base::FilePath file_path
;
86 // Content-Type of file.
87 const std::string content_type
;
89 // Callback to be invoked once the upload has finished.
90 const UploadCompletionCallback completion_callback
;
92 // Callback to periodically notify the upload progress.
93 const ProgressCallback progress_callback
;
95 // Location URL where file is to be uploaded to, returned from
96 // InitiateUpload. Used for the subsequent ResumeUpload requests.
99 // Header content-Length.
100 int64 content_length
;
102 int64 next_start_position
;
104 // Blocks system suspend while upload is in progress.
105 scoped_ptr
<content::PowerSaveBlocker
> power_save_blocker
;
107 // Fields for implementing cancellation. |cancel_callback| is non-null if
108 // there is an in-flight HTTP request. In that case, |cancell_callback| will
109 // cancel the operation. |cancelled| is initially false and turns to true
110 // once Cancel() is called. DriveUploader will check this field before after
111 // an async task other than HTTP requests and cancels the subsequent requests
112 // if this is flagged to true.
113 CancelCallback cancel_callback
;
117 // Cancels the upload represented by this struct.
120 if (!cancel_callback
.is_null())
121 cancel_callback
.Run();
124 base::WeakPtrFactory
<UploadFileInfo
> weak_ptr_factory_
;
125 DISALLOW_COPY_AND_ASSIGN(UploadFileInfo
);
128 DriveUploader::DriveUploader(
129 DriveServiceInterface
* drive_service
,
130 const scoped_refptr
<base::TaskRunner
>& blocking_task_runner
)
131 : drive_service_(drive_service
),
132 blocking_task_runner_(blocking_task_runner
),
133 weak_ptr_factory_(this) {
136 DriveUploader::~DriveUploader() {}
138 CancelCallback
DriveUploader::UploadNewFile(
139 const std::string
& parent_resource_id
,
140 const base::FilePath
& local_file_path
,
141 const std::string
& title
,
142 const std::string
& content_type
,
143 const UploadNewFileOptions
& options
,
144 const UploadCompletionCallback
& callback
,
145 const ProgressCallback
& progress_callback
) {
146 DCHECK(thread_checker_
.CalledOnValidThread());
147 DCHECK(!parent_resource_id
.empty());
148 DCHECK(!local_file_path
.empty());
149 DCHECK(!title
.empty());
150 DCHECK(!content_type
.empty());
151 DCHECK(!callback
.is_null());
153 return StartUploadFile(
154 scoped_ptr
<UploadFileInfo
>(new UploadFileInfo(
155 local_file_path
, content_type
, callback
, progress_callback
)),
156 base::Bind(&DriveUploader::CallUploadServiceAPINewFile
,
157 weak_ptr_factory_
.GetWeakPtr(), parent_resource_id
, title
,
161 CancelCallback
DriveUploader::UploadExistingFile(
162 const std::string
& resource_id
,
163 const base::FilePath
& local_file_path
,
164 const std::string
& content_type
,
165 const UploadExistingFileOptions
& options
,
166 const UploadCompletionCallback
& callback
,
167 const ProgressCallback
& progress_callback
) {
168 DCHECK(thread_checker_
.CalledOnValidThread());
169 DCHECK(!resource_id
.empty());
170 DCHECK(!local_file_path
.empty());
171 DCHECK(!content_type
.empty());
172 DCHECK(!callback
.is_null());
174 return StartUploadFile(
175 scoped_ptr
<UploadFileInfo
>(new UploadFileInfo(
176 local_file_path
, content_type
, callback
, progress_callback
)),
177 base::Bind(&DriveUploader::CallUploadServiceAPIExistingFile
,
178 weak_ptr_factory_
.GetWeakPtr(), resource_id
, options
));
181 CancelCallback
DriveUploader::ResumeUploadFile(
182 const GURL
& upload_location
,
183 const base::FilePath
& local_file_path
,
184 const std::string
& content_type
,
185 const UploadCompletionCallback
& callback
,
186 const ProgressCallback
& progress_callback
) {
187 DCHECK(thread_checker_
.CalledOnValidThread());
188 DCHECK(!local_file_path
.empty());
189 DCHECK(!content_type
.empty());
190 DCHECK(!callback
.is_null());
192 scoped_ptr
<UploadFileInfo
> upload_file_info(new UploadFileInfo(
193 local_file_path
, content_type
,
194 callback
, progress_callback
));
195 upload_file_info
->upload_location
= upload_location
;
197 return StartUploadFile(
198 upload_file_info
.Pass(),
199 base::Bind(&DriveUploader::StartGetUploadStatus
,
200 weak_ptr_factory_
.GetWeakPtr()));
203 CancelCallback
DriveUploader::StartUploadFile(
204 scoped_ptr
<UploadFileInfo
> upload_file_info
,
205 const StartInitiateUploadCallback
& start_initiate_upload_callback
) {
206 DCHECK(thread_checker_
.CalledOnValidThread());
207 DVLOG(1) << "Uploading file: " << upload_file_info
->DebugString();
209 UploadFileInfo
* info_ptr
= upload_file_info
.get();
210 base::PostTaskAndReplyWithResult(
211 blocking_task_runner_
.get(),
213 base::Bind(&base::GetFileSize
,
215 &info_ptr
->content_length
),
216 base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize
,
217 weak_ptr_factory_
.GetWeakPtr(),
218 base::Passed(&upload_file_info
),
219 start_initiate_upload_callback
));
220 return info_ptr
->GetCancelCallback();
223 void DriveUploader::StartUploadFileAfterGetFileSize(
224 scoped_ptr
<UploadFileInfo
> upload_file_info
,
225 const StartInitiateUploadCallback
& start_initiate_upload_callback
,
226 bool get_file_size_result
) {
227 DCHECK(thread_checker_
.CalledOnValidThread());
229 if (!get_file_size_result
) {
230 UploadFailed(upload_file_info
.Pass(), HTTP_NOT_FOUND
);
233 DCHECK_GE(upload_file_info
->content_length
, 0);
235 if (upload_file_info
->cancelled
) {
236 UploadFailed(upload_file_info
.Pass(), DRIVE_CANCELLED
);
239 start_initiate_upload_callback
.Run(upload_file_info
.Pass());
242 void DriveUploader::CallUploadServiceAPINewFile(
243 const std::string
& parent_resource_id
,
244 const std::string
& title
,
245 const UploadNewFileOptions
& options
,
246 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
247 DCHECK(thread_checker_
.CalledOnValidThread());
249 UploadFileInfo
* const info_ptr
= upload_file_info
.get();
250 if (info_ptr
->content_length
<= kMaxMultipartUploadSize
) {
251 info_ptr
->cancel_callback
= drive_service_
->MultipartUploadNewFile(
252 info_ptr
->content_type
, info_ptr
->content_length
, parent_resource_id
,
253 title
, info_ptr
->file_path
, options
,
254 base::Bind(&DriveUploader::OnMultipartUploadComplete
,
255 weak_ptr_factory_
.GetWeakPtr(),
256 base::Passed(&upload_file_info
)),
257 info_ptr
->progress_callback
);
259 info_ptr
->cancel_callback
= drive_service_
->InitiateUploadNewFile(
260 info_ptr
->content_type
, info_ptr
->content_length
, parent_resource_id
,
261 title
, options
, base::Bind(&DriveUploader::OnUploadLocationReceived
,
262 weak_ptr_factory_
.GetWeakPtr(),
263 base::Passed(&upload_file_info
)));
267 void DriveUploader::CallUploadServiceAPIExistingFile(
268 const std::string
& resource_id
,
269 const UploadExistingFileOptions
& options
,
270 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
271 DCHECK(thread_checker_
.CalledOnValidThread());
273 UploadFileInfo
* const info_ptr
= upload_file_info
.get();
274 if (info_ptr
->content_length
<= kMaxMultipartUploadSize
) {
275 info_ptr
->cancel_callback
= drive_service_
->MultipartUploadExistingFile(
276 info_ptr
->content_type
, info_ptr
->content_length
, resource_id
,
277 info_ptr
->file_path
, options
,
278 base::Bind(&DriveUploader::OnMultipartUploadComplete
,
279 weak_ptr_factory_
.GetWeakPtr(),
280 base::Passed(&upload_file_info
)),
281 info_ptr
->progress_callback
);
283 info_ptr
->cancel_callback
= drive_service_
->InitiateUploadExistingFile(
284 info_ptr
->content_type
, info_ptr
->content_length
, resource_id
, options
,
285 base::Bind(&DriveUploader::OnUploadLocationReceived
,
286 weak_ptr_factory_
.GetWeakPtr(),
287 base::Passed(&upload_file_info
)));
291 void DriveUploader::OnUploadLocationReceived(
292 scoped_ptr
<UploadFileInfo
> upload_file_info
,
293 DriveApiErrorCode code
,
294 const GURL
& upload_location
) {
295 DCHECK(thread_checker_
.CalledOnValidThread());
297 DVLOG(1) << "Got upload location [" << upload_location
.spec()
298 << "] for [" << upload_file_info
->file_path
.value() << "]";
300 if (code
!= HTTP_SUCCESS
) {
301 if (code
== HTTP_PRECONDITION
)
302 code
= HTTP_CONFLICT
; // ETag mismatch.
303 UploadFailed(upload_file_info
.Pass(), code
);
307 upload_file_info
->upload_location
= upload_location
;
308 upload_file_info
->next_start_position
= 0;
309 UploadNextChunk(upload_file_info
.Pass());
312 void DriveUploader::StartGetUploadStatus(
313 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
314 DCHECK(thread_checker_
.CalledOnValidThread());
315 DCHECK(upload_file_info
);
317 UploadFileInfo
* info_ptr
= upload_file_info
.get();
318 info_ptr
->cancel_callback
= drive_service_
->GetUploadStatus(
319 info_ptr
->upload_location
,
320 info_ptr
->content_length
,
321 base::Bind(&DriveUploader::OnUploadRangeResponseReceived
,
322 weak_ptr_factory_
.GetWeakPtr(),
323 base::Passed(&upload_file_info
)));
326 void DriveUploader::UploadNextChunk(
327 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
328 DCHECK(thread_checker_
.CalledOnValidThread());
329 DCHECK(upload_file_info
);
330 DCHECK_GE(upload_file_info
->next_start_position
, 0);
331 DCHECK_LE(upload_file_info
->next_start_position
,
332 upload_file_info
->content_length
);
334 if (upload_file_info
->cancelled
) {
335 UploadFailed(upload_file_info
.Pass(), DRIVE_CANCELLED
);
339 // Limit the size of data uploaded per each request by kUploadChunkSize.
340 const int64 end_position
= std::min(
341 upload_file_info
->content_length
,
342 upload_file_info
->next_start_position
+ kUploadChunkSize
);
344 UploadFileInfo
* info_ptr
= upload_file_info
.get();
345 info_ptr
->cancel_callback
= drive_service_
->ResumeUpload(
346 info_ptr
->upload_location
,
347 info_ptr
->next_start_position
,
349 info_ptr
->content_length
,
350 info_ptr
->content_type
,
352 base::Bind(&DriveUploader::OnUploadRangeResponseReceived
,
353 weak_ptr_factory_
.GetWeakPtr(),
354 base::Passed(&upload_file_info
)),
355 base::Bind(&DriveUploader::OnUploadProgress
,
356 weak_ptr_factory_
.GetWeakPtr(),
357 info_ptr
->progress_callback
,
358 info_ptr
->next_start_position
,
359 info_ptr
->content_length
));
362 void DriveUploader::OnUploadRangeResponseReceived(
363 scoped_ptr
<UploadFileInfo
> upload_file_info
,
364 const UploadRangeResponse
& response
,
365 scoped_ptr
<FileResource
> entry
) {
366 DCHECK(thread_checker_
.CalledOnValidThread());
368 if (response
.code
== HTTP_CREATED
|| response
.code
== HTTP_SUCCESS
) {
369 // When uploading a new file, we expect HTTP_CREATED, and when uploading
370 // an existing file (to overwrite), we expect HTTP_SUCCESS.
371 // There is an exception: if we uploading an empty file, uploading a new
372 // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the
373 // fix should be uploading the metadata only. However, to keep the
374 // compatibility with GData WAPI during the migration period, we just
375 // relax the condition here.
376 // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI
378 DVLOG(1) << "Successfully created uploaded file=["
379 << upload_file_info
->file_path
.value() << "]";
382 upload_file_info
->completion_callback
.Run(
383 HTTP_SUCCESS
, GURL(), entry
.Pass());
388 if (response
.code
== HTTP_PRECONDITION
) {
389 UploadFailed(upload_file_info
.Pass(), HTTP_CONFLICT
);
393 // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0
394 // (meaning that the data is uploaded from the beginning of the file),
395 // proceed to upload the next chunk.
396 if (response
.code
!= HTTP_RESUME_INCOMPLETE
||
397 response
.start_position_received
!= 0) {
399 << "UploadNextChunk http code=" << response
.code
400 << ", start_position_received=" << response
.start_position_received
401 << ", end_position_received=" << response
.end_position_received
;
403 upload_file_info
.Pass(),
404 response
.code
== HTTP_FORBIDDEN
? DRIVE_NO_SPACE
: response
.code
);
408 DVLOG(1) << "Received range " << response
.start_position_received
409 << "-" << response
.end_position_received
410 << " for [" << upload_file_info
->file_path
.value() << "]";
412 upload_file_info
->next_start_position
= response
.end_position_received
;
413 UploadNextChunk(upload_file_info
.Pass());
416 void DriveUploader::OnUploadProgress(const ProgressCallback
& callback
,
417 int64 start_position
,
419 int64 progress_of_chunk
,
420 int64 total_of_chunk
) {
421 if (!callback
.is_null())
422 callback
.Run(start_position
+ progress_of_chunk
, total_size
);
425 void DriveUploader::UploadFailed(scoped_ptr
<UploadFileInfo
> upload_file_info
,
426 DriveApiErrorCode error
) {
427 DCHECK(thread_checker_
.CalledOnValidThread());
429 DVLOG(1) << "Upload failed " << upload_file_info
->DebugString();
431 if (upload_file_info
->next_start_position
< 0) {
432 // Discard the upload location because no request could succeed with it.
433 // Maybe it's obsolete.
434 upload_file_info
->upload_location
= GURL();
437 upload_file_info
->completion_callback
.Run(
438 error
, upload_file_info
->upload_location
, scoped_ptr
<FileResource
>());
441 void DriveUploader::OnMultipartUploadComplete(
442 scoped_ptr
<UploadFileInfo
> upload_file_info
,
443 google_apis::DriveApiErrorCode error
,
444 scoped_ptr
<FileResource
> entry
) {
445 DCHECK(thread_checker_
.CalledOnValidThread());
447 if (error
== HTTP_CREATED
|| error
== HTTP_SUCCESS
) {
448 DVLOG(1) << "Successfully created uploaded file=["
449 << upload_file_info
->file_path
.value() << "]";
451 upload_file_info
->completion_callback
.Run(
452 HTTP_SUCCESS
, upload_file_info
->upload_location
, entry
.Pass());
454 DVLOG(1) << "Upload failed " << upload_file_info
->DebugString();
455 if (error
== HTTP_PRECONDITION
)
456 error
= HTTP_CONFLICT
; // ETag mismatch.
457 upload_file_info
->completion_callback
.Run(
458 error
, upload_file_info
->upload_location
, scoped_ptr
<FileResource
>());