OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome/browser/drive/drive_uploader.h" | |
6 | |
7 #include <algorithm> | |
8 | |
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 "chrome/browser/drive/drive_service_interface.h" | |
16 #include "content/public/browser/power_save_blocker.h" | |
17 #include "google_apis/drive/drive_api_parser.h" | |
18 | |
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; | |
33 | |
34 namespace drive { | |
35 | |
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 | |
46 | |
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 | |
54 }; | |
55 | |
56 void RecordDriveUploadProtocol(DriveUploadProtocol protocol) { | |
57 UMA_HISTOGRAM_ENUMERATION( | |
58 "Drive.UploadProtocol", protocol, UPLOAD_METHOD_MAX_VALUE); | |
59 } | |
60 } // namespace | |
61 | |
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()) {} | |
74 | |
75 // Gets pointer of BatchRequestConfiguratorInterface owned by the instance. | |
76 BatchRequestConfiguratorInterface* configurator() const { | |
77 return configurator_.get(); | |
78 } | |
79 | |
80 private: | |
81 friend class base::RefCounted<RefCountedBatchRequest>; | |
82 ~RefCountedBatchRequest() { configurator_->Commit(); } | |
83 scoped_ptr<BatchRequestConfiguratorInterface> configurator_; | |
84 }; | |
85 | |
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) {} | |
105 | |
106 ~UploadFileInfo() { | |
107 } | |
108 | |
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 "]"; | |
115 } | |
116 | |
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()); | |
120 } | |
121 | |
122 // The local file path of the file to be uploaded. | |
123 const base::FilePath file_path; | |
124 | |
125 // Content-Type of file. | |
126 const std::string content_type; | |
127 | |
128 // Callback to be invoked once the upload has finished. | |
129 const UploadCompletionCallback completion_callback; | |
130 | |
131 // Callback to periodically notify the upload progress. | |
132 const ProgressCallback progress_callback; | |
133 | |
134 // Location URL where file is to be uploaded to, returned from | |
135 // InitiateUpload. Used for the subsequent ResumeUpload requests. | |
136 GURL upload_location; | |
137 | |
138 // Header content-Length. | |
139 int64 content_length; | |
140 | |
141 int64 next_start_position; | |
142 | |
143 // Blocks system suspend while upload is in progress. | |
144 scoped_ptr<content::PowerSaveBlocker> power_save_blocker; | |
145 | |
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; | |
154 | |
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(); | |
161 } | |
162 | |
163 base::WeakPtrFactory<UploadFileInfo> weak_ptr_factory_; | |
164 DISALLOW_COPY_AND_ASSIGN(UploadFileInfo); | |
165 }; | |
166 | |
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) { | |
173 } | |
174 | |
175 DriveUploader::~DriveUploader() {} | |
176 | |
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()); | |
191 | |
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_)); | |
198 } | |
199 | |
200 void DriveUploader::StartBatchProcessing() { | |
201 DCHECK(current_batch_request_ == nullptr); | |
202 current_batch_request_ = | |
203 new RefCountedBatchRequest(drive_service_->StartBatchRequest().Pass()); | |
204 } | |
205 | |
206 void DriveUploader::StopBatchProcessing() { | |
207 current_batch_request_ = nullptr; | |
208 } | |
209 | |
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()); | |
222 | |
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_)); | |
229 } | |
230 | |
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()); | |
241 | |
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; | |
245 | |
246 return StartUploadFile( | |
247 upload_file_info.Pass(), | |
248 base::Bind(&DriveUploader::StartGetUploadStatus, | |
249 weak_ptr_factory_.GetWeakPtr())); | |
250 } | |
251 | |
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(); | |
257 | |
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(); | |
270 } | |
271 | |
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()); | |
277 | |
278 if (!get_file_size_result) { | |
279 UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND); | |
280 return; | |
281 } | |
282 DCHECK_GE(upload_file_info->content_length, 0); | |
283 | |
284 if (upload_file_info->cancelled) { | |
285 UploadFailed(upload_file_info.Pass(), DRIVE_CANCELLED); | |
286 return; | |
287 } | |
288 start_initiate_upload_callback.Run(upload_file_info.Pass()); | |
289 } | |
290 | |
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()); | |
298 | |
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); | |
309 } | |
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))); | |
324 } | |
325 } | |
326 | |
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()); | |
333 | |
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); | |
344 } | |
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))); | |
359 } | |
360 } | |
361 | |
362 void DriveUploader::OnUploadLocationReceived( | |
363 scoped_ptr<UploadFileInfo> upload_file_info, | |
364 DriveApiErrorCode code, | |
365 const GURL& upload_location) { | |
366 DCHECK(thread_checker_.CalledOnValidThread()); | |
367 | |
368 DVLOG(1) << "Got upload location [" << upload_location.spec() | |
369 << "] for [" << upload_file_info->file_path.value() << "]"; | |
370 | |
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; | |
376 } | |
377 | |
378 upload_file_info->upload_location = upload_location; | |
379 upload_file_info->next_start_position = 0; | |
380 UploadNextChunk(upload_file_info.Pass()); | |
381 } | |
382 | |
383 void DriveUploader::StartGetUploadStatus( | |
384 scoped_ptr<UploadFileInfo> upload_file_info) { | |
385 DCHECK(thread_checker_.CalledOnValidThread()); | |
386 DCHECK(upload_file_info); | |
387 | |
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))); | |
395 } | |
396 | |
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); | |
404 | |
405 if (upload_file_info->cancelled) { | |
406 UploadFailed(upload_file_info.Pass(), DRIVE_CANCELLED); | |
407 return; | |
408 } | |
409 | |
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); | |
414 | |
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)); | |
431 } | |
432 | |
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()); | |
438 | |
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() << "]"; | |
451 | |
452 // Done uploading. | |
453 upload_file_info->completion_callback.Run( | |
454 HTTP_SUCCESS, GURL(), entry.Pass()); | |
455 return; | |
456 } | |
457 | |
458 // ETag mismatch. | |
459 if (response.code == HTTP_PRECONDITION) { | |
460 UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT); | |
461 return; | |
462 } | |
463 | |
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; | |
477 } | |
478 | |
479 DVLOG(1) << "Received range " << response.start_position_received | |
480 << "-" << response.end_position_received | |
481 << " for [" << upload_file_info->file_path.value() << "]"; | |
482 | |
483 upload_file_info->next_start_position = response.end_position_received; | |
484 UploadNextChunk(upload_file_info.Pass()); | |
485 } | |
486 | |
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); | |
494 } | |
495 | |
496 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info, | |
497 DriveApiErrorCode error) { | |
498 DCHECK(thread_checker_.CalledOnValidThread()); | |
499 | |
500 DVLOG(1) << "Upload failed " << upload_file_info->DebugString(); | |
501 | |
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(); | |
506 } | |
507 | |
508 upload_file_info->completion_callback.Run( | |
509 error, upload_file_info->upload_location, scoped_ptr<FileResource>()); | |
510 } | |
511 | |
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()); | |
517 | |
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>()); | |
530 } | |
531 } | |
532 | |
533 } // namespace drive | |
OLD | NEW |