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