drive_uploader.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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/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 "chrome/browser/google_apis/gdata_wapi_parser.h" 16#include "content/public/browser/browser_thread.h" 17#include "content/public/browser/power_save_blocker.h" 18 19using content::BrowserThread; 20using google_apis::CancelCallback; 21using google_apis::GDATA_CANCELLED; 22using google_apis::GDataErrorCode; 23using google_apis::GDATA_NO_SPACE; 24using google_apis::HTTP_CONFLICT; 25using google_apis::HTTP_CREATED; 26using google_apis::HTTP_FORBIDDEN; 27using google_apis::HTTP_NOT_FOUND; 28using google_apis::HTTP_PRECONDITION; 29using google_apis::HTTP_RESUME_INCOMPLETE; 30using google_apis::HTTP_SUCCESS; 31using google_apis::ProgressCallback; 32using google_apis::ResourceEntry; 33using google_apis::UploadRangeResponse; 34 35namespace drive { 36 37namespace { 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). 43const int64 kUploadChunkSize = (1LL << 30); // 1GB 44} // namespace 45 46// Structure containing current upload information of file, passed between 47// DriveServiceInterface methods and callbacks. 48struct 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), 57 content_length(0), 58 next_start_position(-1), 59 power_save_blocker(content::PowerSaveBlocker::Create( 60 content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension, 61 "Upload in progress")), 62 cancelled(false), 63 weak_ptr_factory_(this) { 64 } 65 66 ~UploadFileInfo() { 67 } 68 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) + 74 "]"; 75 } 76 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()); 80 } 81 82 // The local file path of the file to be uploaded. 83 const base::FilePath file_path; 84 85 // Content-Type of file. 86 const std::string content_type; 87 88 // Callback to be invoked once the upload has finished. 89 const UploadCompletionCallback completion_callback; 90 91 // Callback to periodically notify the upload progress. 92 const ProgressCallback progress_callback; 93 94 // Location URL where file is to be uploaded to, returned from 95 // InitiateUpload. Used for the subsequent ResumeUpload requests. 96 GURL upload_location; 97 98 // Header content-Length. 99 int64 content_length; 100 101 int64 next_start_position; 102 103 // Blocks system suspend while upload is in progress. 104 scoped_ptr<content::PowerSaveBlocker> power_save_blocker; 105 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; 113 bool cancelled; 114 115 private: 116 // Cancels the upload represented by this struct. 117 void Cancel() { 118 cancelled = true; 119 if (!cancel_callback.is_null()) 120 cancel_callback.Run(); 121 } 122 123 base::WeakPtrFactory<UploadFileInfo> weak_ptr_factory_; 124 DISALLOW_COPY_AND_ASSIGN(UploadFileInfo); 125}; 126 127DriveUploader::DriveUploader(DriveServiceInterface* drive_service, 128 base::TaskRunner* blocking_task_runner) 129 : drive_service_(drive_service), 130 blocking_task_runner_(blocking_task_runner), 131 weak_ptr_factory_(this) { 132} 133 134DriveUploader::~DriveUploader() {} 135 136CancelCallback DriveUploader::UploadNewFile( 137 const std::string& parent_resource_id, 138 const base::FilePath& local_file_path, 139 const std::string& title, 140 const std::string& content_type, 141 const UploadCompletionCallback& callback, 142 const ProgressCallback& progress_callback) { 143 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 144 DCHECK(!parent_resource_id.empty()); 145 DCHECK(!local_file_path.empty()); 146 DCHECK(!title.empty()); 147 DCHECK(!content_type.empty()); 148 DCHECK(!callback.is_null()); 149 150 return StartUploadFile( 151 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path, 152 content_type, 153 callback, 154 progress_callback)), 155 base::Bind(&DriveUploader::StartInitiateUploadNewFile, 156 weak_ptr_factory_.GetWeakPtr(), 157 parent_resource_id, 158 title)); 159} 160 161CancelCallback DriveUploader::UploadExistingFile( 162 const std::string& resource_id, 163 const base::FilePath& local_file_path, 164 const std::string& content_type, 165 const std::string& etag, 166 const UploadCompletionCallback& callback, 167 const ProgressCallback& progress_callback) { 168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 169 DCHECK(!resource_id.empty()); 170 DCHECK(!local_file_path.empty()); 171 DCHECK(!content_type.empty()); 172 DCHECK(!callback.is_null()); 173 174 return StartUploadFile( 175 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path, 176 content_type, 177 callback, 178 progress_callback)), 179 base::Bind(&DriveUploader::StartInitiateUploadExistingFile, 180 weak_ptr_factory_.GetWeakPtr(), 181 resource_id, 182 etag)); 183} 184 185CancelCallback DriveUploader::ResumeUploadFile( 186 const GURL& upload_location, 187 const base::FilePath& local_file_path, 188 const std::string& content_type, 189 const UploadCompletionCallback& callback, 190 const ProgressCallback& progress_callback) { 191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 192 DCHECK(!local_file_path.empty()); 193 DCHECK(!content_type.empty()); 194 DCHECK(!callback.is_null()); 195 196 scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo( 197 local_file_path, content_type, 198 callback, progress_callback)); 199 upload_file_info->upload_location = upload_location; 200 201 return StartUploadFile( 202 upload_file_info.Pass(), 203 base::Bind(&DriveUploader::StartGetUploadStatus, 204 weak_ptr_factory_.GetWeakPtr())); 205} 206 207CancelCallback DriveUploader::StartUploadFile( 208 scoped_ptr<UploadFileInfo> upload_file_info, 209 const StartInitiateUploadCallback& start_initiate_upload_callback) { 210 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 211 DVLOG(1) << "Uploading file: " << upload_file_info->DebugString(); 212 213 UploadFileInfo* info_ptr = upload_file_info.get(); 214 base::PostTaskAndReplyWithResult( 215 blocking_task_runner_.get(), 216 FROM_HERE, 217 base::Bind(&file_util::GetFileSize, 218 info_ptr->file_path, 219 &info_ptr->content_length), 220 base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize, 221 weak_ptr_factory_.GetWeakPtr(), 222 base::Passed(&upload_file_info), 223 start_initiate_upload_callback)); 224 return info_ptr->GetCancelCallback(); 225} 226 227void DriveUploader::StartUploadFileAfterGetFileSize( 228 scoped_ptr<UploadFileInfo> upload_file_info, 229 const StartInitiateUploadCallback& start_initiate_upload_callback, 230 bool get_file_size_result) { 231 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 232 233 if (!get_file_size_result) { 234 UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND); 235 return; 236 } 237 DCHECK_GE(upload_file_info->content_length, 0); 238 239 if (upload_file_info->cancelled) { 240 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED); 241 return; 242 } 243 start_initiate_upload_callback.Run(upload_file_info.Pass()); 244} 245 246void DriveUploader::StartInitiateUploadNewFile( 247 const std::string& parent_resource_id, 248 const std::string& title, 249 scoped_ptr<UploadFileInfo> upload_file_info) { 250 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 251 252 UploadFileInfo* info_ptr = upload_file_info.get(); 253 info_ptr->cancel_callback = drive_service_->InitiateUploadNewFile( 254 info_ptr->content_type, 255 info_ptr->content_length, 256 parent_resource_id, 257 title, 258 base::Bind(&DriveUploader::OnUploadLocationReceived, 259 weak_ptr_factory_.GetWeakPtr(), 260 base::Passed(&upload_file_info))); 261} 262 263void DriveUploader::StartInitiateUploadExistingFile( 264 const std::string& resource_id, 265 const std::string& etag, 266 scoped_ptr<UploadFileInfo> upload_file_info) { 267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 268 269 UploadFileInfo* info_ptr = upload_file_info.get(); 270 info_ptr->cancel_callback = drive_service_->InitiateUploadExistingFile( 271 info_ptr->content_type, 272 info_ptr->content_length, 273 resource_id, 274 etag, 275 base::Bind(&DriveUploader::OnUploadLocationReceived, 276 weak_ptr_factory_.GetWeakPtr(), 277 base::Passed(&upload_file_info))); 278} 279 280void DriveUploader::OnUploadLocationReceived( 281 scoped_ptr<UploadFileInfo> upload_file_info, 282 GDataErrorCode code, 283 const GURL& upload_location) { 284 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 285 286 DVLOG(1) << "Got upload location [" << upload_location.spec() 287 << "] for [" << upload_file_info->file_path.value() << "]"; 288 289 if (code != HTTP_SUCCESS) { 290 if (code == HTTP_PRECONDITION) 291 code = HTTP_CONFLICT; // ETag mismatch. 292 UploadFailed(upload_file_info.Pass(), code); 293 return; 294 } 295 296 upload_file_info->upload_location = upload_location; 297 upload_file_info->next_start_position = 0; 298 UploadNextChunk(upload_file_info.Pass()); 299} 300 301void DriveUploader::StartGetUploadStatus( 302 scoped_ptr<UploadFileInfo> upload_file_info) { 303 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 304 DCHECK(upload_file_info); 305 306 UploadFileInfo* info_ptr = upload_file_info.get(); 307 info_ptr->cancel_callback = drive_service_->GetUploadStatus( 308 info_ptr->upload_location, 309 info_ptr->content_length, 310 base::Bind(&DriveUploader::OnUploadRangeResponseReceived, 311 weak_ptr_factory_.GetWeakPtr(), 312 base::Passed(&upload_file_info))); 313} 314 315void DriveUploader::UploadNextChunk( 316 scoped_ptr<UploadFileInfo> upload_file_info) { 317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 318 DCHECK(upload_file_info); 319 DCHECK_GE(upload_file_info->next_start_position, 0); 320 DCHECK_LE(upload_file_info->next_start_position, 321 upload_file_info->content_length); 322 323 if (upload_file_info->cancelled) { 324 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED); 325 return; 326 } 327 328 // Limit the size of data uploaded per each request by kUploadChunkSize. 329 const int64 end_position = std::min( 330 upload_file_info->content_length, 331 upload_file_info->next_start_position + kUploadChunkSize); 332 333 UploadFileInfo* info_ptr = upload_file_info.get(); 334 info_ptr->cancel_callback = drive_service_->ResumeUpload( 335 info_ptr->upload_location, 336 info_ptr->next_start_position, 337 end_position, 338 info_ptr->content_length, 339 info_ptr->content_type, 340 info_ptr->file_path, 341 base::Bind(&DriveUploader::OnUploadRangeResponseReceived, 342 weak_ptr_factory_.GetWeakPtr(), 343 base::Passed(&upload_file_info)), 344 base::Bind(&DriveUploader::OnUploadProgress, 345 weak_ptr_factory_.GetWeakPtr(), 346 info_ptr->progress_callback, 347 info_ptr->next_start_position, 348 info_ptr->content_length)); 349} 350 351void DriveUploader::OnUploadRangeResponseReceived( 352 scoped_ptr<UploadFileInfo> upload_file_info, 353 const UploadRangeResponse& response, 354 scoped_ptr<ResourceEntry> entry) { 355 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 356 357 if (response.code == HTTP_CREATED || response.code == HTTP_SUCCESS) { 358 // When uploading a new file, we expect HTTP_CREATED, and when uploading 359 // an existing file (to overwrite), we expect HTTP_SUCCESS. 360 // There is an exception: if we uploading an empty file, uploading a new 361 // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the 362 // fix should be uploading the metadata only. However, to keep the 363 // compatibility with GData WAPI during the migration period, we just 364 // relax the condition here. 365 // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI 366 // code is gone. 367 DVLOG(1) << "Successfully created uploaded file=[" 368 << upload_file_info->file_path.value() << "]"; 369 370 // Done uploading. 371 upload_file_info->completion_callback.Run( 372 HTTP_SUCCESS, GURL(), entry.Pass()); 373 return; 374 } 375 376 // ETag mismatch. 377 if (response.code == HTTP_PRECONDITION) { 378 UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT); 379 return; 380 } 381 382 // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0 383 // (meaning that the data is uploaded from the beginning of the file), 384 // proceed to upload the next chunk. 385 if (response.code != HTTP_RESUME_INCOMPLETE || 386 response.start_position_received != 0) { 387 DVLOG(1) 388 << "UploadNextChunk http code=" << response.code 389 << ", start_position_received=" << response.start_position_received 390 << ", end_position_received=" << response.end_position_received; 391 UploadFailed( 392 upload_file_info.Pass(), 393 response.code == HTTP_FORBIDDEN ? GDATA_NO_SPACE : response.code); 394 return; 395 } 396 397 DVLOG(1) << "Received range " << response.start_position_received 398 << "-" << response.end_position_received 399 << " for [" << upload_file_info->file_path.value() << "]"; 400 401 upload_file_info->next_start_position = response.end_position_received; 402 UploadNextChunk(upload_file_info.Pass()); 403} 404 405void DriveUploader::OnUploadProgress(const ProgressCallback& callback, 406 int64 start_position, 407 int64 total_size, 408 int64 progress_of_chunk, 409 int64 total_of_chunk) { 410 if (!callback.is_null()) 411 callback.Run(start_position + progress_of_chunk, total_size); 412} 413 414void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info, 415 GDataErrorCode error) { 416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 417 418 DVLOG(1) << "Upload failed " << upload_file_info->DebugString(); 419 420 if (upload_file_info->next_start_position < 0) { 421 // Discard the upload location because no request could succeed with it. 422 // Maybe it's obsolete. 423 upload_file_info->upload_location = GURL(); 424 } 425 426 upload_file_info->completion_callback.Run( 427 error, upload_file_info->upload_location, scoped_ptr<ResourceEntry>()); 428} 429 430} // namespace drive 431