1 // Copyright 2013 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/chromeos/drive/file_system/download_operation.h" 6 7 #include "base/callback_helpers.h" 8 #include "base/file_util.h" 9 #include "base/files/file_path.h" 10 #include "base/logging.h" 11 #include "base/strings/stringprintf.h" 12 #include "base/task_runner_util.h" 13 #include "chrome/browser/chromeos/drive/drive.pb.h" 14 #include "chrome/browser/chromeos/drive/file_cache.h" 15 #include "chrome/browser/chromeos/drive/file_errors.h" 16 #include "chrome/browser/chromeos/drive/file_system/operation_observer.h" 17 #include "chrome/browser/chromeos/drive/file_system_util.h" 18 #include "chrome/browser/chromeos/drive/job_scheduler.h" 19 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h" 20 #include "chrome/browser/chromeos/drive/resource_metadata.h" 21 #include "content/public/browser/browser_thread.h" 22 #include "google_apis/drive/gdata_errorcode.h" 23 24 using content::BrowserThread; 25 26 namespace drive { 27 namespace file_system { 28 namespace { 29 30 // Generates an unused file path with |extension| to |out_path|, as a descendant 31 // of |dir|, with its parent directory created. 32 bool GeneratesUniquePathWithExtension( 33 const base::FilePath& dir, 34 const base::FilePath::StringType& extension, 35 base::FilePath* out_path) { 36 base::FilePath subdir; 37 if (!base::CreateTemporaryDirInDir(dir, base::FilePath::StringType(), 38 &subdir)) { 39 return false; 40 } 41 *out_path = subdir.Append(FILE_PATH_LITERAL("tmp") + extension); 42 return true; 43 } 44 45 // Prepares for downloading the file. Allocates the enough space for the file 46 // in the cache. 47 // If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the 48 // path to the file in the cache. 49 FileError PrepareForDownloadFile(internal::FileCache* cache, 50 int64 expected_file_size, 51 const base::FilePath& temporary_file_directory, 52 base::FilePath* temp_download_file) { 53 DCHECK(cache); 54 DCHECK(temp_download_file); 55 56 // Ensure enough space in the cache. 57 if (!cache->FreeDiskSpaceIfNeededFor(expected_file_size)) 58 return FILE_ERROR_NO_LOCAL_SPACE; 59 60 return base::CreateTemporaryFileInDir( 61 temporary_file_directory, 62 temp_download_file) ? FILE_ERROR_OK : FILE_ERROR_FAILED; 63 } 64 65 // If the resource is a hosted document, creates a JSON file representing the 66 // resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing 67 // the path to the JSON file. 68 // If the resource is a regular file and its local cache is available, 69 // returns FILE_ERROR_OK with |cache_file_path| storing the path to the 70 // cache file. 71 // If the resource is a regular file but its local cache is NOT available, 72 // returns FILE_ERROR_OK, but |cache_file_path| is kept empty. 73 // Otherwise returns error code. 74 FileError CheckPreConditionForEnsureFileDownloaded( 75 internal::ResourceMetadata* metadata, 76 internal::FileCache* cache, 77 const base::FilePath& temporary_file_directory, 78 const std::string& local_id, 79 ResourceEntry* entry, 80 base::FilePath* cache_file_path, 81 base::FilePath* temp_download_file_path) { 82 DCHECK(metadata); 83 DCHECK(cache); 84 DCHECK(cache_file_path); 85 86 FileError error = metadata->GetResourceEntryById(local_id, entry); 87 if (error != FILE_ERROR_OK) 88 return error; 89 90 if (entry->file_info().is_directory()) 91 return FILE_ERROR_NOT_A_FILE; 92 93 // For a hosted document, we create a special JSON file to represent the 94 // document instead of fetching the document content in one of the exported 95 // formats. The JSON file contains the edit URL and resource ID of the 96 // document. 97 if (entry->file_specific_info().is_hosted_document()) { 98 base::FilePath::StringType extension = base::FilePath::FromUTF8Unsafe( 99 entry->file_specific_info().document_extension()).value(); 100 base::FilePath gdoc_file_path; 101 // TODO(rvargas): Convert this code to use base::File::Info. 102 base::File::Info file_info; 103 // We add the gdoc file extension in the temporary file, so that in cross 104 // profile drag-and-drop between Drive folders, the destination profiles's 105 // CopyOperation can detect the special JSON file only by the path. 106 if (!GeneratesUniquePathWithExtension(temporary_file_directory, 107 extension, 108 &gdoc_file_path) || 109 !util::CreateGDocFile(gdoc_file_path, 110 GURL(entry->file_specific_info().alternate_url()), 111 entry->resource_id()) || 112 !base::GetFileInfo(gdoc_file_path, 113 reinterpret_cast<base::File::Info*>(&file_info))) 114 return FILE_ERROR_FAILED; 115 116 *cache_file_path = gdoc_file_path; 117 entry->mutable_file_info()->set_size(file_info.size); 118 return FILE_ERROR_OK; 119 } 120 121 if (!entry->file_specific_info().cache_state().is_present()) { 122 // This file has no cache file. 123 if (!entry->resource_id().empty()) { 124 // This entry exists on the server, leave |cache_file_path| empty to 125 // start download. 126 return PrepareForDownloadFile(cache, entry->file_info().size(), 127 temporary_file_directory, 128 temp_download_file_path); 129 } 130 131 // This entry does not exist on the server, store an empty file and mark it 132 // as dirty. 133 base::FilePath empty_file; 134 if (!base::CreateTemporaryFileInDir(temporary_file_directory, &empty_file)) 135 return FILE_ERROR_FAILED; 136 error = cache->Store(local_id, std::string(), empty_file, 137 internal::FileCache::FILE_OPERATION_MOVE); 138 if (error != FILE_ERROR_OK) 139 return error; 140 141 error = metadata->GetResourceEntryById(local_id, entry); 142 if (error != FILE_ERROR_OK) 143 return error; 144 } 145 146 // Leave |cache_file_path| empty when the stored file is obsolete and has no 147 // local modification. 148 if (!entry->file_specific_info().cache_state().is_dirty() && 149 entry->file_specific_info().md5() != 150 entry->file_specific_info().cache_state().md5()) { 151 return PrepareForDownloadFile(cache, entry->file_info().size(), 152 temporary_file_directory, 153 temp_download_file_path); 154 } 155 156 // Fill |cache_file_path| with the path to the cached file. 157 error = cache->GetFile(local_id, cache_file_path); 158 if (error != FILE_ERROR_OK) 159 return error; 160 161 // If the cache file is to be returned as the download result, the file info 162 // of the cache needs to be returned via |entry|. 163 // TODO(kinaba): crbug.com/246469. The logic below is similar to that in 164 // drive::FileSystem::CheckLocalModificationAndRun. We should merge them. 165 base::File::Info file_info; 166 if (base::GetFileInfo(*cache_file_path, &file_info)) 167 entry->mutable_file_info()->set_size(file_info.size); 168 169 return FILE_ERROR_OK; 170 } 171 172 struct CheckPreconditionForEnsureFileDownloadedParams { 173 internal::ResourceMetadata* metadata; 174 internal::FileCache* cache; 175 base::FilePath temporary_file_directory; 176 }; 177 178 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by 179 // the given ID. Also fills |drive_file_path| with the path of the entry. 180 FileError CheckPreConditionForEnsureFileDownloadedByLocalId( 181 const CheckPreconditionForEnsureFileDownloadedParams& params, 182 const std::string& local_id, 183 base::FilePath* drive_file_path, 184 base::FilePath* cache_file_path, 185 base::FilePath* temp_download_file_path, 186 ResourceEntry* entry) { 187 FileError error = params.metadata->GetFilePath(local_id, drive_file_path); 188 if (error != FILE_ERROR_OK) 189 return error; 190 return CheckPreConditionForEnsureFileDownloaded( 191 params.metadata, params.cache, params.temporary_file_directory, local_id, 192 entry, cache_file_path, temp_download_file_path); 193 } 194 195 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by 196 // the given file path. 197 FileError CheckPreConditionForEnsureFileDownloadedByPath( 198 const CheckPreconditionForEnsureFileDownloadedParams& params, 199 const base::FilePath& file_path, 200 base::FilePath* cache_file_path, 201 base::FilePath* temp_download_file_path, 202 ResourceEntry* entry) { 203 std::string local_id; 204 FileError error = params.metadata->GetIdByPath(file_path, &local_id); 205 if (error != FILE_ERROR_OK) 206 return error; 207 return CheckPreConditionForEnsureFileDownloaded( 208 params.metadata, params.cache, params.temporary_file_directory, local_id, 209 entry, cache_file_path, temp_download_file_path); 210 } 211 212 // Stores the downloaded file at |downloaded_file_path| into |cache|. 213 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the 214 // path to the cache file. 215 // If failed, returns an error code with deleting |downloaded_file_path|. 216 FileError UpdateLocalStateForDownloadFile( 217 internal::ResourceMetadata* metadata, 218 internal::FileCache* cache, 219 const ResourceEntry& entry_before_download, 220 google_apis::GDataErrorCode gdata_error, 221 const base::FilePath& downloaded_file_path, 222 ResourceEntry* entry_after_update, 223 base::FilePath* cache_file_path) { 224 DCHECK(cache); 225 226 // Downloaded file should be deleted on errors. 227 base::ScopedClosureRunner file_deleter(base::Bind( 228 base::IgnoreResult(&base::DeleteFile), 229 downloaded_file_path, false /* recursive */)); 230 231 FileError error = GDataToFileError(gdata_error); 232 if (error != FILE_ERROR_OK) 233 return error; 234 235 const std::string& local_id = entry_before_download.local_id(); 236 237 // Do not overwrite locally edited file with server side contents. 238 ResourceEntry entry; 239 error = metadata->GetResourceEntryById(local_id, &entry); 240 if (error != FILE_ERROR_OK) 241 return error; 242 if (entry.file_specific_info().cache_state().is_dirty()) 243 return FILE_ERROR_IN_USE; 244 245 // Here the download is completed successfully, so store it into the cache. 246 error = cache->Store(local_id, 247 entry_before_download.file_specific_info().md5(), 248 downloaded_file_path, 249 internal::FileCache::FILE_OPERATION_MOVE); 250 if (error != FILE_ERROR_OK) 251 return error; 252 base::Closure unused_file_deleter_closure = file_deleter.Release(); 253 254 error = metadata->GetResourceEntryById(local_id, entry_after_update); 255 if (error != FILE_ERROR_OK) 256 return error; 257 258 return cache->GetFile(local_id, cache_file_path); 259 } 260 261 } // namespace 262 263 class DownloadOperation::DownloadParams { 264 public: 265 DownloadParams( 266 const GetFileContentInitializedCallback initialized_callback, 267 const google_apis::GetContentCallback get_content_callback, 268 const GetFileCallback completion_callback, 269 scoped_ptr<ResourceEntry> entry) 270 : initialized_callback_(initialized_callback), 271 get_content_callback_(get_content_callback), 272 completion_callback_(completion_callback), 273 entry_(entry.Pass()), 274 was_cancelled_(false), 275 weak_ptr_factory_(this) { 276 DCHECK(!completion_callback_.is_null()); 277 DCHECK(entry_); 278 } 279 280 base::Closure GetCancelClosure() { 281 return base::Bind(&DownloadParams::Cancel, weak_ptr_factory_.GetWeakPtr()); 282 } 283 284 void OnCacheFileFound(const base::FilePath& cache_file_path) { 285 if (!initialized_callback_.is_null()) { 286 initialized_callback_.Run(FILE_ERROR_OK, cache_file_path, 287 make_scoped_ptr(new ResourceEntry(*entry_))); 288 } 289 completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry_.Pass()); 290 } 291 292 void OnStartDownloading(const base::Closure& cancel_download_closure) { 293 cancel_download_closure_ = cancel_download_closure; 294 if (initialized_callback_.is_null()) { 295 return; 296 } 297 298 DCHECK(entry_); 299 initialized_callback_.Run(FILE_ERROR_OK, base::FilePath(), 300 make_scoped_ptr(new ResourceEntry(*entry_))); 301 } 302 303 void OnError(FileError error) const { 304 completion_callback_.Run( 305 error, base::FilePath(), scoped_ptr<ResourceEntry>()); 306 } 307 308 void OnDownloadCompleted(const base::FilePath& cache_file_path, 309 scoped_ptr<ResourceEntry> entry) const { 310 completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry.Pass()); 311 } 312 313 const google_apis::GetContentCallback& get_content_callback() const { 314 return get_content_callback_; 315 } 316 317 const ResourceEntry& entry() const { return *entry_; } 318 319 bool was_cancelled() const { return was_cancelled_; } 320 321 private: 322 void Cancel() { 323 was_cancelled_ = true; 324 if (!cancel_download_closure_.is_null()) 325 cancel_download_closure_.Run(); 326 } 327 328 const GetFileContentInitializedCallback initialized_callback_; 329 const google_apis::GetContentCallback get_content_callback_; 330 const GetFileCallback completion_callback_; 331 332 scoped_ptr<ResourceEntry> entry_; 333 base::Closure cancel_download_closure_; 334 bool was_cancelled_; 335 336 base::WeakPtrFactory<DownloadParams> weak_ptr_factory_; 337 DISALLOW_COPY_AND_ASSIGN(DownloadParams); 338 }; 339 340 DownloadOperation::DownloadOperation( 341 base::SequencedTaskRunner* blocking_task_runner, 342 OperationObserver* observer, 343 JobScheduler* scheduler, 344 internal::ResourceMetadata* metadata, 345 internal::FileCache* cache, 346 const base::FilePath& temporary_file_directory) 347 : blocking_task_runner_(blocking_task_runner), 348 observer_(observer), 349 scheduler_(scheduler), 350 metadata_(metadata), 351 cache_(cache), 352 temporary_file_directory_(temporary_file_directory), 353 weak_ptr_factory_(this) { 354 } 355 356 DownloadOperation::~DownloadOperation() { 357 } 358 359 base::Closure DownloadOperation::EnsureFileDownloadedByLocalId( 360 const std::string& local_id, 361 const ClientContext& context, 362 const GetFileContentInitializedCallback& initialized_callback, 363 const google_apis::GetContentCallback& get_content_callback, 364 const GetFileCallback& completion_callback) { 365 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 366 DCHECK(!completion_callback.is_null()); 367 368 CheckPreconditionForEnsureFileDownloadedParams params; 369 params.metadata = metadata_; 370 params.cache = cache_; 371 params.temporary_file_directory = temporary_file_directory_; 372 base::FilePath* drive_file_path = new base::FilePath; 373 base::FilePath* cache_file_path = new base::FilePath; 374 base::FilePath* temp_download_file_path = new base::FilePath; 375 ResourceEntry* entry = new ResourceEntry; 376 scoped_ptr<DownloadParams> download_params(new DownloadParams( 377 initialized_callback, get_content_callback, completion_callback, 378 make_scoped_ptr(entry))); 379 base::Closure cancel_closure = download_params->GetCancelClosure(); 380 base::PostTaskAndReplyWithResult( 381 blocking_task_runner_.get(), 382 FROM_HERE, 383 base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId, 384 params, 385 local_id, 386 drive_file_path, 387 cache_file_path, 388 temp_download_file_path, 389 entry), 390 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition, 391 weak_ptr_factory_.GetWeakPtr(), 392 base::Passed(&download_params), 393 context, 394 base::Owned(drive_file_path), 395 base::Owned(cache_file_path), 396 base::Owned(temp_download_file_path))); 397 return cancel_closure; 398 } 399 400 base::Closure DownloadOperation::EnsureFileDownloadedByPath( 401 const base::FilePath& file_path, 402 const ClientContext& context, 403 const GetFileContentInitializedCallback& initialized_callback, 404 const google_apis::GetContentCallback& get_content_callback, 405 const GetFileCallback& completion_callback) { 406 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 407 DCHECK(!completion_callback.is_null()); 408 409 CheckPreconditionForEnsureFileDownloadedParams params; 410 params.metadata = metadata_; 411 params.cache = cache_; 412 params.temporary_file_directory = temporary_file_directory_; 413 base::FilePath* drive_file_path = new base::FilePath(file_path); 414 base::FilePath* cache_file_path = new base::FilePath; 415 base::FilePath* temp_download_file_path = new base::FilePath; 416 ResourceEntry* entry = new ResourceEntry; 417 scoped_ptr<DownloadParams> download_params(new DownloadParams( 418 initialized_callback, get_content_callback, completion_callback, 419 make_scoped_ptr(entry))); 420 base::Closure cancel_closure = download_params->GetCancelClosure(); 421 base::PostTaskAndReplyWithResult( 422 blocking_task_runner_.get(), 423 FROM_HERE, 424 base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath, 425 params, 426 file_path, 427 cache_file_path, 428 temp_download_file_path, 429 entry), 430 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition, 431 weak_ptr_factory_.GetWeakPtr(), 432 base::Passed(&download_params), 433 context, 434 base::Owned(drive_file_path), 435 base::Owned(cache_file_path), 436 base::Owned(temp_download_file_path))); 437 return cancel_closure; 438 } 439 440 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition( 441 scoped_ptr<DownloadParams> params, 442 const ClientContext& context, 443 base::FilePath* drive_file_path, 444 base::FilePath* cache_file_path, 445 base::FilePath* temp_download_file_path, 446 FileError error) { 447 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 448 DCHECK(params); 449 DCHECK(drive_file_path); 450 DCHECK(cache_file_path); 451 452 if (error != FILE_ERROR_OK) { 453 // During precondition check, an error is found. 454 params->OnError(error); 455 return; 456 } 457 458 if (!cache_file_path->empty()) { 459 // The cache file is found. 460 params->OnCacheFileFound(*cache_file_path); 461 return; 462 } 463 464 if (params->was_cancelled()) { 465 params->OnError(FILE_ERROR_ABORT); 466 return; 467 } 468 469 DCHECK(!params->entry().resource_id().empty()); 470 DownloadParams* params_ptr = params.get(); 471 JobID id = scheduler_->DownloadFile( 472 *drive_file_path, 473 params_ptr->entry().file_info().size(), 474 *temp_download_file_path, 475 params_ptr->entry().resource_id(), 476 context, 477 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile, 478 weak_ptr_factory_.GetWeakPtr(), 479 *drive_file_path, 480 base::Passed(¶ms)), 481 params_ptr->get_content_callback()); 482 483 // Notify via |initialized_callback| if necessary. 484 params_ptr->OnStartDownloading( 485 base::Bind(&DownloadOperation::CancelJob, 486 weak_ptr_factory_.GetWeakPtr(), id)); 487 } 488 489 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile( 490 const base::FilePath& drive_file_path, 491 scoped_ptr<DownloadParams> params, 492 google_apis::GDataErrorCode gdata_error, 493 const base::FilePath& downloaded_file_path) { 494 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 495 496 DownloadParams* params_ptr = params.get(); 497 ResourceEntry* entry_after_update = new ResourceEntry; 498 base::FilePath* cache_file_path = new base::FilePath; 499 base::PostTaskAndReplyWithResult( 500 blocking_task_runner_.get(), 501 FROM_HERE, 502 base::Bind(&UpdateLocalStateForDownloadFile, 503 metadata_, 504 cache_, 505 params_ptr->entry(), 506 gdata_error, 507 downloaded_file_path, 508 entry_after_update, 509 cache_file_path), 510 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState, 511 weak_ptr_factory_.GetWeakPtr(), 512 drive_file_path, 513 base::Passed(¶ms), 514 base::Passed(make_scoped_ptr(entry_after_update)), 515 base::Owned(cache_file_path))); 516 } 517 518 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState( 519 const base::FilePath& file_path, 520 scoped_ptr<DownloadParams> params, 521 scoped_ptr<ResourceEntry> entry_after_update, 522 base::FilePath* cache_file_path, 523 FileError error) { 524 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 525 526 if (error != FILE_ERROR_OK) { 527 params->OnError(error); 528 return; 529 } 530 531 // Storing to cache changes the "offline available" status, hence notify. 532 observer_->OnDirectoryChangedByOperation(file_path.DirName()); 533 params->OnDownloadCompleted(*cache_file_path, entry_after_update.Pass()); 534 } 535 536 void DownloadOperation::CancelJob(JobID job_id) { 537 scheduler_->CancelJob(job_id); 538 } 539 540 } // namespace file_system 541 } // namespace drive 542