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/browser_thread.h"
16 #include "content/public/browser/power_save_blocker.h"
17 #include "google_apis/drive/drive_api_parser.h"
19 using content::BrowserThread
;
20 using google_apis::CancelCallback
;
21 using google_apis::FileResource
;
22 using google_apis::GDATA_CANCELLED
;
23 using google_apis::GDataErrorCode
;
24 using google_apis::GDATA_NO_SPACE
;
25 using google_apis::HTTP_CONFLICT
;
26 using google_apis::HTTP_CREATED
;
27 using google_apis::HTTP_FORBIDDEN
;
28 using google_apis::HTTP_NOT_FOUND
;
29 using google_apis::HTTP_PRECONDITION
;
30 using google_apis::HTTP_RESUME_INCOMPLETE
;
31 using google_apis::HTTP_SUCCESS
;
32 using google_apis::ProgressCallback
;
33 using google_apis::UploadRangeResponse
;
38 // Upload data is split to multiple HTTP request each conveying kUploadChunkSize
39 // bytes (except the request for uploading the last chunk of data).
40 // The value must be a multiple of 512KB according to the spec of GData WAPI and
41 // Drive API v2. It is set to a smaller value than 2^31 for working around
42 // server side error (crbug.com/264089).
43 const int64 kUploadChunkSize
= (1LL << 30); // 1GB
46 // Structure containing current upload information of file, passed between
47 // DriveServiceInterface methods and callbacks.
48 struct DriveUploader::UploadFileInfo
{
49 UploadFileInfo(const base::FilePath
& local_path
,
50 const std::string
& content_type
,
51 const UploadCompletionCallback
& callback
,
52 const ProgressCallback
& progress_callback
)
53 : file_path(local_path
),
54 content_type(content_type
),
55 completion_callback(callback
),
56 progress_callback(progress_callback
),
58 next_start_position(-1),
59 power_save_blocker(content::PowerSaveBlocker::Create(
60 content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension
,
61 "Upload in progress")),
63 weak_ptr_factory_(this) {
69 // Useful for printf debugging.
70 std::string
DebugString() const {
71 return "file_path=[" + file_path
.AsUTF8Unsafe() +
72 "], content_type=[" + content_type
+
73 "], content_length=[" + base::UintToString(content_length
) +
77 // Returns the callback to cancel the upload represented by this struct.
78 CancelCallback
GetCancelCallback() {
79 return base::Bind(&UploadFileInfo::Cancel
, weak_ptr_factory_
.GetWeakPtr());
82 // The local file path of the file to be uploaded.
83 const base::FilePath file_path
;
85 // Content-Type of file.
86 const std::string content_type
;
88 // Callback to be invoked once the upload has finished.
89 const UploadCompletionCallback completion_callback
;
91 // Callback to periodically notify the upload progress.
92 const ProgressCallback progress_callback
;
94 // Location URL where file is to be uploaded to, returned from
95 // InitiateUpload. Used for the subsequent ResumeUpload requests.
98 // Header content-Length.
101 int64 next_start_position
;
103 // Blocks system suspend while upload is in progress.
104 scoped_ptr
<content::PowerSaveBlocker
> power_save_blocker
;
106 // Fields for implementing cancellation. |cancel_callback| is non-null if
107 // there is an in-flight HTTP request. In that case, |cancell_callback| will
108 // cancel the operation. |cancelled| is initially false and turns to true
109 // once Cancel() is called. DriveUploader will check this field before after
110 // an async task other than HTTP requests and cancels the subsequent requests
111 // if this is flagged to true.
112 CancelCallback cancel_callback
;
116 // Cancels the upload represented by this struct.
119 if (!cancel_callback
.is_null())
120 cancel_callback
.Run();
123 base::WeakPtrFactory
<UploadFileInfo
> weak_ptr_factory_
;
124 DISALLOW_COPY_AND_ASSIGN(UploadFileInfo
);
127 DriveUploader::DriveUploader(
128 DriveServiceInterface
* drive_service
,
129 const scoped_refptr
<base::TaskRunner
>& blocking_task_runner
)
130 : drive_service_(drive_service
),
131 blocking_task_runner_(blocking_task_runner
),
132 weak_ptr_factory_(this) {
135 DriveUploader::~DriveUploader() {}
137 CancelCallback
DriveUploader::UploadNewFile(
138 const std::string
& parent_resource_id
,
139 const base::FilePath
& local_file_path
,
140 const std::string
& title
,
141 const std::string
& content_type
,
142 const UploadNewFileOptions
& options
,
143 const UploadCompletionCallback
& callback
,
144 const ProgressCallback
& progress_callback
) {
145 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
146 DCHECK(!parent_resource_id
.empty());
147 DCHECK(!local_file_path
.empty());
148 DCHECK(!title
.empty());
149 DCHECK(!content_type
.empty());
150 DCHECK(!callback
.is_null());
152 return StartUploadFile(
153 scoped_ptr
<UploadFileInfo
>(new UploadFileInfo(local_file_path
,
157 base::Bind(&DriveUploader::StartInitiateUploadNewFile
,
158 weak_ptr_factory_
.GetWeakPtr(),
164 CancelCallback
DriveUploader::UploadExistingFile(
165 const std::string
& resource_id
,
166 const base::FilePath
& local_file_path
,
167 const std::string
& content_type
,
168 const UploadExistingFileOptions
& options
,
169 const UploadCompletionCallback
& callback
,
170 const ProgressCallback
& progress_callback
) {
171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
172 DCHECK(!resource_id
.empty());
173 DCHECK(!local_file_path
.empty());
174 DCHECK(!content_type
.empty());
175 DCHECK(!callback
.is_null());
177 return StartUploadFile(
178 scoped_ptr
<UploadFileInfo
>(new UploadFileInfo(local_file_path
,
182 base::Bind(&DriveUploader::StartInitiateUploadExistingFile
,
183 weak_ptr_factory_
.GetWeakPtr(),
188 CancelCallback
DriveUploader::ResumeUploadFile(
189 const GURL
& upload_location
,
190 const base::FilePath
& local_file_path
,
191 const std::string
& content_type
,
192 const UploadCompletionCallback
& callback
,
193 const ProgressCallback
& progress_callback
) {
194 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
195 DCHECK(!local_file_path
.empty());
196 DCHECK(!content_type
.empty());
197 DCHECK(!callback
.is_null());
199 scoped_ptr
<UploadFileInfo
> upload_file_info(new UploadFileInfo(
200 local_file_path
, content_type
,
201 callback
, progress_callback
));
202 upload_file_info
->upload_location
= upload_location
;
204 return StartUploadFile(
205 upload_file_info
.Pass(),
206 base::Bind(&DriveUploader::StartGetUploadStatus
,
207 weak_ptr_factory_
.GetWeakPtr()));
210 CancelCallback
DriveUploader::StartUploadFile(
211 scoped_ptr
<UploadFileInfo
> upload_file_info
,
212 const StartInitiateUploadCallback
& start_initiate_upload_callback
) {
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
214 DVLOG(1) << "Uploading file: " << upload_file_info
->DebugString();
216 UploadFileInfo
* info_ptr
= upload_file_info
.get();
217 base::PostTaskAndReplyWithResult(
218 blocking_task_runner_
.get(),
220 base::Bind(&base::GetFileSize
,
222 &info_ptr
->content_length
),
223 base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize
,
224 weak_ptr_factory_
.GetWeakPtr(),
225 base::Passed(&upload_file_info
),
226 start_initiate_upload_callback
));
227 return info_ptr
->GetCancelCallback();
230 void DriveUploader::StartUploadFileAfterGetFileSize(
231 scoped_ptr
<UploadFileInfo
> upload_file_info
,
232 const StartInitiateUploadCallback
& start_initiate_upload_callback
,
233 bool get_file_size_result
) {
234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
236 if (!get_file_size_result
) {
237 UploadFailed(upload_file_info
.Pass(), HTTP_NOT_FOUND
);
240 DCHECK_GE(upload_file_info
->content_length
, 0);
242 if (upload_file_info
->cancelled
) {
243 UploadFailed(upload_file_info
.Pass(), GDATA_CANCELLED
);
246 start_initiate_upload_callback
.Run(upload_file_info
.Pass());
249 void DriveUploader::StartInitiateUploadNewFile(
250 const std::string
& parent_resource_id
,
251 const std::string
& title
,
252 const UploadNewFileOptions
& options
,
253 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
256 UploadFileInfo
* info_ptr
= upload_file_info
.get();
257 info_ptr
->cancel_callback
= drive_service_
->InitiateUploadNewFile(
258 info_ptr
->content_type
,
259 info_ptr
->content_length
,
263 base::Bind(&DriveUploader::OnUploadLocationReceived
,
264 weak_ptr_factory_
.GetWeakPtr(),
265 base::Passed(&upload_file_info
)));
268 void DriveUploader::StartInitiateUploadExistingFile(
269 const std::string
& resource_id
,
270 const UploadExistingFileOptions
& options
,
271 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
274 UploadFileInfo
* info_ptr
= upload_file_info
.get();
275 info_ptr
->cancel_callback
= drive_service_
->InitiateUploadExistingFile(
276 info_ptr
->content_type
,
277 info_ptr
->content_length
,
280 base::Bind(&DriveUploader::OnUploadLocationReceived
,
281 weak_ptr_factory_
.GetWeakPtr(),
282 base::Passed(&upload_file_info
)));
285 void DriveUploader::OnUploadLocationReceived(
286 scoped_ptr
<UploadFileInfo
> upload_file_info
,
288 const GURL
& upload_location
) {
289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
291 DVLOG(1) << "Got upload location [" << upload_location
.spec()
292 << "] for [" << upload_file_info
->file_path
.value() << "]";
294 if (code
!= HTTP_SUCCESS
) {
295 if (code
== HTTP_PRECONDITION
)
296 code
= HTTP_CONFLICT
; // ETag mismatch.
297 UploadFailed(upload_file_info
.Pass(), code
);
301 upload_file_info
->upload_location
= upload_location
;
302 upload_file_info
->next_start_position
= 0;
303 UploadNextChunk(upload_file_info
.Pass());
306 void DriveUploader::StartGetUploadStatus(
307 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
309 DCHECK(upload_file_info
);
311 UploadFileInfo
* info_ptr
= upload_file_info
.get();
312 info_ptr
->cancel_callback
= drive_service_
->GetUploadStatus(
313 info_ptr
->upload_location
,
314 info_ptr
->content_length
,
315 base::Bind(&DriveUploader::OnUploadRangeResponseReceived
,
316 weak_ptr_factory_
.GetWeakPtr(),
317 base::Passed(&upload_file_info
)));
320 void DriveUploader::UploadNextChunk(
321 scoped_ptr
<UploadFileInfo
> upload_file_info
) {
322 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
323 DCHECK(upload_file_info
);
324 DCHECK_GE(upload_file_info
->next_start_position
, 0);
325 DCHECK_LE(upload_file_info
->next_start_position
,
326 upload_file_info
->content_length
);
328 if (upload_file_info
->cancelled
) {
329 UploadFailed(upload_file_info
.Pass(), GDATA_CANCELLED
);
333 // Limit the size of data uploaded per each request by kUploadChunkSize.
334 const int64 end_position
= std::min(
335 upload_file_info
->content_length
,
336 upload_file_info
->next_start_position
+ kUploadChunkSize
);
338 UploadFileInfo
* info_ptr
= upload_file_info
.get();
339 info_ptr
->cancel_callback
= drive_service_
->ResumeUpload(
340 info_ptr
->upload_location
,
341 info_ptr
->next_start_position
,
343 info_ptr
->content_length
,
344 info_ptr
->content_type
,
346 base::Bind(&DriveUploader::OnUploadRangeResponseReceived
,
347 weak_ptr_factory_
.GetWeakPtr(),
348 base::Passed(&upload_file_info
)),
349 base::Bind(&DriveUploader::OnUploadProgress
,
350 weak_ptr_factory_
.GetWeakPtr(),
351 info_ptr
->progress_callback
,
352 info_ptr
->next_start_position
,
353 info_ptr
->content_length
));
356 void DriveUploader::OnUploadRangeResponseReceived(
357 scoped_ptr
<UploadFileInfo
> upload_file_info
,
358 const UploadRangeResponse
& response
,
359 scoped_ptr
<FileResource
> entry
) {
360 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
362 if (response
.code
== HTTP_CREATED
|| response
.code
== HTTP_SUCCESS
) {
363 // When uploading a new file, we expect HTTP_CREATED, and when uploading
364 // an existing file (to overwrite), we expect HTTP_SUCCESS.
365 // There is an exception: if we uploading an empty file, uploading a new
366 // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the
367 // fix should be uploading the metadata only. However, to keep the
368 // compatibility with GData WAPI during the migration period, we just
369 // relax the condition here.
370 // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI
372 DVLOG(1) << "Successfully created uploaded file=["
373 << upload_file_info
->file_path
.value() << "]";
376 upload_file_info
->completion_callback
.Run(
377 HTTP_SUCCESS
, GURL(), entry
.Pass());
382 if (response
.code
== HTTP_PRECONDITION
) {
383 UploadFailed(upload_file_info
.Pass(), HTTP_CONFLICT
);
387 // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0
388 // (meaning that the data is uploaded from the beginning of the file),
389 // proceed to upload the next chunk.
390 if (response
.code
!= HTTP_RESUME_INCOMPLETE
||
391 response
.start_position_received
!= 0) {
393 << "UploadNextChunk http code=" << response
.code
394 << ", start_position_received=" << response
.start_position_received
395 << ", end_position_received=" << response
.end_position_received
;
397 upload_file_info
.Pass(),
398 response
.code
== HTTP_FORBIDDEN
? GDATA_NO_SPACE
: response
.code
);
402 DVLOG(1) << "Received range " << response
.start_position_received
403 << "-" << response
.end_position_received
404 << " for [" << upload_file_info
->file_path
.value() << "]";
406 upload_file_info
->next_start_position
= response
.end_position_received
;
407 UploadNextChunk(upload_file_info
.Pass());
410 void DriveUploader::OnUploadProgress(const ProgressCallback
& callback
,
411 int64 start_position
,
413 int64 progress_of_chunk
,
414 int64 total_of_chunk
) {
415 if (!callback
.is_null())
416 callback
.Run(start_position
+ progress_of_chunk
, total_size
);
419 void DriveUploader::UploadFailed(scoped_ptr
<UploadFileInfo
> upload_file_info
,
420 GDataErrorCode error
) {
421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
423 DVLOG(1) << "Upload failed " << upload_file_info
->DebugString();
425 if (upload_file_info
->next_start_position
< 0) {
426 // Discard the upload location because no request could succeed with it.
427 // Maybe it's obsolete.
428 upload_file_info
->upload_location
= GURL();
431 upload_file_info
->completion_callback
.Run(
432 error
, upload_file_info
->upload_location
, scoped_ptr
<FileResource
>());