download_operation.cc revision 58e6fbe4ee35d65e14b626c557d37565bf8ad179
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/file_util.h" 8#include "base/files/file_path.h" 9#include "base/logging.h" 10#include "base/task_runner_util.h" 11#include "chrome/browser/chromeos/drive/drive.pb.h" 12#include "chrome/browser/chromeos/drive/file_cache.h" 13#include "chrome/browser/chromeos/drive/file_errors.h" 14#include "chrome/browser/chromeos/drive/file_system/operation_observer.h" 15#include "chrome/browser/chromeos/drive/file_system_util.h" 16#include "chrome/browser/chromeos/drive/job_scheduler.h" 17#include "chrome/browser/chromeos/drive/resource_metadata.h" 18#include "chrome/browser/google_apis/gdata_errorcode.h" 19#include "content/public/browser/browser_thread.h" 20 21using content::BrowserThread; 22 23namespace drive { 24namespace file_system { 25namespace { 26 27// If the resource is a hosted document, creates a JSON file representing the 28// resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing 29// the path to the JSON file. 30// If the resource is a regular file and its local cache is available, 31// returns FILE_ERROR_OK with |cache_file_path| storing the path to the 32// cache file. 33// If the resource is a regular file but its local cache is NOT available, 34// returns FILE_ERROR_OK, but |cache_file_path| is kept empty. 35// Otherwise returns error code. 36FileError CheckPreConditionForEnsureFileDownloaded( 37 internal::ResourceMetadata* metadata, 38 internal::FileCache* cache, 39 const base::FilePath& temporary_file_directory, 40 ResourceEntry* entry, 41 base::FilePath* cache_file_path) { 42 DCHECK(metadata); 43 DCHECK(cache); 44 DCHECK(cache_file_path); 45 46 if (entry->file_info().is_directory()) 47 return FILE_ERROR_NOT_A_FILE; 48 49 // The file's entry should have its file specific info. 50 DCHECK(entry->has_file_specific_info()); 51 52 // For a hosted document, we create a special JSON file to represent the 53 // document instead of fetching the document content in one of the exported 54 // formats. The JSON file contains the edit URL and resource ID of the 55 // document. 56 if (entry->file_specific_info().is_hosted_document()) { 57 base::FilePath gdoc_file_path; 58 if (!file_util::CreateTemporaryFileInDir(temporary_file_directory, 59 &gdoc_file_path) || 60 !util::CreateGDocFile(gdoc_file_path, 61 GURL(entry->file_specific_info().alternate_url()), 62 entry->resource_id())) 63 return FILE_ERROR_FAILED; 64 65 *cache_file_path = gdoc_file_path; 66 return FILE_ERROR_OK; 67 } 68 69 // Leave |cache_file_path| empty when no cache entry is found. 70 FileCacheEntry cache_entry; 71 if (!cache->GetCacheEntry(entry->resource_id(), 72 entry->file_specific_info().md5(), 73 &cache_entry)) 74 return FILE_ERROR_OK; 75 76 // Leave |cache_file_path| empty when the stored file is obsolete and has no 77 // local modification. 78 if (!cache_entry.is_dirty() && 79 entry->file_specific_info().md5() != cache_entry.md5()) 80 return FILE_ERROR_OK; 81 82 // Fill |cache_file_path| with the path to the cached file. 83 FileError error = cache->GetFile(entry->resource_id(), cache_file_path); 84 if (error != FILE_ERROR_OK) 85 return error; 86 87 // If the cache file is dirty, the modified file info needs to be stored in 88 // |entry|. 89 // TODO(kinaba): crbug.com/246469. The logic below is a duplicate of that in 90 // drive::FileSystem::CheckLocalModificationAndRun. We should merge them once 91 // the drive::FS side is also converted to run fully on blocking pool. 92 if (cache_entry.is_dirty()) { 93 base::PlatformFileInfo file_info; 94 if (file_util::GetFileInfo(*cache_file_path, &file_info)) { 95 PlatformFileInfoProto entry_file_info; 96 util::ConvertPlatformFileInfoToResourceEntry(file_info, 97 &entry_file_info); 98 *entry->mutable_file_info() = entry_file_info; 99 } 100 } 101 102 return FILE_ERROR_OK; 103} 104 105// Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by 106// the given ID. Also fills |drive_file_path| with the path of the entry. 107FileError CheckPreConditionForEnsureFileDownloadedByResourceId( 108 internal::ResourceMetadata* metadata, 109 internal::FileCache* cache, 110 const std::string& resource_id, 111 const base::FilePath& temporary_file_directory, 112 base::FilePath* drive_file_path, 113 base::FilePath* cache_file_path, 114 ResourceEntry* entry) { 115 FileError error = metadata->GetResourceEntryById(resource_id, entry); 116 *drive_file_path = metadata->GetFilePath(resource_id); 117 if (error != FILE_ERROR_OK) 118 return error; 119 return CheckPreConditionForEnsureFileDownloaded( 120 metadata, cache, temporary_file_directory, entry, cache_file_path); 121} 122 123// Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by 124// the given file path. 125FileError CheckPreConditionForEnsureFileDownloadedByPath( 126 internal::ResourceMetadata* metadata, 127 internal::FileCache* cache, 128 const base::FilePath& file_path, 129 const base::FilePath& temporary_file_directory, 130 base::FilePath* cache_file_path, 131 ResourceEntry* entry) { 132 FileError error = metadata->GetResourceEntryByPath(file_path, entry); 133 if (error != FILE_ERROR_OK) 134 return error; 135 return CheckPreConditionForEnsureFileDownloaded( 136 metadata, cache, temporary_file_directory, entry, cache_file_path); 137} 138 139// Creates a file with unique name in |dir| and stores the path to |temp_file|. 140// Additionally, sets the permission of the file to allow read access from 141// others and group member users (i.e, "-rw-r--r--"). 142// We need this wrapper because Drive cache files may be read from other 143// processes (e.g., cros_disks for mounting zip files). 144bool CreateTemporaryReadableFileInDir(const base::FilePath& dir, 145 base::FilePath* temp_file) { 146 if (!file_util::CreateTemporaryFileInDir(dir, temp_file)) 147 return false; 148 return file_util::SetPosixFilePermissions( 149 *temp_file, 150 file_util::FILE_PERMISSION_READ_BY_USER | 151 file_util::FILE_PERMISSION_WRITE_BY_USER | 152 file_util::FILE_PERMISSION_READ_BY_GROUP | 153 file_util::FILE_PERMISSION_READ_BY_OTHERS); 154} 155 156// Prepares for downloading the file. Given the |resource_id|, allocates the 157// enough space for the file in the cache. 158// If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the 159// path to the file in the cache. 160FileError PrepareForDownloadFile(internal::FileCache* cache, 161 int64 expected_file_size, 162 const base::FilePath& temporary_file_directory, 163 base::FilePath* temp_download_file) { 164 DCHECK(cache); 165 DCHECK(temp_download_file); 166 167 // Ensure enough space in the cache. 168 if (!cache->FreeDiskSpaceIfNeededFor(expected_file_size)) 169 return FILE_ERROR_NO_SPACE; 170 171 // Create the temporary file which will store the downloaded content. 172 return CreateTemporaryReadableFileInDir( 173 temporary_file_directory, 174 temp_download_file) ? FILE_ERROR_OK : FILE_ERROR_FAILED; 175} 176 177// Stores the downloaded file at |downloaded_file_path| into |cache|. 178// If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the 179// path to the cache file. 180// If failed, returns an error code with deleting |downloaded_file_path|. 181FileError UpdateLocalStateForDownloadFile( 182 internal::FileCache* cache, 183 const std::string& resource_id, 184 const std::string& md5, 185 google_apis::GDataErrorCode gdata_error, 186 const base::FilePath& downloaded_file_path, 187 base::FilePath* cache_file_path) { 188 DCHECK(cache); 189 190 FileError error = util::GDataToFileError(gdata_error); 191 if (error != FILE_ERROR_OK) { 192 base::DeleteFile(downloaded_file_path, false /* recursive */); 193 return error; 194 } 195 196 // Here the download is completed successfully, so store it into the cache. 197 error = cache->Store(resource_id, md5, downloaded_file_path, 198 internal::FileCache::FILE_OPERATION_MOVE); 199 if (error != FILE_ERROR_OK) { 200 base::DeleteFile(downloaded_file_path, false /* recursive */); 201 return error; 202 } 203 204 return cache->GetFile(resource_id, cache_file_path); 205} 206 207} // namespace 208 209class DownloadOperation::DownloadCallback { 210 public: 211 DownloadCallback( 212 const GetFileContentInitializedCallback initialized_callback, 213 const google_apis::GetContentCallback get_content_callback, 214 const GetFileCallback completion_callback) 215 : initialized_callback_(initialized_callback), 216 get_content_callback_(get_content_callback), 217 completion_callback_(completion_callback) { 218 DCHECK(!completion_callback_.is_null()); 219 } 220 221 void OnCacheFileFound(const ResourceEntry& entry, 222 const base::FilePath& cache_file_path) const { 223 if (initialized_callback_.is_null()) 224 return; 225 226 initialized_callback_.Run( 227 FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(entry)), 228 cache_file_path, base::Closure()); 229 } 230 231 void OnStartDownloading(const ResourceEntry& entry, 232 const base::Closure& cancel_download_closure) const { 233 if (initialized_callback_.is_null()) { 234 return; 235 } 236 237 initialized_callback_.Run( 238 FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(entry)), 239 base::FilePath(), cancel_download_closure); 240 } 241 242 void OnError(FileError error) const { 243 completion_callback_.Run( 244 error, base::FilePath(), scoped_ptr<ResourceEntry>()); 245 } 246 247 void OnComplete(const base::FilePath& cache_file_path, 248 scoped_ptr<ResourceEntry> entry) const { 249 completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry.Pass()); 250 } 251 252 const google_apis::GetContentCallback& get_content_callback() const { 253 return get_content_callback_; 254 } 255 256 private: 257 const GetFileContentInitializedCallback initialized_callback_; 258 const google_apis::GetContentCallback get_content_callback_; 259 const GetFileCallback completion_callback_; 260 261 // This class is copiable. 262}; 263 264DownloadOperation::DownloadOperation( 265 base::SequencedTaskRunner* blocking_task_runner, 266 OperationObserver* observer, 267 JobScheduler* scheduler, 268 internal::ResourceMetadata* metadata, 269 internal::FileCache* cache, 270 const base::FilePath& temporary_file_directory) 271 : blocking_task_runner_(blocking_task_runner), 272 observer_(observer), 273 scheduler_(scheduler), 274 metadata_(metadata), 275 cache_(cache), 276 temporary_file_directory_(temporary_file_directory), 277 weak_ptr_factory_(this) { 278} 279 280DownloadOperation::~DownloadOperation() { 281} 282 283void DownloadOperation::EnsureFileDownloadedByResourceId( 284 const std::string& resource_id, 285 const ClientContext& context, 286 const GetFileContentInitializedCallback& initialized_callback, 287 const google_apis::GetContentCallback& get_content_callback, 288 const GetFileCallback& completion_callback) { 289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 290 DCHECK(!completion_callback.is_null()); 291 292 DownloadCallback callback( 293 initialized_callback, get_content_callback, completion_callback); 294 295 base::FilePath* drive_file_path = new base::FilePath; 296 base::FilePath* cache_file_path = new base::FilePath; 297 ResourceEntry* entry = new ResourceEntry; 298 base::PostTaskAndReplyWithResult( 299 blocking_task_runner_.get(), 300 FROM_HERE, 301 base::Bind(&CheckPreConditionForEnsureFileDownloadedByResourceId, 302 base::Unretained(metadata_), 303 base::Unretained(cache_), 304 resource_id, 305 temporary_file_directory_, 306 drive_file_path, 307 cache_file_path, 308 entry), 309 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition, 310 weak_ptr_factory_.GetWeakPtr(), 311 callback, 312 context, 313 base::Passed(make_scoped_ptr(entry)), 314 base::Owned(drive_file_path), 315 base::Owned(cache_file_path))); 316} 317 318void DownloadOperation::EnsureFileDownloadedByPath( 319 const base::FilePath& file_path, 320 const ClientContext& context, 321 const GetFileContentInitializedCallback& initialized_callback, 322 const google_apis::GetContentCallback& get_content_callback, 323 const GetFileCallback& completion_callback) { 324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 325 DCHECK(!completion_callback.is_null()); 326 327 DownloadCallback callback( 328 initialized_callback, get_content_callback, completion_callback); 329 330 base::FilePath* drive_file_path = new base::FilePath(file_path); 331 base::FilePath* cache_file_path = new base::FilePath; 332 ResourceEntry* entry = new ResourceEntry; 333 base::PostTaskAndReplyWithResult( 334 blocking_task_runner_.get(), 335 FROM_HERE, 336 base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath, 337 base::Unretained(metadata_), 338 base::Unretained(cache_), 339 file_path, 340 temporary_file_directory_, 341 cache_file_path, 342 entry), 343 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition, 344 weak_ptr_factory_.GetWeakPtr(), 345 callback, 346 context, 347 base::Passed(make_scoped_ptr(entry)), 348 base::Owned(drive_file_path), 349 base::Owned(cache_file_path))); 350} 351 352void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition( 353 const DownloadCallback& callback, 354 const ClientContext& context, 355 scoped_ptr<ResourceEntry> entry, 356 base::FilePath* drive_file_path, 357 base::FilePath* cache_file_path, 358 FileError error) { 359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 360 DCHECK(entry); 361 DCHECK(drive_file_path); 362 DCHECK(cache_file_path); 363 364 if (error != FILE_ERROR_OK) { 365 // During precondition check, an error is found. 366 callback.OnError(error); 367 return; 368 } 369 370 if (!cache_file_path->empty()) { 371 // The cache file is found. 372 callback.OnCacheFileFound(*entry, *cache_file_path); 373 callback.OnComplete(*cache_file_path, entry.Pass()); 374 return; 375 } 376 377 // If cache file is not found, try to download the file from the server 378 // instead. Check if we have enough space, based on the expected file size. 379 // - if we don't have enough space, try to free up the disk space 380 // - if we still don't have enough space, return "no space" error 381 // - if we have enough space, start downloading the file from the server 382 int64 size = entry->file_info().size(); 383 base::FilePath* temp_download_file_path = new base::FilePath; 384 base::PostTaskAndReplyWithResult( 385 blocking_task_runner_.get(), 386 FROM_HERE, 387 base::Bind(&PrepareForDownloadFile, 388 base::Unretained(cache_), 389 size, 390 temporary_file_directory_, 391 temp_download_file_path), 392 base::Bind( 393 &DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile, 394 weak_ptr_factory_.GetWeakPtr(), 395 callback, 396 context, 397 base::Passed(&entry), 398 *drive_file_path, 399 base::Owned(temp_download_file_path))); 400} 401 402void DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile( 403 const DownloadCallback& callback, 404 const ClientContext& context, 405 scoped_ptr<ResourceEntry> entry, 406 const base::FilePath& drive_file_path, 407 base::FilePath* temp_download_file_path, 408 FileError error) { 409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 410 DCHECK(entry); 411 DCHECK(temp_download_file_path); 412 413 if (error != FILE_ERROR_OK) { 414 callback.OnError(error); 415 return; 416 } 417 418 ResourceEntry* entry_ptr = entry.get(); 419 JobID id = scheduler_->DownloadFile( 420 drive_file_path, 421 *temp_download_file_path, 422 entry_ptr->resource_id(), 423 context, 424 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile, 425 weak_ptr_factory_.GetWeakPtr(), 426 drive_file_path, 427 base::Passed(&entry), 428 callback), 429 callback.get_content_callback()); 430 431 // Notify via |initialized_callback| if necessary. 432 callback.OnStartDownloading( 433 *entry_ptr, 434 base::Bind(&DownloadOperation::CancelJob, 435 weak_ptr_factory_.GetWeakPtr(), id)); 436} 437 438void DownloadOperation::EnsureFileDownloadedAfterDownloadFile( 439 const base::FilePath& drive_file_path, 440 scoped_ptr<ResourceEntry> entry, 441 const DownloadCallback& callback, 442 google_apis::GDataErrorCode gdata_error, 443 const base::FilePath& downloaded_file_path) { 444 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 445 446 ResourceEntry* entry_ptr = entry.get(); 447 base::FilePath* cache_file_path = new base::FilePath; 448 base::PostTaskAndReplyWithResult( 449 blocking_task_runner_.get(), 450 FROM_HERE, 451 base::Bind(&UpdateLocalStateForDownloadFile, 452 base::Unretained(cache_), 453 entry_ptr->resource_id(), 454 entry_ptr->file_specific_info().md5(), 455 gdata_error, 456 downloaded_file_path, 457 cache_file_path), 458 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState, 459 weak_ptr_factory_.GetWeakPtr(), 460 drive_file_path, 461 callback, 462 base::Passed(&entry), 463 base::Owned(cache_file_path))); 464} 465 466void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState( 467 const base::FilePath& file_path, 468 const DownloadCallback& callback, 469 scoped_ptr<ResourceEntry> entry, 470 base::FilePath* cache_file_path, 471 FileError error) { 472 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 473 474 if (error != FILE_ERROR_OK) { 475 callback.OnError(error); 476 return; 477 } 478 479 // Storing to cache changes the "offline available" status, hence notify. 480 observer_->OnDirectoryChangedByOperation(file_path.DirName()); 481 callback.OnComplete(*cache_file_path, entry.Pass()); 482} 483 484void DownloadOperation::CancelJob(JobID job_id) { 485 scheduler_->CancelJob(job_id); 486} 487 488} // namespace file_system 489} // namespace drive 490