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 "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(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 134 DriveUploader::~DriveUploader() {} 135 136 CancelCallback 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 UploadNewFileOptions& options, 142 const UploadCompletionCallback& callback, 143 const ProgressCallback& progress_callback) { 144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 145 DCHECK(!parent_resource_id.empty()); 146 DCHECK(!local_file_path.empty()); 147 DCHECK(!title.empty()); 148 DCHECK(!content_type.empty()); 149 DCHECK(!callback.is_null()); 150 151 return StartUploadFile( 152 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path, 153 content_type, 154 callback, 155 progress_callback)), 156 base::Bind(&DriveUploader::StartInitiateUploadNewFile, 157 weak_ptr_factory_.GetWeakPtr(), 158 parent_resource_id, 159 title, 160 options)); 161 } 162 163 CancelCallback DriveUploader::UploadExistingFile( 164 const std::string& resource_id, 165 const base::FilePath& local_file_path, 166 const std::string& content_type, 167 const UploadExistingFileOptions& options, 168 const UploadCompletionCallback& callback, 169 const ProgressCallback& progress_callback) { 170 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 171 DCHECK(!resource_id.empty()); 172 DCHECK(!local_file_path.empty()); 173 DCHECK(!content_type.empty()); 174 DCHECK(!callback.is_null()); 175 176 return StartUploadFile( 177 scoped_ptr<UploadFileInfo>(new UploadFileInfo(local_file_path, 178 content_type, 179 callback, 180 progress_callback)), 181 base::Bind(&DriveUploader::StartInitiateUploadExistingFile, 182 weak_ptr_factory_.GetWeakPtr(), 183 resource_id, 184 options)); 185 } 186 187 CancelCallback DriveUploader::ResumeUploadFile( 188 const GURL& upload_location, 189 const base::FilePath& local_file_path, 190 const std::string& content_type, 191 const UploadCompletionCallback& callback, 192 const ProgressCallback& progress_callback) { 193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 194 DCHECK(!local_file_path.empty()); 195 DCHECK(!content_type.empty()); 196 DCHECK(!callback.is_null()); 197 198 scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo( 199 local_file_path, content_type, 200 callback, progress_callback)); 201 upload_file_info->upload_location = upload_location; 202 203 return StartUploadFile( 204 upload_file_info.Pass(), 205 base::Bind(&DriveUploader::StartGetUploadStatus, 206 weak_ptr_factory_.GetWeakPtr())); 207 } 208 209 CancelCallback DriveUploader::StartUploadFile( 210 scoped_ptr<UploadFileInfo> upload_file_info, 211 const StartInitiateUploadCallback& start_initiate_upload_callback) { 212 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 213 DVLOG(1) << "Uploading file: " << upload_file_info->DebugString(); 214 215 UploadFileInfo* info_ptr = upload_file_info.get(); 216 base::PostTaskAndReplyWithResult( 217 blocking_task_runner_.get(), 218 FROM_HERE, 219 base::Bind(&base::GetFileSize, 220 info_ptr->file_path, 221 &info_ptr->content_length), 222 base::Bind(&DriveUploader::StartUploadFileAfterGetFileSize, 223 weak_ptr_factory_.GetWeakPtr(), 224 base::Passed(&upload_file_info), 225 start_initiate_upload_callback)); 226 return info_ptr->GetCancelCallback(); 227 } 228 229 void DriveUploader::StartUploadFileAfterGetFileSize( 230 scoped_ptr<UploadFileInfo> upload_file_info, 231 const StartInitiateUploadCallback& start_initiate_upload_callback, 232 bool get_file_size_result) { 233 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 234 235 if (!get_file_size_result) { 236 UploadFailed(upload_file_info.Pass(), HTTP_NOT_FOUND); 237 return; 238 } 239 DCHECK_GE(upload_file_info->content_length, 0); 240 241 if (upload_file_info->cancelled) { 242 UploadFailed(upload_file_info.Pass(), GDATA_CANCELLED); 243 return; 244 } 245 start_initiate_upload_callback.Run(upload_file_info.Pass()); 246 } 247 248 void DriveUploader::StartInitiateUploadNewFile( 249 const std::string& parent_resource_id, 250 const std::string& title, 251 const UploadNewFileOptions& options, 252 scoped_ptr<UploadFileInfo> upload_file_info) { 253 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 254 255 UploadFileInfo* info_ptr = upload_file_info.get(); 256 info_ptr->cancel_callback = drive_service_->InitiateUploadNewFile( 257 info_ptr->content_type, 258 info_ptr->content_length, 259 parent_resource_id, 260 title, 261 options, 262 base::Bind(&DriveUploader::OnUploadLocationReceived, 263 weak_ptr_factory_.GetWeakPtr(), 264 base::Passed(&upload_file_info))); 265 } 266 267 void DriveUploader::StartInitiateUploadExistingFile( 268 const std::string& resource_id, 269 const UploadExistingFileOptions& options, 270 scoped_ptr<UploadFileInfo> upload_file_info) { 271 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 272 273 UploadFileInfo* info_ptr = upload_file_info.get(); 274 info_ptr->cancel_callback = drive_service_->InitiateUploadExistingFile( 275 info_ptr->content_type, 276 info_ptr->content_length, 277 resource_id, 278 options, 279 base::Bind(&DriveUploader::OnUploadLocationReceived, 280 weak_ptr_factory_.GetWeakPtr(), 281 base::Passed(&upload_file_info))); 282 } 283 284 void DriveUploader::OnUploadLocationReceived( 285 scoped_ptr<UploadFileInfo> upload_file_info, 286 GDataErrorCode code, 287 const GURL& upload_location) { 288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 289 290 DVLOG(1) << "Got upload location [" << upload_location.spec() 291 << "] for [" << upload_file_info->file_path.value() << "]"; 292 293 if (code != HTTP_SUCCESS) { 294 if (code == HTTP_PRECONDITION) 295 code = HTTP_CONFLICT; // ETag mismatch. 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 305 void 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 319 void 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 355 void DriveUploader::OnUploadRangeResponseReceived( 356 scoped_ptr<UploadFileInfo> upload_file_info, 357 const UploadRangeResponse& response, 358 scoped_ptr<FileResource> 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 409 void 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 418 void 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<FileResource>()); 432 } 433 434 } // namespace drive 435