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/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" 18 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; 34 35 namespace drive { 36 37 namespace { 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 44 } // namespace 45 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), 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 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) { 133 } 134 135 DriveUploader::~DriveUploader() {} 136 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()); 151 152 return StartUploadFile( 153 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path, 154 content_type, 155 callback, 156 progress_callback)), 157 base::Bind(&DriveUploader::StartInitiateUploadNewFile, 158 weak_ptr_factory_.GetWeakPtr(), 159 parent_resource_id, 160 title, 161 options)); 162 } 163 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()); 176 177 return StartUploadFile( 178 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path, 179 content_type, 180 callback, 181 progress_callback)), 182 base::Bind(&DriveUploader::StartInitiateUploadExistingFile, 183 weak_ptr_factory_.GetWeakPtr(), 184 resource_id, 185 options)); 186 } 187 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()); 198 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; 203 204 return StartUploadFile( 205 upload_file_info.Pass(), 206 base::Bind(&DriveUploader::StartGetUploadStatus, 207 weak_ptr_factory_.GetWeakPtr())); 208 } 209 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(); 215 216 UploadFileInfo* info_ptr = upload_file_info.get(); 217 base::PostTaskAndReplyWithResult( 218 blocking_task_runner_.get(), 219 FROM_HERE, 220 base::Bind(&base::GetFileSize, 221 info_ptr->file_path, 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(); 228 } 229 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)); 235 236 if (!get_file_size_result) { 237 UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND); 238 return; 239 } 240 DCHECK_GE(upload_file_info->content_length, 0); 241 242 if (upload_file_info->cancelled) { 243 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED); 244 return; 245 } 246 start_initiate_upload_callback.Run(upload_file_info.Pass()); 247 } 248 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)); 255 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, 260 parent_resource_id, 261 title, 262 options, 263 base::Bind(&DriveUploader::OnUploadLocationReceived, 264 weak_ptr_factory_.GetWeakPtr(), 265 base::Passed(&upload_file_info))); 266 } 267 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)); 273 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, 278 resource_id, 279 options, 280 base::Bind(&DriveUploader::OnUploadLocationReceived, 281 weak_ptr_factory_.GetWeakPtr(), 282 base::Passed(&upload_file_info))); 283 } 284 285 void DriveUploader::OnUploadLocationReceived( 286 scoped_ptr<UploadFileInfo> upload_file_info, 287 GDataErrorCode code, 288 const GURL& upload_location) { 289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 290 291 DVLOG(1) << "Got upload location [" << upload_location.spec() 292 << "] for [" << upload_file_info->file_path.value() << "]"; 293 294 if (code != HTTP_SUCCESS) { 295 if (code == HTTP_PRECONDITION) 296 code = HTTP_CONFLICT; // ETag mismatch. 297 UploadFailed(upload_file_info.Pass(), code); 298 return; 299 } 300 301 upload_file_info->upload_location = upload_location; 302 upload_file_info->next_start_position = 0; 303 UploadNextChunk(upload_file_info.Pass()); 304 } 305 306 void DriveUploader::StartGetUploadStatus( 307 scoped_ptr<UploadFileInfo> upload_file_info) { 308 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 309 DCHECK(upload_file_info); 310 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))); 318 } 319 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); 327 328 if (upload_file_info->cancelled) { 329 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED); 330 return; 331 } 332 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); 337 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, 342 end_position, 343 info_ptr->content_length, 344 info_ptr->content_type, 345 info_ptr->file_path, 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)); 354 } 355 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)); 361 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 371 // code is gone. 372 DVLOG(1) << "Successfully created uploaded file=[" 373 << upload_file_info->file_path.value() << "]"; 374 375 // Done uploading. 376 upload_file_info->completion_callback.Run( 377 HTTP_SUCCESS, GURL(), entry.Pass()); 378 return; 379 } 380 381 // ETag mismatch. 382 if (response.code == HTTP_PRECONDITION) { 383 UploadFailed(upload_file_info.Pass(), HTTP_CONFLICT); 384 return; 385 } 386 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) { 392 DVLOG(1) 393 << "UploadNextChunk http code=" << response.code 394 << ", start_position_received=" << response.start_position_received 395 << ", end_position_received=" << response.end_position_received; 396 UploadFailed( 397 upload_file_info.Pass(), 398 response.code == HTTP_FORBIDDEN ? GDATA_NO_SPACE : response.code); 399 return; 400 } 401 402 DVLOG(1) << "Received range " << response.start_position_received 403 << "-" << response.end_position_received 404 << " for [" << upload_file_info->file_path.value() << "]"; 405 406 upload_file_info->next_start_position = response.end_position_received; 407 UploadNextChunk(upload_file_info.Pass()); 408 } 409 410 void DriveUploader::OnUploadProgress(const ProgressCallback& callback, 411 int64 start_position, 412 int64 total_size, 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); 417 } 418 419 void DriveUploader::UploadFailed(scoped_ptr<UploadFileInfo> upload_file_info, 420 GDataErrorCode error) { 421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 422 423 DVLOG(1) << "Upload failed " << upload_file_info->DebugString(); 424 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(); 429 } 430 431 upload_file_info->completion_callback.Run( 432 error, upload_file_info->upload_location, scoped_ptr<FileResource>()); 433 } 434 435 } // namespace drive 436