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