drive_uploader.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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 // TODO(achuith): Handle error codes from Google Docs server. 291 if (code == HTTP_PRECONDITION) { 292 // ETag mismatch. 293 UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT); 294 return; 295 } 296 UploadFailed(upload_file_info.Pass(), code); 297 return; 298 } 299 300 upload_file_info->upload_location = upload_location; 301 upload_file_info->next_start_position = 0; 302 UploadNextChunk(upload_file_info.Pass()); 303} 304 305void DriveUploader::StartGetUploadStatus( 306 scoped_ptr<UploadFileInfo> upload_file_info) { 307 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 308 DCHECK(upload_file_info); 309 310 UploadFileInfo* info_ptr = upload_file_info.get(); 311 info_ptr->cancel_callback = drive_service_->GetUploadStatus( 312 info_ptr->upload_location, 313 info_ptr->content_length, 314 base::Bind(&DriveUploader::OnUploadRangeResponseReceived, 315 weak_ptr_factory_.GetWeakPtr(), 316 base::Passed(&upload_file_info))); 317} 318 319void DriveUploader::UploadNextChunk( 320 scoped_ptr<UploadFileInfo> upload_file_info) { 321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 322 DCHECK(upload_file_info); 323 DCHECK_GE(upload_file_info->next_start_position, 0); 324 DCHECK_LE(upload_file_info->next_start_position, 325 upload_file_info->content_length); 326 327 if (upload_file_info->cancelled) { 328 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED); 329 return; 330 } 331 332 // Limit the size of data uploaded per each request by kUploadChunkSize. 333 const int64 end_position = std::min( 334 upload_file_info->content_length, 335 upload_file_info->next_start_position + kUploadChunkSize); 336 337 UploadFileInfo* info_ptr = upload_file_info.get(); 338 info_ptr->cancel_callback = drive_service_->ResumeUpload( 339 info_ptr->upload_location, 340 info_ptr->next_start_position, 341 end_position, 342 info_ptr->content_length, 343 info_ptr->content_type, 344 info_ptr->file_path, 345 base::Bind(&DriveUploader::OnUploadRangeResponseReceived, 346 weak_ptr_factory_.GetWeakPtr(), 347 base::Passed(&upload_file_info)), 348 base::Bind(&DriveUploader::OnUploadProgress, 349 weak_ptr_factory_.GetWeakPtr(), 350 info_ptr->progress_callback, 351 info_ptr->next_start_position, 352 info_ptr->content_length)); 353} 354 355void DriveUploader::OnUploadRangeResponseReceived( 356 scoped_ptr<UploadFileInfo> upload_file_info, 357 const UploadRangeResponse& response, 358 scoped_ptr<ResourceEntry> entry) { 359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 360 361 if (response.code == HTTP_CREATED || response.code == HTTP_SUCCESS) { 362 // When uploading a new file, we expect HTTP_CREATED, and when uploading 363 // an existing file (to overwrite), we expect HTTP_SUCCESS. 364 // There is an exception: if we uploading an empty file, uploading a new 365 // file also returns HTTP_SUCCESS on Drive API v2. The correct way of the 366 // fix should be uploading the metadata only. However, to keep the 367 // compatibility with GData WAPI during the migration period, we just 368 // relax the condition here. 369 // TODO(hidehiko): Upload metadata only for empty files, after GData WAPI 370 // code is gone. 371 DVLOG(1) << "Successfully created uploaded file=[" 372 << upload_file_info->file_path.value() << "]"; 373 374 // Done uploading. 375 upload_file_info->completion_callback.Run( 376 HTTP_SUCCESS, GURL(), entry.Pass()); 377 return; 378 } 379 380 // ETag mismatch. 381 if (response.code == HTTP_PRECONDITION) { 382 UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT); 383 return; 384 } 385 386 // If code is 308 (RESUME_INCOMPLETE) and |range_received| starts with 0 387 // (meaning that the data is uploaded from the beginning of the file), 388 // proceed to upload the next chunk. 389 if (response.code != HTTP_RESUME_INCOMPLETE || 390 response.start_position_received != 0) { 391 DVLOG(1) 392 << "UploadNextChunk http code=" << response.code 393 << ", start_position_received=" << response.start_position_received 394 << ", end_position_received=" << response.end_position_received; 395 UploadFailed( 396 upload_file_info.Pass(), 397 response.code == HTTP_FORBIDDEN ? GDATA_NO_SPACE : response.code); 398 return; 399 } 400 401 DVLOG(1) << "Received range " << response.start_position_received 402 << "-" << response.end_position_received 403 << " for [" << upload_file_info->file_path.value() << "]"; 404 405 upload_file_info->next_start_position = response.end_position_received; 406 UploadNextChunk(upload_file_info.Pass()); 407} 408 409void DriveUploader::OnUploadProgress(const ProgressCallback& callback, 410 int64 start_position, 411 int64 total_size, 412 int64 progress_of_chunk, 413 int64 total_of_chunk) { 414 if (!callback.is_null()) 415 callback.Run(start_position + progress_of_chunk, total_size); 416} 417 418void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info, 419 GDataErrorCode error) { 420 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 421 422 DVLOG(1) << "Upload failed " << upload_file_info->DebugString(); 423 424 if (upload_file_info->next_start_position < 0) { 425 // Discard the upload location because no request could succeed with it. 426 // Maybe it's obsolete. 427 upload_file_info->upload_location = GURL(); 428 } 429 430 upload_file_info->completion_callback.Run( 431 error, upload_file_info->upload_location, scoped_ptr<ResourceEntry>()); 432} 433 434} // namespace drive 435