file_cache.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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/chromeos/drive/resource_metadata_storage.h" 20#include "chrome/browser/google_apis/task_util.h" 21#include "chromeos/chromeos_constants.h" 22#include "content/public/browser/browser_thread.h" 23 24using content::BrowserThread; 25 26namespace drive { 27namespace internal { 28namespace { 29 30typedef std::map<std::string, FileCacheEntry> CacheMap; 31 32// Returns true if |md5| matches the one in |cache_entry| with some 33// exceptions. See the function definition for details. 34bool CheckIfMd5Matches(const std::string& md5, 35 const FileCacheEntry& cache_entry) { 36 if (cache_entry.is_dirty()) { 37 // If the entry is dirty, its MD5 may have been replaced by "local" 38 // during cache initialization, so we don't compare MD5. 39 return true; 40 } else if (cache_entry.is_pinned() && cache_entry.md5().empty()) { 41 // If the entry is pinned, it's ok for the entry to have an empty 42 // MD5. This can happen if the pinned file is not fetched. 43 return true; 44 } else if (md5.empty()) { 45 // If the MD5 matching is not requested, don't check MD5. 46 return true; 47 } else if (md5 == cache_entry.md5()) { 48 // Otherwise, compare the MD5. 49 return true; 50 } 51 return false; 52} 53 54// Scans cache subdirectory and insert found files to |cache_map|. 55void ScanCacheDirectory(const base::FilePath& directory_path, 56 CacheMap* cache_map) { 57 base::FileEnumerator enumerator(directory_path, 58 false, // not recursive 59 base::FileEnumerator::FILES); 60 for (base::FilePath current = enumerator.Next(); !current.empty(); 61 current = enumerator.Next()) { 62 // Extract resource_id and md5 from filename. 63 std::string resource_id; 64 std::string md5; 65 util::ParseCacheFilePath(current, &resource_id, &md5); 66 67 // Determine cache state. 68 FileCacheEntry cache_entry; 69 cache_entry.set_md5(md5); 70 cache_entry.set_is_present(true); 71 72 // Add the dirty bit if |md5| indicates that the file is dirty. 73 if (md5 == util::kLocallyModifiedFileExtension) 74 cache_entry.set_is_dirty(true); 75 76 // Create and insert new entry into cache map. 77 cache_map->insert(std::make_pair(resource_id, cache_entry)); 78 } 79} 80 81// Moves the file. 82bool MoveFile(const base::FilePath& source_path, 83 const base::FilePath& dest_path) { 84 if (!base::Move(source_path, dest_path)) { 85 LOG(ERROR) << "Failed to move " << source_path.value() 86 << " to " << dest_path.value(); 87 return false; 88 } 89 DVLOG(1) << "Moved " << source_path.value() << " to " << dest_path.value(); 90 return true; 91} 92 93// Copies the file. 94bool CopyFile(const base::FilePath& source_path, 95 const base::FilePath& dest_path) { 96 if (!file_util::CopyFile(source_path, dest_path)) { 97 LOG(ERROR) << "Failed to copy " << source_path.value() 98 << " to " << dest_path.value(); 99 return false; 100 } 101 DVLOG(1) << "Copied " << source_path.value() << " to " << dest_path.value(); 102 return true; 103} 104 105// Deletes all files that match |path_to_delete_pattern| except for 106// |path_to_keep| on blocking pool. 107// If |path_to_keep| is empty, all files in |path_to_delete_pattern| are 108// deleted. 109void DeleteFilesSelectively(const base::FilePath& path_to_delete_pattern, 110 const base::FilePath& path_to_keep) { 111 // Enumerate all files in directory of |path_to_delete_pattern| that match 112 // base name of |path_to_delete_pattern|. 113 // If a file is not |path_to_keep|, delete it. 114 bool success = true; 115 base::FileEnumerator enumerator( 116 path_to_delete_pattern.DirName(), 117 false, // not recursive 118 base::FileEnumerator::FILES, 119 path_to_delete_pattern.BaseName().value()); 120 for (base::FilePath current = enumerator.Next(); !current.empty(); 121 current = enumerator.Next()) { 122 // If |path_to_keep| is not empty and same as current, don't delete it. 123 if (!path_to_keep.empty() && current == path_to_keep) 124 continue; 125 126 success = base::Delete(current, false); 127 if (!success) 128 DVLOG(1) << "Error deleting " << current.value(); 129 else 130 DVLOG(1) << "Deleted " << current.value(); 131 } 132} 133 134// Runs callback with pointers dereferenced. 135// Used to implement GetFile, MarkAsMounted. 136void RunGetFileFromCacheCallback( 137 const GetFileFromCacheCallback& callback, 138 base::FilePath* file_path, 139 FileError error) { 140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 141 DCHECK(!callback.is_null()); 142 DCHECK(file_path); 143 144 callback.Run(error, *file_path); 145} 146 147// Runs callback with pointers dereferenced. 148// Used to implement GetCacheEntry(). 149void RunGetCacheEntryCallback(const GetCacheEntryCallback& callback, 150 FileCacheEntry* cache_entry, 151 bool success) { 152 DCHECK(cache_entry); 153 DCHECK(!callback.is_null()); 154 callback.Run(success, *cache_entry); 155} 156 157// Calls |iteration_callback| with each entry in |cache|. 158void IterateCache(FileCache* cache, 159 const CacheIterateCallback& iteration_callback) { 160 scoped_ptr<FileCache::Iterator> it = cache->GetIterator(); 161 for (; !it->IsAtEnd(); it->Advance()) 162 iteration_callback.Run(it->GetID(), it->GetValue()); 163 DCHECK(!it->HasError()); 164} 165 166} // namespace 167 168const base::FilePath::CharType FileCache::kOldCacheMetadataDBName[] = 169 FILE_PATH_LITERAL("cache_metadata.db"); 170 171FileCache::FileCache(ResourceMetadataStorage* storage, 172 const base::FilePath& cache_file_directory, 173 base::SequencedTaskRunner* blocking_task_runner, 174 FreeDiskSpaceGetterInterface* free_disk_space_getter) 175 : cache_file_directory_(cache_file_directory), 176 blocking_task_runner_(blocking_task_runner), 177 storage_(storage), 178 free_disk_space_getter_(free_disk_space_getter), 179 weak_ptr_factory_(this) { 180 DCHECK(blocking_task_runner_.get()); 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 182} 183 184FileCache::~FileCache() { 185 // Must be on the sequenced worker pool, as |metadata_| must be deleted on 186 // the sequenced worker pool. 187 AssertOnSequencedWorkerPool(); 188} 189 190base::FilePath FileCache::GetCacheFilePath(const std::string& resource_id, 191 const std::string& md5, 192 CachedFileOrigin file_origin) const { 193 // Runs on any thread. 194 // Filename is formatted as resource_id.md5, i.e. resource_id is the base 195 // name and md5 is the extension. 196 std::string base_name = util::EscapeCacheFileName(resource_id); 197 if (file_origin == CACHED_FILE_LOCALLY_MODIFIED) { 198 base_name += base::FilePath::kExtensionSeparator; 199 base_name += util::kLocallyModifiedFileExtension; 200 } else if (!md5.empty()) { 201 base_name += base::FilePath::kExtensionSeparator; 202 base_name += util::EscapeCacheFileName(md5); 203 } 204 return cache_file_directory_.Append( 205 base::FilePath::FromUTF8Unsafe(base_name)); 206} 207 208void FileCache::AssertOnSequencedWorkerPool() { 209 DCHECK(!blocking_task_runner_.get() || 210 blocking_task_runner_->RunsTasksOnCurrentThread()); 211} 212 213bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const { 214 return cache_file_directory_.IsParent(path); 215} 216 217void FileCache::GetCacheEntryOnUIThread(const std::string& resource_id, 218 const std::string& md5, 219 const GetCacheEntryCallback& callback) { 220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 221 DCHECK(!callback.is_null()); 222 223 FileCacheEntry* cache_entry = new FileCacheEntry; 224 base::PostTaskAndReplyWithResult( 225 blocking_task_runner_.get(), 226 FROM_HERE, 227 base::Bind(&FileCache::GetCacheEntry, 228 base::Unretained(this), 229 resource_id, 230 md5, 231 cache_entry), 232 base::Bind( 233 &RunGetCacheEntryCallback, callback, base::Owned(cache_entry))); 234} 235 236bool FileCache::GetCacheEntry(const std::string& resource_id, 237 const std::string& md5, 238 FileCacheEntry* entry) { 239 DCHECK(entry); 240 AssertOnSequencedWorkerPool(); 241 return storage_->GetCacheEntry(resource_id, entry) && 242 CheckIfMd5Matches(md5, *entry); 243} 244 245void FileCache::IterateOnUIThread( 246 const CacheIterateCallback& iteration_callback, 247 const base::Closure& completion_callback) { 248 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 249 DCHECK(!iteration_callback.is_null()); 250 DCHECK(!completion_callback.is_null()); 251 252 blocking_task_runner_->PostTaskAndReply( 253 FROM_HERE, 254 base::Bind(&IterateCache, 255 base::Unretained(this), 256 google_apis::CreateRelayCallback(iteration_callback)), 257 completion_callback); 258} 259 260scoped_ptr<FileCache::Iterator> FileCache::GetIterator() { 261 AssertOnSequencedWorkerPool(); 262 return storage_->GetCacheEntryIterator(); 263} 264 265void FileCache::FreeDiskSpaceIfNeededForOnUIThread( 266 int64 num_bytes, 267 const InitializeCacheCallback& callback) { 268 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 269 DCHECK(!callback.is_null()); 270 271 base::PostTaskAndReplyWithResult( 272 blocking_task_runner_.get(), 273 FROM_HERE, 274 base::Bind(&FileCache::FreeDiskSpaceIfNeededFor, 275 base::Unretained(this), 276 num_bytes), 277 callback); 278} 279 280bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) { 281 AssertOnSequencedWorkerPool(); 282 283 // Do nothing and return if we have enough space. 284 if (HasEnoughSpaceFor(num_bytes, cache_file_directory_)) 285 return true; 286 287 // Otherwise, try to free up the disk space. 288 DVLOG(1) << "Freeing up disk space for " << num_bytes; 289 290 // Remove all entries unless specially marked. 291 scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it = 292 storage_->GetCacheEntryIterator(); 293 for (; !it->IsAtEnd(); it->Advance()) { 294 const FileCacheEntry& entry = it->GetValue(); 295 if (!entry.is_pinned() && 296 !entry.is_dirty() && 297 !mounted_files_.count(it->GetID())) 298 storage_->RemoveCacheEntry(it->GetID()); 299 } 300 DCHECK(!it->HasError()); 301 302 // Remove all files which have no corresponding cache entries. 303 base::FileEnumerator enumerator(cache_file_directory_, 304 false, // not recursive 305 base::FileEnumerator::FILES); 306 std::string resource_id; 307 std::string md5; 308 FileCacheEntry entry; 309 for (base::FilePath current = enumerator.Next(); !current.empty(); 310 current = enumerator.Next()) { 311 util::ParseCacheFilePath(current, &resource_id, &md5); 312 if (!GetCacheEntry(resource_id, md5, &entry)) 313 base::Delete(current, false /* recursive */); 314 } 315 316 // Check the disk space again. 317 return HasEnoughSpaceFor(num_bytes, cache_file_directory_); 318} 319 320void FileCache::GetFileOnUIThread(const std::string& resource_id, 321 const std::string& md5, 322 const GetFileFromCacheCallback& callback) { 323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 324 DCHECK(!callback.is_null()); 325 326 base::FilePath* cache_file_path = new base::FilePath; 327 base::PostTaskAndReplyWithResult(blocking_task_runner_.get(), 328 FROM_HERE, 329 base::Bind(&FileCache::GetFile, 330 base::Unretained(this), 331 resource_id, 332 md5, 333 cache_file_path), 334 base::Bind(&RunGetFileFromCacheCallback, 335 callback, 336 base::Owned(cache_file_path))); 337} 338 339FileError FileCache::GetFile(const std::string& resource_id, 340 const std::string& md5, 341 base::FilePath* cache_file_path) { 342 AssertOnSequencedWorkerPool(); 343 DCHECK(cache_file_path); 344 345 FileCacheEntry cache_entry; 346 if (!GetCacheEntry(resource_id, md5, &cache_entry) || 347 !cache_entry.is_present()) 348 return FILE_ERROR_NOT_FOUND; 349 350 CachedFileOrigin file_origin = cache_entry.is_dirty() ? 351 CACHED_FILE_LOCALLY_MODIFIED : CACHED_FILE_FROM_SERVER; 352 *cache_file_path = GetCacheFilePath(resource_id, cache_entry.md5(), 353 file_origin); 354 return FILE_ERROR_OK; 355} 356 357void FileCache::StoreOnUIThread(const std::string& resource_id, 358 const std::string& md5, 359 const base::FilePath& source_path, 360 FileOperationType file_operation_type, 361 const FileOperationCallback& callback) { 362 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 363 DCHECK(!callback.is_null()); 364 365 base::PostTaskAndReplyWithResult(blocking_task_runner_.get(), 366 FROM_HERE, 367 base::Bind(&FileCache::Store, 368 base::Unretained(this), 369 resource_id, 370 md5, 371 source_path, 372 file_operation_type), 373 callback); 374} 375 376FileError FileCache::Store(const std::string& resource_id, 377 const std::string& md5, 378 const base::FilePath& source_path, 379 FileOperationType file_operation_type) { 380 AssertOnSequencedWorkerPool(); 381 return StoreInternal(resource_id, md5, source_path, file_operation_type); 382} 383 384void FileCache::PinOnUIThread(const std::string& resource_id, 385 const FileOperationCallback& callback) { 386 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 387 DCHECK(!callback.is_null()); 388 389 base::PostTaskAndReplyWithResult( 390 blocking_task_runner_.get(), 391 FROM_HERE, 392 base::Bind(&FileCache::Pin, base::Unretained(this), resource_id), 393 callback); 394} 395 396FileError FileCache::Pin(const std::string& resource_id) { 397 AssertOnSequencedWorkerPool(); 398 399 FileCacheEntry cache_entry; 400 storage_->GetCacheEntry(resource_id, &cache_entry); 401 cache_entry.set_is_pinned(true); 402 storage_->PutCacheEntry(resource_id, cache_entry); 403 return FILE_ERROR_OK; 404} 405 406void FileCache::UnpinOnUIThread(const std::string& resource_id, 407 const FileOperationCallback& callback) { 408 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 409 DCHECK(!callback.is_null()); 410 411 base::PostTaskAndReplyWithResult( 412 blocking_task_runner_.get(), 413 FROM_HERE, 414 base::Bind(&FileCache::Unpin, base::Unretained(this), resource_id), 415 callback); 416} 417 418FileError FileCache::Unpin(const std::string& resource_id) { 419 AssertOnSequencedWorkerPool(); 420 421 // Unpinning a file means its entry must exist in cache. 422 FileCacheEntry cache_entry; 423 if (!storage_->GetCacheEntry(resource_id, &cache_entry)) 424 return FILE_ERROR_NOT_FOUND; 425 426 // Now that file operations have completed, update metadata. 427 if (cache_entry.is_present()) { 428 cache_entry.set_is_pinned(false); 429 storage_->PutCacheEntry(resource_id, cache_entry); 430 } else { 431 // Remove the existing entry if we are unpinning a non-present file. 432 storage_->RemoveCacheEntry(resource_id); 433 } 434 435 // Now it's a chance to free up space if needed. 436 FreeDiskSpaceIfNeededFor(0); 437 438 return FILE_ERROR_OK; 439} 440 441void FileCache::MarkAsMountedOnUIThread( 442 const std::string& resource_id, 443 const GetFileFromCacheCallback& callback) { 444 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 445 DCHECK(!callback.is_null()); 446 447 base::FilePath* cache_file_path = new base::FilePath; 448 base::PostTaskAndReplyWithResult( 449 blocking_task_runner_.get(), 450 FROM_HERE, 451 base::Bind(&FileCache::MarkAsMounted, 452 base::Unretained(this), 453 resource_id, 454 cache_file_path), 455 base::Bind( 456 RunGetFileFromCacheCallback, callback, base::Owned(cache_file_path))); 457} 458 459void FileCache::MarkAsUnmountedOnUIThread( 460 const base::FilePath& file_path, 461 const FileOperationCallback& callback) { 462 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 463 DCHECK(!callback.is_null()); 464 465 base::PostTaskAndReplyWithResult( 466 blocking_task_runner_.get(), 467 FROM_HERE, 468 base::Bind( 469 &FileCache::MarkAsUnmounted, base::Unretained(this), file_path), 470 callback); 471} 472 473void FileCache::MarkDirtyOnUIThread(const std::string& resource_id, 474 const std::string& md5, 475 const FileOperationCallback& callback) { 476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 477 DCHECK(!callback.is_null()); 478 479 base::PostTaskAndReplyWithResult( 480 blocking_task_runner_.get(), 481 FROM_HERE, 482 base::Bind( 483 &FileCache::MarkDirty, base::Unretained(this), resource_id, md5), 484 callback); 485} 486 487FileError FileCache::MarkDirty(const std::string& resource_id, 488 const std::string& md5) { 489 AssertOnSequencedWorkerPool(); 490 491 // If file has already been marked dirty in previous instance of chrome, we 492 // would have lost the md5 info during cache initialization, because the file 493 // would have been renamed to .local extension. 494 // So, search for entry in cache without comparing md5. 495 496 // Marking a file dirty means its entry and actual file blob must exist in 497 // cache. 498 FileCacheEntry cache_entry; 499 if (!storage_->GetCacheEntry(resource_id, &cache_entry) || 500 !cache_entry.is_present()) { 501 LOG(WARNING) << "Can't mark dirty a file that wasn't cached: res_id=" 502 << resource_id 503 << ", md5=" << md5; 504 return FILE_ERROR_NOT_FOUND; 505 } 506 507 if (cache_entry.is_dirty()) 508 return FILE_ERROR_OK; 509 510 // Get the current path of the file in cache. 511 base::FilePath source_path = GetCacheFilePath(resource_id, md5, 512 CACHED_FILE_FROM_SERVER); 513 // Determine destination path. 514 base::FilePath cache_file_path = GetCacheFilePath( 515 resource_id, md5, CACHED_FILE_LOCALLY_MODIFIED); 516 517 if (!MoveFile(source_path, cache_file_path)) 518 return FILE_ERROR_FAILED; 519 520 // Now that file operations have completed, update metadata. 521 cache_entry.set_md5(md5); 522 cache_entry.set_is_dirty(true); 523 storage_->PutCacheEntry(resource_id, cache_entry); 524 return FILE_ERROR_OK; 525} 526 527FileError FileCache::ClearDirty(const std::string& resource_id, 528 const std::string& md5) { 529 AssertOnSequencedWorkerPool(); 530 531 // |md5| is the new .<md5> extension to rename the file to. 532 // So, search for entry in cache without comparing md5. 533 FileCacheEntry cache_entry; 534 535 // Clearing a dirty file means its entry and actual file blob must exist in 536 // cache. 537 if (!storage_->GetCacheEntry(resource_id, &cache_entry) || 538 !cache_entry.is_present()) { 539 LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: " 540 << "res_id=" << resource_id 541 << ", md5=" << md5; 542 return FILE_ERROR_NOT_FOUND; 543 } 544 545 // If a file is not dirty (it should have been marked dirty via 546 // MarkDirtyInCache), clearing its dirty state is an invalid operation. 547 if (!cache_entry.is_dirty()) { 548 LOG(WARNING) << "Can't clear dirty state of a non-dirty file: res_id=" 549 << resource_id 550 << ", md5=" << md5; 551 return FILE_ERROR_INVALID_OPERATION; 552 } 553 554 base::FilePath source_path = GetCacheFilePath(resource_id, md5, 555 CACHED_FILE_LOCALLY_MODIFIED); 556 base::FilePath dest_path = GetCacheFilePath(resource_id, md5, 557 CACHED_FILE_FROM_SERVER); 558 if (!MoveFile(source_path, dest_path)) 559 return FILE_ERROR_FAILED; 560 561 // Now that file operations have completed, update metadata. 562 cache_entry.set_md5(md5); 563 cache_entry.set_is_dirty(false); 564 storage_->PutCacheEntry(resource_id, cache_entry); 565 return FILE_ERROR_OK; 566} 567 568void FileCache::RemoveOnUIThread(const std::string& resource_id, 569 const FileOperationCallback& callback) { 570 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 571 DCHECK(!callback.is_null()); 572 573 base::PostTaskAndReplyWithResult( 574 blocking_task_runner_.get(), 575 FROM_HERE, 576 base::Bind(&FileCache::Remove, base::Unretained(this), resource_id), 577 callback); 578} 579 580FileError FileCache::Remove(const std::string& resource_id) { 581 AssertOnSequencedWorkerPool(); 582 583 // MD5 is not passed into RemoveCacheEntry because we would delete all 584 // cache files corresponding to <resource_id> regardless of the md5. 585 // So, search for entry in cache without taking md5 into account. 586 FileCacheEntry cache_entry; 587 588 // If entry doesn't exist, nothing to do. 589 if (!storage_->GetCacheEntry(resource_id, &cache_entry)) 590 return FILE_ERROR_OK; 591 592 // Cannot delete a dirty or mounted file. 593 if (cache_entry.is_dirty() || mounted_files_.count(resource_id)) 594 return FILE_ERROR_IN_USE; 595 596 // Delete files that match "<resource_id>.*" unless modified locally. 597 base::FilePath path_to_delete = GetCacheFilePath(resource_id, util::kWildCard, 598 CACHED_FILE_FROM_SERVER); 599 base::FilePath path_to_keep = GetCacheFilePath(resource_id, std::string(), 600 CACHED_FILE_LOCALLY_MODIFIED); 601 DeleteFilesSelectively(path_to_delete, path_to_keep); 602 603 // Now that all file operations have completed, remove from metadata. 604 storage_->RemoveCacheEntry(resource_id); 605 606 return FILE_ERROR_OK; 607} 608 609void FileCache::ClearAllOnUIThread(const InitializeCacheCallback& callback) { 610 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 611 DCHECK(!callback.is_null()); 612 613 base::PostTaskAndReplyWithResult( 614 blocking_task_runner_.get(), 615 FROM_HERE, 616 base::Bind(&FileCache::ClearAll, base::Unretained(this)), 617 callback); 618} 619 620bool FileCache::Initialize() { 621 AssertOnSequencedWorkerPool(); 622 623 if (!ImportOldDB(storage_->directory_path().Append( 624 kOldCacheMetadataDBName)) && 625 !storage_->opened_existing_db()) { 626 CacheMap cache_map; 627 ScanCacheDirectory(cache_file_directory_, &cache_map); 628 for (CacheMap::const_iterator it = cache_map.begin(); 629 it != cache_map.end(); ++it) { 630 storage_->PutCacheEntry(it->first, it->second); 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 storage_->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 storage_->PutCacheEntry(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 (!storage_->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<ResourceMetadataStorage::CacheEntryIterator> it = 781 storage_->GetCacheEntryIterator(); 782 for (; !it->IsAtEnd(); it->Advance()) 783 storage_->RemoveCacheEntry(it->GetID()); 784 785 if (it->HasError()) 786 return false; 787 788 // Remove files. 789 base::FileEnumerator enumerator(cache_file_directory_, 790 false, // not recursive 791 base::FileEnumerator::FILES); 792 for (base::FilePath file = enumerator.Next(); !file.empty(); 793 file = enumerator.Next()) 794 base::Delete(file, false /* recursive */); 795 796 return true; 797} 798 799bool FileCache::HasEnoughSpaceFor(int64 num_bytes, 800 const base::FilePath& path) { 801 int64 free_space = 0; 802 if (free_disk_space_getter_) 803 free_space = free_disk_space_getter_->AmountOfFreeDiskSpace(); 804 else 805 free_space = base::SysInfo::AmountOfFreeDiskSpace(path); 806 807 // Subtract this as if this portion does not exist. 808 free_space -= kMinFreeSpace; 809 return (free_space >= num_bytes); 810} 811 812bool FileCache::ImportOldDB(const base::FilePath& old_db_path) { 813 if (!file_util::PathExists(old_db_path)) // Old DB is not there, do nothing. 814 return false; 815 816 // Copy all entries stored in the old DB. 817 bool imported = false; 818 { 819 FileCacheMetadata old_data(blocking_task_runner_); 820 if (old_data.Initialize(old_db_path) == 821 FileCacheMetadata::INITIALIZE_OPENED) { 822 scoped_ptr<FileCacheMetadata::Iterator> it = old_data.GetIterator(); 823 for (; !it->IsAtEnd(); it->Advance()) { 824 FileCacheEntry entry; 825 if (storage_->GetCacheEntry(it->GetKey(), &entry)) 826 continue; // Do not overwrite. 827 828 storage_->PutCacheEntry(it->GetKey(), it->GetValue()); 829 } 830 imported = true; 831 } 832 } 833 834 // Delete old DB. 835 base::Delete(old_db_path, true /* recursive */ ); 836 return imported; 837} 838 839} // namespace internal 840} // namespace drive 841