file_cache.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
1// Copyright (c) 2012 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_cache.h" 6 7#include <vector> 8 9#include "base/file_util.h" 10#include "base/files/file_enumerator.h" 11#include "base/logging.h" 12#include "base/strings/string_util.h" 13#include "base/strings/stringprintf.h" 14#include "base/sys_info.h" 15#include "base/task_runner_util.h" 16#include "chrome/browser/chromeos/drive/drive.pb.h" 17#include "chrome/browser/chromeos/drive/file_cache_metadata.h" 18#include "chrome/browser/chromeos/drive/file_system_util.h" 19#include "chrome/browser/google_apis/task_util.h" 20#include "chromeos/chromeos_constants.h" 21#include "content/public/browser/browser_thread.h" 22 23using content::BrowserThread; 24 25namespace drive { 26namespace internal { 27namespace { 28 29typedef std::map<std::string, FileCacheEntry> CacheMap; 30 31// Returns true if |md5| matches the one in |cache_entry| with some 32// exceptions. See the function definition for details. 33bool CheckIfMd5Matches(const std::string& md5, 34 const FileCacheEntry& cache_entry) { 35 if (cache_entry.is_dirty()) { 36 // If the entry is dirty, its MD5 may have been replaced by "local" 37 // during cache initialization, so we don't compare MD5. 38 return true; 39 } else if (cache_entry.is_pinned() && cache_entry.md5().empty()) { 40 // If the entry is pinned, it's ok for the entry to have an empty 41 // MD5. This can happen if the pinned file is not fetched. 42 return true; 43 } else if (md5.empty()) { 44 // If the MD5 matching is not requested, don't check MD5. 45 return true; 46 } else if (md5 == cache_entry.md5()) { 47 // Otherwise, compare the MD5. 48 return true; 49 } 50 return false; 51} 52 53// Scans cache subdirectory and insert found files to |cache_map|. 54void ScanCacheDirectory(const base::FilePath& directory_path, 55 CacheMap* cache_map) { 56 base::FileEnumerator enumerator(directory_path, 57 false, // not recursive 58 base::FileEnumerator::FILES); 59 for (base::FilePath current = enumerator.Next(); !current.empty(); 60 current = enumerator.Next()) { 61 // Extract resource_id and md5 from filename. 62 std::string resource_id; 63 std::string md5; 64 util::ParseCacheFilePath(current, &resource_id, &md5); 65 66 // Determine cache state. 67 FileCacheEntry cache_entry; 68 cache_entry.set_md5(md5); 69 cache_entry.set_is_present(true); 70 71 // Add the dirty bit if |md5| indicates that the file is dirty. 72 if (md5 == util::kLocallyModifiedFileExtension) 73 cache_entry.set_is_dirty(true); 74 75 // Create and insert new entry into cache map. 76 cache_map->insert(std::make_pair(resource_id, cache_entry)); 77 } 78} 79 80// Moves the file. 81bool MoveFile(const base::FilePath& source_path, 82 const base::FilePath& dest_path) { 83 if (!file_util::Move(source_path, dest_path)) { 84 LOG(ERROR) << "Failed to move " << source_path.value() 85 << " to " << dest_path.value(); 86 return false; 87 } 88 DVLOG(1) << "Moved " << source_path.value() << " to " << dest_path.value(); 89 return true; 90} 91 92// Copies the file. 93bool CopyFile(const base::FilePath& source_path, 94 const base::FilePath& dest_path) { 95 if (!file_util::CopyFile(source_path, dest_path)) { 96 LOG(ERROR) << "Failed to copy " << source_path.value() 97 << " to " << dest_path.value(); 98 return false; 99 } 100 DVLOG(1) << "Copied " << source_path.value() << " to " << dest_path.value(); 101 return true; 102} 103 104// Deletes all files that match |path_to_delete_pattern| except for 105// |path_to_keep| on blocking pool. 106// If |path_to_keep| is empty, all files in |path_to_delete_pattern| are 107// deleted. 108void DeleteFilesSelectively(const base::FilePath& path_to_delete_pattern, 109 const base::FilePath& path_to_keep) { 110 // Enumerate all files in directory of |path_to_delete_pattern| that match 111 // base name of |path_to_delete_pattern|. 112 // If a file is not |path_to_keep|, delete it. 113 bool success = true; 114 base::FileEnumerator enumerator( 115 path_to_delete_pattern.DirName(), 116 false, // not recursive 117 base::FileEnumerator::FILES, 118 path_to_delete_pattern.BaseName().value()); 119 for (base::FilePath current = enumerator.Next(); !current.empty(); 120 current = enumerator.Next()) { 121 // If |path_to_keep| is not empty and same as current, don't delete it. 122 if (!path_to_keep.empty() && current == path_to_keep) 123 continue; 124 125 success = file_util::Delete(current, false); 126 if (!success) 127 DVLOG(1) << "Error deleting " << current.value(); 128 else 129 DVLOG(1) << "Deleted " << current.value(); 130 } 131} 132 133// Runs callback with pointers dereferenced. 134// Used to implement GetFile, MarkAsMounted. 135void RunGetFileFromCacheCallback( 136 const GetFileFromCacheCallback& callback, 137 base::FilePath* file_path, 138 FileError error) { 139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 140 DCHECK(!callback.is_null()); 141 DCHECK(file_path); 142 143 callback.Run(error, *file_path); 144} 145 146// Runs callback with pointers dereferenced. 147// Used to implement GetCacheEntry(). 148void RunGetCacheEntryCallback(const GetCacheEntryCallback& callback, 149 FileCacheEntry* cache_entry, 150 bool success) { 151 DCHECK(cache_entry); 152 DCHECK(!callback.is_null()); 153 callback.Run(success, *cache_entry); 154} 155 156} // namespace 157 158FileCache::FileCache(const base::FilePath& metadata_directory, 159 const base::FilePath& cache_file_directory, 160 base::SequencedTaskRunner* blocking_task_runner, 161 FreeDiskSpaceGetterInterface* free_disk_space_getter) 162 : metadata_directory_(metadata_directory), 163 cache_file_directory_(cache_file_directory), 164 blocking_task_runner_(blocking_task_runner), 165 free_disk_space_getter_(free_disk_space_getter), 166 weak_ptr_factory_(this) { 167 DCHECK(blocking_task_runner_.get()); 168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 169} 170 171FileCache::~FileCache() { 172 // Must be on the sequenced worker pool, as |metadata_| must be deleted on 173 // the sequenced worker pool. 174 AssertOnSequencedWorkerPool(); 175} 176 177base::FilePath FileCache::GetCacheFilePath(const std::string& resource_id, 178 const std::string& md5, 179 CachedFileOrigin file_origin) const { 180 // Runs on any thread. 181 // Filename is formatted as resource_id.md5, i.e. resource_id is the base 182 // name and md5 is the extension. 183 std::string base_name = util::EscapeCacheFileName(resource_id); 184 if (file_origin == CACHED_FILE_LOCALLY_MODIFIED) { 185 base_name += base::FilePath::kExtensionSeparator; 186 base_name += util::kLocallyModifiedFileExtension; 187 } else if (!md5.empty()) { 188 base_name += base::FilePath::kExtensionSeparator; 189 base_name += util::EscapeCacheFileName(md5); 190 } 191 return cache_file_directory_.Append( 192 base::FilePath::FromUTF8Unsafe(base_name)); 193} 194 195void FileCache::AssertOnSequencedWorkerPool() { 196 DCHECK(!blocking_task_runner_.get() || 197 blocking_task_runner_->RunsTasksOnCurrentThread()); 198} 199 200bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const { 201 return cache_file_directory_.IsParent(path); 202} 203 204void FileCache::GetCacheEntryOnUIThread(const std::string& resource_id, 205 const std::string& md5, 206 const GetCacheEntryCallback& callback) { 207 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 208 DCHECK(!callback.is_null()); 209 210 FileCacheEntry* cache_entry = new FileCacheEntry; 211 base::PostTaskAndReplyWithResult( 212 blocking_task_runner_.get(), 213 FROM_HERE, 214 base::Bind(&FileCache::GetCacheEntry, 215 base::Unretained(this), 216 resource_id, 217 md5, 218 cache_entry), 219 base::Bind( 220 &RunGetCacheEntryCallback, callback, base::Owned(cache_entry))); 221} 222 223bool FileCache::GetCacheEntry(const std::string& resource_id, 224 const std::string& md5, 225 FileCacheEntry* entry) { 226 DCHECK(entry); 227 AssertOnSequencedWorkerPool(); 228 return metadata_->GetCacheEntry(resource_id, entry) && 229 CheckIfMd5Matches(md5, *entry); 230} 231 232void FileCache::IterateOnUIThread( 233 const CacheIterateCallback& iteration_callback, 234 const base::Closure& completion_callback) { 235 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 236 DCHECK(!iteration_callback.is_null()); 237 DCHECK(!completion_callback.is_null()); 238 239 blocking_task_runner_->PostTaskAndReply( 240 FROM_HERE, 241 base::Bind(&FileCache::Iterate, 242 base::Unretained(this), 243 google_apis::CreateRelayCallback(iteration_callback)), 244 completion_callback); 245} 246 247void FileCache::Iterate(const CacheIterateCallback& iteration_callback) { 248 AssertOnSequencedWorkerPool(); 249 DCHECK(!iteration_callback.is_null()); 250 251 scoped_ptr<FileCacheMetadata::Iterator> it = metadata_->GetIterator(); 252 for (; !it->IsAtEnd(); it->Advance()) 253 iteration_callback.Run(it->GetKey(), it->GetValue()); 254 DCHECK(!it->HasError()); 255} 256 257void FileCache::FreeDiskSpaceIfNeededForOnUIThread( 258 int64 num_bytes, 259 const InitializeCacheCallback& callback) { 260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 261 DCHECK(!callback.is_null()); 262 263 base::PostTaskAndReplyWithResult( 264 blocking_task_runner_.get(), 265 FROM_HERE, 266 base::Bind(&FileCache::FreeDiskSpaceIfNeededFor, 267 base::Unretained(this), 268 num_bytes), 269 callback); 270} 271 272bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) { 273 AssertOnSequencedWorkerPool(); 274 275 // Do nothing and return if we have enough space. 276 if (HasEnoughSpaceFor(num_bytes, cache_file_directory_)) 277 return true; 278 279 // Otherwise, try to free up the disk space. 280 DVLOG(1) << "Freeing up disk space for " << num_bytes; 281 282 // Remove all entries unless specially marked. 283 scoped_ptr<FileCacheMetadata::Iterator> it = metadata_->GetIterator(); 284 for (; !it->IsAtEnd(); it->Advance()) { 285 const FileCacheEntry& entry = it->GetValue(); 286 if (!entry.is_pinned() && 287 !entry.is_dirty() && 288 !mounted_files_.count(it->GetKey())) 289 metadata_->RemoveCacheEntry(it->GetKey()); 290 } 291 DCHECK(!it->HasError()); 292 293 // Remove all files which have no corresponding cache entries. 294 base::FileEnumerator enumerator(cache_file_directory_, 295 false, // not recursive 296 base::FileEnumerator::FILES); 297 std::string resource_id; 298 std::string md5; 299 FileCacheEntry entry; 300 for (base::FilePath current = enumerator.Next(); !current.empty(); 301 current = enumerator.Next()) { 302 util::ParseCacheFilePath(current, &resource_id, &md5); 303 if (!GetCacheEntry(resource_id, md5, &entry)) 304 file_util::Delete(current, false /* recursive */); 305 } 306 307 // Check the disk space again. 308 return HasEnoughSpaceFor(num_bytes, cache_file_directory_); 309} 310 311void FileCache::GetFileOnUIThread(const std::string& resource_id, 312 const std::string& md5, 313 const GetFileFromCacheCallback& callback) { 314 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 315 DCHECK(!callback.is_null()); 316 317 base::FilePath* cache_file_path = new base::FilePath; 318 base::PostTaskAndReplyWithResult(blocking_task_runner_.get(), 319 FROM_HERE, 320 base::Bind(&FileCache::GetFile, 321 base::Unretained(this), 322 resource_id, 323 md5, 324 cache_file_path), 325 base::Bind(&RunGetFileFromCacheCallback, 326 callback, 327 base::Owned(cache_file_path))); 328} 329 330FileError FileCache::GetFile(const std::string& resource_id, 331 const std::string& md5, 332 base::FilePath* cache_file_path) { 333 AssertOnSequencedWorkerPool(); 334 DCHECK(cache_file_path); 335 336 FileCacheEntry cache_entry; 337 if (!GetCacheEntry(resource_id, md5, &cache_entry) || 338 !cache_entry.is_present()) 339 return FILE_ERROR_NOT_FOUND; 340 341 CachedFileOrigin file_origin = cache_entry.is_dirty() ? 342 CACHED_FILE_LOCALLY_MODIFIED : CACHED_FILE_FROM_SERVER; 343 *cache_file_path = GetCacheFilePath(resource_id, cache_entry.md5(), 344 file_origin); 345 return FILE_ERROR_OK; 346} 347 348void FileCache::StoreOnUIThread(const std::string& resource_id, 349 const std::string& md5, 350 const base::FilePath& source_path, 351 FileOperationType file_operation_type, 352 const FileOperationCallback& callback) { 353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 354 DCHECK(!callback.is_null()); 355 356 base::PostTaskAndReplyWithResult(blocking_task_runner_.get(), 357 FROM_HERE, 358 base::Bind(&FileCache::Store, 359 base::Unretained(this), 360 resource_id, 361 md5, 362 source_path, 363 file_operation_type), 364 callback); 365} 366 367FileError FileCache::Store(const std::string& resource_id, 368 const std::string& md5, 369 const base::FilePath& source_path, 370 FileOperationType file_operation_type) { 371 AssertOnSequencedWorkerPool(); 372 return StoreInternal(resource_id, md5, source_path, file_operation_type); 373} 374 375void FileCache::PinOnUIThread(const std::string& resource_id, 376 const FileOperationCallback& callback) { 377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 378 DCHECK(!callback.is_null()); 379 380 base::PostTaskAndReplyWithResult( 381 blocking_task_runner_.get(), 382 FROM_HERE, 383 base::Bind(&FileCache::Pin, base::Unretained(this), resource_id), 384 callback); 385} 386 387FileError FileCache::Pin(const std::string& resource_id) { 388 AssertOnSequencedWorkerPool(); 389 390 FileCacheEntry cache_entry; 391 metadata_->GetCacheEntry(resource_id, &cache_entry); 392 cache_entry.set_is_pinned(true); 393 metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry); 394 return FILE_ERROR_OK; 395} 396 397void FileCache::UnpinOnUIThread(const std::string& resource_id, 398 const FileOperationCallback& callback) { 399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 400 DCHECK(!callback.is_null()); 401 402 base::PostTaskAndReplyWithResult( 403 blocking_task_runner_.get(), 404 FROM_HERE, 405 base::Bind(&FileCache::Unpin, base::Unretained(this), resource_id), 406 callback); 407} 408 409FileError FileCache::Unpin(const std::string& resource_id) { 410 AssertOnSequencedWorkerPool(); 411 412 // Unpinning a file means its entry must exist in cache. 413 FileCacheEntry cache_entry; 414 if (!metadata_->GetCacheEntry(resource_id, &cache_entry)) 415 return FILE_ERROR_NOT_FOUND; 416 417 // Now that file operations have completed, update metadata. 418 if (cache_entry.is_present()) { 419 cache_entry.set_is_pinned(false); 420 metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry); 421 } else { 422 // Remove the existing entry if we are unpinning a non-present file. 423 metadata_->RemoveCacheEntry(resource_id); 424 } 425 426 // Now it's a chance to free up space if needed. 427 FreeDiskSpaceIfNeededFor(0); 428 429 return FILE_ERROR_OK; 430} 431 432void FileCache::MarkAsMountedOnUIThread( 433 const std::string& resource_id, 434 const GetFileFromCacheCallback& callback) { 435 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 436 DCHECK(!callback.is_null()); 437 438 base::FilePath* cache_file_path = new base::FilePath; 439 base::PostTaskAndReplyWithResult( 440 blocking_task_runner_.get(), 441 FROM_HERE, 442 base::Bind(&FileCache::MarkAsMounted, 443 base::Unretained(this), 444 resource_id, 445 cache_file_path), 446 base::Bind( 447 RunGetFileFromCacheCallback, callback, base::Owned(cache_file_path))); 448} 449 450void FileCache::MarkAsUnmountedOnUIThread( 451 const base::FilePath& file_path, 452 const FileOperationCallback& callback) { 453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 454 DCHECK(!callback.is_null()); 455 456 base::PostTaskAndReplyWithResult( 457 blocking_task_runner_.get(), 458 FROM_HERE, 459 base::Bind( 460 &FileCache::MarkAsUnmounted, base::Unretained(this), file_path), 461 callback); 462} 463 464void FileCache::MarkDirtyOnUIThread(const std::string& resource_id, 465 const std::string& md5, 466 const FileOperationCallback& callback) { 467 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 468 DCHECK(!callback.is_null()); 469 470 base::PostTaskAndReplyWithResult( 471 blocking_task_runner_.get(), 472 FROM_HERE, 473 base::Bind( 474 &FileCache::MarkDirty, base::Unretained(this), resource_id, md5), 475 callback); 476} 477 478FileError FileCache::MarkDirty(const std::string& resource_id, 479 const std::string& md5) { 480 AssertOnSequencedWorkerPool(); 481 482 // If file has already been marked dirty in previous instance of chrome, we 483 // would have lost the md5 info during cache initialization, because the file 484 // would have been renamed to .local extension. 485 // So, search for entry in cache without comparing md5. 486 487 // Marking a file dirty means its entry and actual file blob must exist in 488 // cache. 489 FileCacheEntry cache_entry; 490 if (!metadata_->GetCacheEntry(resource_id, &cache_entry) || 491 !cache_entry.is_present()) { 492 LOG(WARNING) << "Can't mark dirty a file that wasn't cached: res_id=" 493 << resource_id 494 << ", md5=" << md5; 495 return FILE_ERROR_NOT_FOUND; 496 } 497 498 if (cache_entry.is_dirty()) 499 return FILE_ERROR_OK; 500 501 // Get the current path of the file in cache. 502 base::FilePath source_path = GetCacheFilePath(resource_id, md5, 503 CACHED_FILE_FROM_SERVER); 504 // Determine destination path. 505 base::FilePath cache_file_path = GetCacheFilePath( 506 resource_id, md5, CACHED_FILE_LOCALLY_MODIFIED); 507 508 if (!MoveFile(source_path, cache_file_path)) 509 return FILE_ERROR_FAILED; 510 511 // Now that file operations have completed, update metadata. 512 cache_entry.set_md5(md5); 513 cache_entry.set_is_dirty(true); 514 metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry); 515 return FILE_ERROR_OK; 516} 517 518FileError FileCache::ClearDirty(const std::string& resource_id, 519 const std::string& md5) { 520 AssertOnSequencedWorkerPool(); 521 522 // |md5| is the new .<md5> extension to rename the file to. 523 // So, search for entry in cache without comparing md5. 524 FileCacheEntry cache_entry; 525 526 // Clearing a dirty file means its entry and actual file blob must exist in 527 // cache. 528 if (!metadata_->GetCacheEntry(resource_id, &cache_entry) || 529 !cache_entry.is_present()) { 530 LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: " 531 << "res_id=" << resource_id 532 << ", md5=" << md5; 533 return FILE_ERROR_NOT_FOUND; 534 } 535 536 // If a file is not dirty (it should have been marked dirty via 537 // MarkDirtyInCache), clearing its dirty state is an invalid operation. 538 if (!cache_entry.is_dirty()) { 539 LOG(WARNING) << "Can't clear dirty state of a non-dirty file: res_id=" 540 << resource_id 541 << ", md5=" << md5; 542 return FILE_ERROR_INVALID_OPERATION; 543 } 544 545 base::FilePath source_path = GetCacheFilePath(resource_id, md5, 546 CACHED_FILE_LOCALLY_MODIFIED); 547 base::FilePath dest_path = GetCacheFilePath(resource_id, md5, 548 CACHED_FILE_FROM_SERVER); 549 if (!MoveFile(source_path, dest_path)) 550 return FILE_ERROR_FAILED; 551 552 // Now that file operations have completed, update metadata. 553 cache_entry.set_md5(md5); 554 cache_entry.set_is_dirty(false); 555 metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry); 556 return FILE_ERROR_OK; 557} 558 559void FileCache::RemoveOnUIThread(const std::string& resource_id, 560 const FileOperationCallback& callback) { 561 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 562 DCHECK(!callback.is_null()); 563 564 base::PostTaskAndReplyWithResult( 565 blocking_task_runner_.get(), 566 FROM_HERE, 567 base::Bind(&FileCache::Remove, base::Unretained(this), resource_id), 568 callback); 569} 570 571FileError FileCache::Remove(const std::string& resource_id) { 572 AssertOnSequencedWorkerPool(); 573 574 // MD5 is not passed into RemoveCacheEntry because we would delete all 575 // cache files corresponding to <resource_id> regardless of the md5. 576 // So, search for entry in cache without taking md5 into account. 577 FileCacheEntry cache_entry; 578 579 // If entry doesn't exist, nothing to do. 580 if (!metadata_->GetCacheEntry(resource_id, &cache_entry)) 581 return FILE_ERROR_OK; 582 583 // Cannot delete a dirty or mounted file. 584 if (cache_entry.is_dirty() || mounted_files_.count(resource_id)) 585 return FILE_ERROR_IN_USE; 586 587 // Delete files that match "<resource_id>.*" unless modified locally. 588 base::FilePath path_to_delete = GetCacheFilePath(resource_id, util::kWildCard, 589 CACHED_FILE_FROM_SERVER); 590 base::FilePath path_to_keep = GetCacheFilePath(resource_id, std::string(), 591 CACHED_FILE_LOCALLY_MODIFIED); 592 DeleteFilesSelectively(path_to_delete, path_to_keep); 593 594 // Now that all file operations have completed, remove from metadata. 595 metadata_->RemoveCacheEntry(resource_id); 596 597 return FILE_ERROR_OK; 598} 599 600void FileCache::ClearAllOnUIThread(const InitializeCacheCallback& callback) { 601 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 602 DCHECK(!callback.is_null()); 603 604 base::PostTaskAndReplyWithResult( 605 blocking_task_runner_.get(), 606 FROM_HERE, 607 base::Bind(&FileCache::ClearAll, base::Unretained(this)), 608 callback); 609} 610 611bool FileCache::Initialize() { 612 AssertOnSequencedWorkerPool(); 613 614 metadata_.reset(new FileCacheMetadata(blocking_task_runner_.get())); 615 616 switch (metadata_->Initialize(metadata_directory_)) { 617 case FileCacheMetadata::INITIALIZE_FAILED: 618 return false; 619 620 case FileCacheMetadata::INITIALIZE_OPENED: // Do nothing. 621 break; 622 623 case FileCacheMetadata::INITIALIZE_CREATED: { 624 CacheMap cache_map; 625 ScanCacheDirectory(cache_file_directory_, &cache_map); 626 for (CacheMap::const_iterator it = cache_map.begin(); 627 it != cache_map.end(); ++it) { 628 metadata_->AddOrUpdateCacheEntry(it->first, it->second); 629 } 630 break; 631 } 632 } 633 return true; 634} 635 636void FileCache::Destroy() { 637 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 638 639 // Invalidate the weak pointer. 640 weak_ptr_factory_.InvalidateWeakPtrs(); 641 642 // Destroy myself on the blocking pool. 643 // Note that base::DeletePointer<> cannot be used as the destructor of this 644 // class is private. 645 blocking_task_runner_->PostTask( 646 FROM_HERE, 647 base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this))); 648} 649 650void FileCache::DestroyOnBlockingPool() { 651 AssertOnSequencedWorkerPool(); 652 delete this; 653} 654 655FileError FileCache::StoreInternal(const std::string& resource_id, 656 const std::string& md5, 657 const base::FilePath& source_path, 658 FileOperationType file_operation_type) { 659 AssertOnSequencedWorkerPool(); 660 661 int64 file_size = 0; 662 if (file_operation_type == FILE_OPERATION_COPY) { 663 if (!file_util::GetFileSize(source_path, &file_size)) { 664 LOG(WARNING) << "Couldn't get file size for: " << source_path.value(); 665 return FILE_ERROR_FAILED; 666 } 667 } 668 if (!FreeDiskSpaceIfNeededFor(file_size)) 669 return FILE_ERROR_NO_SPACE; 670 671 FileCacheEntry cache_entry; 672 metadata_->GetCacheEntry(resource_id, &cache_entry); 673 674 // If file is dirty or mounted, return error. 675 if (cache_entry.is_dirty() || mounted_files_.count(resource_id)) 676 return FILE_ERROR_IN_USE; 677 678 base::FilePath dest_path = GetCacheFilePath(resource_id, md5, 679 CACHED_FILE_FROM_SERVER); 680 bool success = false; 681 switch (file_operation_type) { 682 case FILE_OPERATION_MOVE: 683 success = MoveFile(source_path, dest_path); 684 break; 685 case FILE_OPERATION_COPY: 686 success = CopyFile(source_path, dest_path); 687 break; 688 default: 689 NOTREACHED(); 690 } 691 692 // Determine search pattern for stale filenames corresponding to resource_id, 693 // either "<resource_id>*" or "<resource_id>.*". 694 base::FilePath stale_filenames_pattern; 695 if (md5.empty()) { 696 // No md5 means no extension, append '*' after base name, i.e. 697 // "<resource_id>*". 698 // Cannot call |dest_path|.ReplaceExtension when there's no md5 extension: 699 // if base name of |dest_path| (i.e. escaped resource_id) contains the 700 // extension separator '.', ReplaceExtension will remove it and everything 701 // after it. The result will be nothing like the escaped resource_id. 702 stale_filenames_pattern = 703 base::FilePath(dest_path.value() + util::kWildCard); 704 } else { 705 // Replace md5 extension with '*' i.e. "<resource_id>.*". 706 // Note that ReplaceExtension automatically prefixes the extension with the 707 // extension separator '.'. 708 stale_filenames_pattern = dest_path.ReplaceExtension(util::kWildCard); 709 } 710 711 // Delete files that match |stale_filenames_pattern| except for |dest_path|. 712 DeleteFilesSelectively(stale_filenames_pattern, dest_path); 713 714 if (success) { 715 // Now that file operations have completed, update metadata. 716 cache_entry.set_md5(md5); 717 cache_entry.set_is_present(true); 718 cache_entry.set_is_dirty(false); 719 metadata_->AddOrUpdateCacheEntry(resource_id, cache_entry); 720 } 721 722 return success ? FILE_ERROR_OK : FILE_ERROR_FAILED; 723} 724 725FileError FileCache::MarkAsMounted(const std::string& resource_id, 726 base::FilePath* cache_file_path) { 727 AssertOnSequencedWorkerPool(); 728 DCHECK(cache_file_path); 729 730 // Get cache entry associated with the resource_id and md5 731 FileCacheEntry cache_entry; 732 if (!metadata_->GetCacheEntry(resource_id, &cache_entry)) 733 return FILE_ERROR_NOT_FOUND; 734 735 if (mounted_files_.count(resource_id)) 736 return FILE_ERROR_INVALID_OPERATION; 737 738 // Ensure the file is readable to cros_disks. See crbug.com/236994. 739 base::FilePath path = GetCacheFilePath( 740 resource_id, cache_entry.md5(), CACHED_FILE_FROM_SERVER); 741 file_util::SetPosixFilePermissions( 742 path, 743 file_util::FILE_PERMISSION_READ_BY_USER | 744 file_util::FILE_PERMISSION_WRITE_BY_USER | 745 file_util::FILE_PERMISSION_READ_BY_GROUP | 746 file_util::FILE_PERMISSION_READ_BY_OTHERS); 747 748 mounted_files_.insert(resource_id); 749 750 *cache_file_path = path; 751 return FILE_ERROR_OK; 752} 753 754FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) { 755 AssertOnSequencedWorkerPool(); 756 DCHECK(IsUnderFileCacheDirectory(file_path)); 757 758 // Parse file path to obtain resource_id, md5 and extra_extension. 759 std::string resource_id; 760 std::string md5; 761 util::ParseCacheFilePath(file_path, &resource_id, &md5); 762 763 // Get cache entry associated with the resource_id and md5 764 FileCacheEntry cache_entry; 765 if (!GetCacheEntry(resource_id, md5, &cache_entry)) 766 return FILE_ERROR_NOT_FOUND; 767 768 std::set<std::string>::iterator it = mounted_files_.find(resource_id); 769 if (it == mounted_files_.end()) 770 return FILE_ERROR_INVALID_OPERATION; 771 772 mounted_files_.erase(it); 773 return FILE_ERROR_OK; 774} 775 776bool FileCache::ClearAll() { 777 AssertOnSequencedWorkerPool(); 778 779 // Remove entries on the metadata. 780 scoped_ptr<FileCacheMetadata::Iterator> it = metadata_->GetIterator(); 781 for (; !it->IsAtEnd(); it->Advance()) 782 metadata_->RemoveCacheEntry(it->GetKey()); 783 784 if (it->HasError()) 785 return false; 786 787 // Remove files. 788 base::FileEnumerator enumerator(cache_file_directory_, 789 false, // not recursive 790 base::FileEnumerator::FILES); 791 for (base::FilePath file = enumerator.Next(); !file.empty(); 792 file = enumerator.Next()) 793 file_util::Delete(file, false /* recursive */); 794 795 return true; 796} 797 798bool FileCache::HasEnoughSpaceFor(int64 num_bytes, 799 const base::FilePath& path) { 800 int64 free_space = 0; 801 if (free_disk_space_getter_) 802 free_space = free_disk_space_getter_->AmountOfFreeDiskSpace(); 803 else 804 free_space = base::SysInfo::AmountOfFreeDiskSpace(path); 805 806 // Subtract this as if this portion does not exist. 807 free_space -= kMinFreeSpace; 808 return (free_space >= num_bytes); 809} 810 811} // namespace internal 812} // namespace drive 813