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