Index: chrome/browser/chromeos/drive/file_cache.cc |
diff --git a/chrome/browser/chromeos/drive/file_cache.cc b/chrome/browser/chromeos/drive/file_cache.cc |
deleted file mode 100644 |
index 9ffd827bb56f8e0929ea9a377a9240cce7897879..0000000000000000000000000000000000000000 |
--- a/chrome/browser/chromeos/drive/file_cache.cc |
+++ /dev/null |
@@ -1,617 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include "chrome/browser/chromeos/drive/file_cache.h" |
- |
-#include <vector> |
- |
-#include "base/bind.h" |
-#include "base/bind_helpers.h" |
-#include "base/callback_helpers.h" |
-#include "base/files/file_enumerator.h" |
-#include "base/files/file_util.h" |
-#include "base/location.h" |
-#include "base/logging.h" |
-#include "base/metrics/histogram.h" |
-#include "base/strings/string_util.h" |
-#include "base/strings/stringprintf.h" |
-#include "base/sys_info.h" |
-#include "chrome/browser/chromeos/drive/file_system_core_util.h" |
-#include "chrome/browser/chromeos/drive/resource_metadata_storage.h" |
-#include "chromeos/chromeos_constants.h" |
-#include "components/drive/drive.pb.h" |
-#include "components/drive/drive_api_util.h" |
-#include "google_apis/drive/task_util.h" |
-#include "net/base/filename_util.h" |
-#include "net/base/mime_sniffer.h" |
-#include "net/base/mime_util.h" |
-#include "third_party/cros_system_api/constants/cryptohome.h" |
- |
-namespace drive { |
-namespace internal { |
-namespace { |
- |
-// Returns ID extracted from the path. |
-std::string GetIdFromPath(const base::FilePath& path) { |
- return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe()); |
-} |
- |
-} // namespace |
- |
-FileCache::FileCache(ResourceMetadataStorage* storage, |
- const base::FilePath& cache_file_directory, |
- base::SequencedTaskRunner* blocking_task_runner, |
- FreeDiskSpaceGetterInterface* free_disk_space_getter) |
- : cache_file_directory_(cache_file_directory), |
- blocking_task_runner_(blocking_task_runner), |
- storage_(storage), |
- free_disk_space_getter_(free_disk_space_getter), |
- weak_ptr_factory_(this) { |
- DCHECK(blocking_task_runner_.get()); |
-} |
- |
-FileCache::~FileCache() { |
- // Must be on the sequenced worker pool, as |metadata_| must be deleted on |
- // the sequenced worker pool. |
- AssertOnSequencedWorkerPool(); |
-} |
- |
-base::FilePath FileCache::GetCacheFilePath(const std::string& id) const { |
- return cache_file_directory_.Append( |
- base::FilePath::FromUTF8Unsafe(util::EscapeCacheFileName(id))); |
-} |
- |
-void FileCache::AssertOnSequencedWorkerPool() { |
- DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); |
-} |
- |
-bool FileCache::IsUnderFileCacheDirectory(const base::FilePath& path) const { |
- return cache_file_directory_.IsParent(path); |
-} |
- |
-bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) { |
- AssertOnSequencedWorkerPool(); |
- |
- // Do nothing and return if we have enough space. |
- if (HasEnoughSpaceFor(num_bytes, cache_file_directory_)) |
- return true; |
- |
- // Otherwise, try to free up the disk space. |
- DVLOG(1) << "Freeing up disk space for " << num_bytes; |
- |
- // Remove all entries unless specially marked. |
- scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator(); |
- for (; !it->IsAtEnd(); it->Advance()) { |
- if (it->GetValue().file_specific_info().has_cache_state() && |
- !it->GetValue().file_specific_info().cache_state().is_pinned() && |
- !it->GetValue().file_specific_info().cache_state().is_dirty() && |
- !mounted_files_.count(it->GetID())) { |
- ResourceEntry entry(it->GetValue()); |
- entry.mutable_file_specific_info()->clear_cache_state(); |
- storage_->PutEntry(entry); |
- } |
- } |
- if (it->HasError()) |
- return false; |
- |
- // Remove all files which have no corresponding cache entries. |
- base::FileEnumerator enumerator(cache_file_directory_, |
- false, // not recursive |
- base::FileEnumerator::FILES); |
- ResourceEntry entry; |
- for (base::FilePath current = enumerator.Next(); !current.empty(); |
- current = enumerator.Next()) { |
- std::string id = GetIdFromPath(current); |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error == FILE_ERROR_NOT_FOUND || |
- (error == FILE_ERROR_OK && |
- !entry.file_specific_info().cache_state().is_present())) |
- base::DeleteFile(current, false /* recursive */); |
- else if (error != FILE_ERROR_OK) |
- return false; |
- } |
- |
- // Check the disk space again. |
- return HasEnoughSpaceFor(num_bytes, cache_file_directory_); |
-} |
- |
-FileError FileCache::GetFile(const std::string& id, |
- base::FilePath* cache_file_path) { |
- AssertOnSequencedWorkerPool(); |
- DCHECK(cache_file_path); |
- |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- if (!entry.file_specific_info().cache_state().is_present()) |
- return FILE_ERROR_NOT_FOUND; |
- |
- *cache_file_path = GetCacheFilePath(id); |
- return FILE_ERROR_OK; |
-} |
- |
-FileError FileCache::Store(const std::string& id, |
- const std::string& md5, |
- const base::FilePath& source_path, |
- FileOperationType file_operation_type) { |
- AssertOnSequencedWorkerPool(); |
- |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- |
- int64 file_size = 0; |
- if (file_operation_type == FILE_OPERATION_COPY) { |
- if (!base::GetFileSize(source_path, &file_size)) { |
- LOG(WARNING) << "Couldn't get file size for: " << source_path.value(); |
- return FILE_ERROR_FAILED; |
- } |
- } |
- if (!FreeDiskSpaceIfNeededFor(file_size)) |
- return FILE_ERROR_NO_LOCAL_SPACE; |
- |
- // If file is mounted, return error. |
- if (mounted_files_.count(id)) |
- return FILE_ERROR_IN_USE; |
- |
- base::FilePath dest_path = GetCacheFilePath(id); |
- bool success = false; |
- switch (file_operation_type) { |
- case FILE_OPERATION_MOVE: |
- success = base::Move(source_path, dest_path); |
- break; |
- case FILE_OPERATION_COPY: |
- success = base::CopyFile(source_path, dest_path); |
- break; |
- default: |
- NOTREACHED(); |
- } |
- |
- if (!success) { |
- LOG(ERROR) << "Failed to store: " |
- << "source_path = " << source_path.value() << ", " |
- << "dest_path = " << dest_path.value() << ", " |
- << "file_operation_type = " << file_operation_type; |
- return FILE_ERROR_FAILED; |
- } |
- |
- // Now that file operations have completed, update metadata. |
- FileCacheEntry* cache_state = |
- entry.mutable_file_specific_info()->mutable_cache_state(); |
- cache_state->set_md5(md5); |
- cache_state->set_is_present(true); |
- if (md5.empty()) |
- cache_state->set_is_dirty(true); |
- return storage_->PutEntry(entry); |
-} |
- |
-FileError FileCache::Pin(const std::string& id) { |
- AssertOnSequencedWorkerPool(); |
- |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- entry.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned( |
- true); |
- return storage_->PutEntry(entry); |
-} |
- |
-FileError FileCache::Unpin(const std::string& id) { |
- AssertOnSequencedWorkerPool(); |
- |
- // Unpinning a file means its entry must exist in cache. |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- |
- // Now that file operations have completed, update metadata. |
- if (entry.file_specific_info().cache_state().is_present()) { |
- entry.mutable_file_specific_info()->mutable_cache_state()->set_is_pinned( |
- false); |
- } else { |
- // Remove the existing entry if we are unpinning a non-present file. |
- entry.mutable_file_specific_info()->clear_cache_state(); |
- } |
- error = storage_->PutEntry(entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- |
- // Now it's a chance to free up space if needed. |
- FreeDiskSpaceIfNeededFor(0); |
- |
- return FILE_ERROR_OK; |
-} |
- |
-FileError FileCache::MarkAsMounted(const std::string& id, |
- base::FilePath* cache_file_path) { |
- AssertOnSequencedWorkerPool(); |
- DCHECK(cache_file_path); |
- |
- // Get cache entry associated with the id and md5 |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- if (!entry.file_specific_info().cache_state().is_present()) |
- return FILE_ERROR_NOT_FOUND; |
- |
- if (mounted_files_.count(id)) |
- return FILE_ERROR_INVALID_OPERATION; |
- |
- // Ensure the file is readable to cros_disks. See crbug.com/236994. |
- base::FilePath path = GetCacheFilePath(id); |
- if (!base::SetPosixFilePermissions( |
- path, |
- base::FILE_PERMISSION_READ_BY_USER | |
- base::FILE_PERMISSION_WRITE_BY_USER | |
- base::FILE_PERMISSION_READ_BY_GROUP | |
- base::FILE_PERMISSION_READ_BY_OTHERS)) |
- return FILE_ERROR_FAILED; |
- |
- mounted_files_.insert(id); |
- |
- *cache_file_path = path; |
- return FILE_ERROR_OK; |
-} |
- |
-FileError FileCache::OpenForWrite( |
- const std::string& id, |
- scoped_ptr<base::ScopedClosureRunner>* file_closer) { |
- AssertOnSequencedWorkerPool(); |
- |
- // Marking a file dirty means its entry and actual file blob must exist in |
- // cache. |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- if (!entry.file_specific_info().cache_state().is_present()) { |
- LOG(WARNING) << "Can't mark dirty a file that wasn't cached: " << id; |
- return FILE_ERROR_NOT_FOUND; |
- } |
- |
- entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty(true); |
- entry.mutable_file_specific_info()->mutable_cache_state()->clear_md5(); |
- error = storage_->PutEntry(entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- |
- write_opened_files_[id]++; |
- file_closer->reset(new base::ScopedClosureRunner( |
- base::Bind(&google_apis::RunTaskWithTaskRunner, |
- blocking_task_runner_, |
- base::Bind(&FileCache::CloseForWrite, |
- weak_ptr_factory_.GetWeakPtr(), |
- id)))); |
- return FILE_ERROR_OK; |
-} |
- |
-bool FileCache::IsOpenedForWrite(const std::string& id) { |
- AssertOnSequencedWorkerPool(); |
- return write_opened_files_.count(id) != 0; |
-} |
- |
-FileError FileCache::UpdateMd5(const std::string& id) { |
- AssertOnSequencedWorkerPool(); |
- |
- if (IsOpenedForWrite(id)) |
- return FILE_ERROR_IN_USE; |
- |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- if (!entry.file_specific_info().cache_state().is_present()) |
- return FILE_ERROR_NOT_FOUND; |
- |
- const std::string& md5 = |
- util::GetMd5Digest(GetCacheFilePath(id), &in_shutdown_); |
- if (in_shutdown_.IsSet()) |
- return FILE_ERROR_ABORT; |
- if (md5.empty()) |
- return FILE_ERROR_NOT_FOUND; |
- |
- entry.mutable_file_specific_info()->mutable_cache_state()->set_md5(md5); |
- return storage_->PutEntry(entry); |
-} |
- |
-FileError FileCache::ClearDirty(const std::string& id) { |
- AssertOnSequencedWorkerPool(); |
- |
- if (IsOpenedForWrite(id)) |
- return FILE_ERROR_IN_USE; |
- |
- // Clearing a dirty file means its entry and actual file blob must exist in |
- // cache. |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- if (!entry.file_specific_info().cache_state().is_present()) { |
- LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: " |
- << id; |
- return FILE_ERROR_NOT_FOUND; |
- } |
- |
- // If a file is not dirty (it should have been marked dirty via OpenForWrite), |
- // clearing its dirty state is an invalid operation. |
- if (!entry.file_specific_info().cache_state().is_dirty()) { |
- LOG(WARNING) << "Can't clear dirty state of a non-dirty file: " << id; |
- return FILE_ERROR_INVALID_OPERATION; |
- } |
- |
- entry.mutable_file_specific_info()->mutable_cache_state()->set_is_dirty( |
- false); |
- return storage_->PutEntry(entry); |
-} |
- |
-FileError FileCache::Remove(const std::string& id) { |
- AssertOnSequencedWorkerPool(); |
- |
- ResourceEntry entry; |
- |
- // If entry doesn't exist, nothing to do. |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error == FILE_ERROR_NOT_FOUND) |
- return FILE_ERROR_OK; |
- if (error != FILE_ERROR_OK) |
- return error; |
- if (!entry.file_specific_info().has_cache_state()) |
- return FILE_ERROR_OK; |
- |
- // Cannot delete a mounted file. |
- if (mounted_files_.count(id)) |
- return FILE_ERROR_IN_USE; |
- |
- // Delete the file. |
- base::FilePath path = GetCacheFilePath(id); |
- if (!base::DeleteFile(path, false /* recursive */)) |
- return FILE_ERROR_FAILED; |
- |
- // Now that all file operations have completed, remove from metadata. |
- entry.mutable_file_specific_info()->clear_cache_state(); |
- return storage_->PutEntry(entry); |
-} |
- |
-bool FileCache::ClearAll() { |
- AssertOnSequencedWorkerPool(); |
- |
- // Remove files. |
- base::FileEnumerator enumerator(cache_file_directory_, |
- false, // not recursive |
- base::FileEnumerator::FILES); |
- for (base::FilePath file = enumerator.Next(); !file.empty(); |
- file = enumerator.Next()) |
- base::DeleteFile(file, false /* recursive */); |
- |
- return true; |
-} |
- |
-bool FileCache::Initialize() { |
- AssertOnSequencedWorkerPool(); |
- |
- // Older versions do not clear MD5 when marking entries dirty. |
- // Clear MD5 of all dirty entries to deal with old data. |
- scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator(); |
- for (; !it->IsAtEnd(); it->Advance()) { |
- if (it->GetValue().file_specific_info().cache_state().is_dirty()) { |
- ResourceEntry new_entry(it->GetValue()); |
- new_entry.mutable_file_specific_info()->mutable_cache_state()-> |
- clear_md5(); |
- if (storage_->PutEntry(new_entry) != FILE_ERROR_OK) |
- return false; |
- } |
- } |
- if (it->HasError()) |
- return false; |
- |
- if (!RenameCacheFilesToNewFormat()) |
- return false; |
- return true; |
-} |
- |
-void FileCache::Destroy() { |
- DCHECK(thread_checker_.CalledOnValidThread()); |
- |
- in_shutdown_.Set(); |
- |
- // Destroy myself on the blocking pool. |
- // Note that base::DeletePointer<> cannot be used as the destructor of this |
- // class is private. |
- blocking_task_runner_->PostTask( |
- FROM_HERE, |
- base::Bind(&FileCache::DestroyOnBlockingPool, base::Unretained(this))); |
-} |
- |
-void FileCache::DestroyOnBlockingPool() { |
- AssertOnSequencedWorkerPool(); |
- delete this; |
-} |
- |
-bool FileCache::RecoverFilesFromCacheDirectory( |
- const base::FilePath& dest_directory, |
- const ResourceMetadataStorage::RecoveredCacheInfoMap& |
- recovered_cache_info) { |
- int file_number = 1; |
- |
- base::FileEnumerator enumerator(cache_file_directory_, |
- false, // not recursive |
- base::FileEnumerator::FILES); |
- for (base::FilePath current = enumerator.Next(); !current.empty(); |
- current = enumerator.Next()) { |
- const std::string& id = GetIdFromPath(current); |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK && error != FILE_ERROR_NOT_FOUND) |
- return false; |
- if (error == FILE_ERROR_OK && |
- entry.file_specific_info().cache_state().is_present()) { |
- // This file is managed by FileCache, no need to recover it. |
- continue; |
- } |
- |
- // If a cache entry which is non-dirty and has matching MD5 is found in |
- // |recovered_cache_entries|, it means the current file is already uploaded |
- // to the server. Just delete it instead of recovering it. |
- ResourceMetadataStorage::RecoveredCacheInfoMap::const_iterator it = |
- recovered_cache_info.find(id); |
- if (it != recovered_cache_info.end()) { |
- // Due to the DB corruption, cache info might be recovered from old |
- // revision. Perform MD5 check even when is_dirty is false just in case. |
- if (!it->second.is_dirty && |
- it->second.md5 == util::GetMd5Digest(current, &in_shutdown_)) { |
- base::DeleteFile(current, false /* recursive */); |
- continue; |
- } |
- } |
- |
- // Read file contents to sniff mime type. |
- std::vector<char> content(net::kMaxBytesToSniff); |
- const int read_result = |
- base::ReadFile(current, &content[0], content.size()); |
- if (read_result < 0) { |
- LOG(WARNING) << "Cannot read: " << current.value(); |
- return false; |
- } |
- if (read_result == 0) // Skip empty files. |
- continue; |
- |
- // Use recovered file name if available, otherwise decide file name with |
- // sniffed mime type. |
- base::FilePath dest_base_name(FILE_PATH_LITERAL("file")); |
- std::string mime_type; |
- if (it != recovered_cache_info.end() && !it->second.title.empty()) { |
- // We can use a file name recovered from the trashed DB. |
- dest_base_name = base::FilePath::FromUTF8Unsafe(it->second.title); |
- } else if (net::SniffMimeType(&content[0], read_result, |
- net::FilePathToFileURL(current), |
- std::string(), &mime_type) || |
- net::SniffMimeTypeFromLocalData(&content[0], read_result, |
- &mime_type)) { |
- // Change base name for common mime types. |
- if (net::MatchesMimeType("image/*", mime_type)) { |
- dest_base_name = base::FilePath(FILE_PATH_LITERAL("image")); |
- } else if (net::MatchesMimeType("video/*", mime_type)) { |
- dest_base_name = base::FilePath(FILE_PATH_LITERAL("video")); |
- } else if (net::MatchesMimeType("audio/*", mime_type)) { |
- dest_base_name = base::FilePath(FILE_PATH_LITERAL("audio")); |
- } |
- |
- // Estimate extension from mime type. |
- std::vector<base::FilePath::StringType> extensions; |
- base::FilePath::StringType extension; |
- if (net::GetPreferredExtensionForMimeType(mime_type, &extension)) |
- extensions.push_back(extension); |
- else |
- net::GetExtensionsForMimeType(mime_type, &extensions); |
- |
- // Add extension if possible. |
- if (!extensions.empty()) |
- dest_base_name = dest_base_name.AddExtension(extensions[0]); |
- } |
- |
- // Add file number to the file name and move. |
- const base::FilePath& dest_path = dest_directory.Append(dest_base_name) |
- .InsertBeforeExtensionASCII(base::StringPrintf("%08d", file_number++)); |
- if (!base::CreateDirectory(dest_directory) || |
- !base::Move(current, dest_path)) { |
- LOG(WARNING) << "Failed to move: " << current.value() |
- << " to " << dest_path.value(); |
- return false; |
- } |
- } |
- UMA_HISTOGRAM_COUNTS("Drive.NumberOfCacheFilesRecoveredAfterDBCorruption", |
- file_number - 1); |
- return true; |
-} |
- |
-FileError FileCache::MarkAsUnmounted(const base::FilePath& file_path) { |
- AssertOnSequencedWorkerPool(); |
- DCHECK(IsUnderFileCacheDirectory(file_path)); |
- |
- std::string id = GetIdFromPath(file_path); |
- |
- // Get the entry associated with the id. |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) |
- return error; |
- |
- std::set<std::string>::iterator it = mounted_files_.find(id); |
- if (it == mounted_files_.end()) |
- return FILE_ERROR_INVALID_OPERATION; |
- |
- mounted_files_.erase(it); |
- return FILE_ERROR_OK; |
-} |
- |
-bool FileCache::HasEnoughSpaceFor(int64 num_bytes, |
- const base::FilePath& path) { |
- int64 free_space = 0; |
- if (free_disk_space_getter_) |
- free_space = free_disk_space_getter_->AmountOfFreeDiskSpace(); |
- else |
- free_space = base::SysInfo::AmountOfFreeDiskSpace(path); |
- |
- // Subtract this as if this portion does not exist. |
- free_space -= cryptohome::kMinFreeSpaceInBytes; |
- return (free_space >= num_bytes); |
-} |
- |
-bool FileCache::RenameCacheFilesToNewFormat() { |
- base::FileEnumerator enumerator(cache_file_directory_, |
- false, // not recursive |
- base::FileEnumerator::FILES); |
- for (base::FilePath current = enumerator.Next(); !current.empty(); |
- current = enumerator.Next()) { |
- base::FilePath new_path = current.RemoveExtension(); |
- if (!new_path.Extension().empty()) { |
- // Delete files with multiple extensions. |
- if (!base::DeleteFile(current, false /* recursive */)) |
- return false; |
- continue; |
- } |
- const std::string& id = GetIdFromPath(new_path); |
- new_path = GetCacheFilePath(util::CanonicalizeResourceId(id)); |
- if (new_path != current && !base::Move(current, new_path)) |
- return false; |
- } |
- return true; |
-} |
- |
-void FileCache::CloseForWrite(const std::string& id) { |
- AssertOnSequencedWorkerPool(); |
- |
- std::map<std::string, int>::iterator it = write_opened_files_.find(id); |
- if (it == write_opened_files_.end()) |
- return; |
- |
- DCHECK_LT(0, it->second); |
- --it->second; |
- if (it->second == 0) |
- write_opened_files_.erase(it); |
- |
- // Update last modified date. |
- ResourceEntry entry; |
- FileError error = storage_->GetEntry(id, &entry); |
- if (error != FILE_ERROR_OK) { |
- LOG(ERROR) << "Failed to get entry: " << id << ", " |
- << FileErrorToString(error); |
- return; |
- } |
- entry.mutable_file_info()->set_last_modified( |
- base::Time::Now().ToInternalValue()); |
- error = storage_->PutEntry(entry); |
- if (error != FILE_ERROR_OK) { |
- LOG(ERROR) << "Failed to put entry: " << id << ", " |
- << FileErrorToString(error); |
- } |
-} |
- |
-} // namespace internal |
-} // namespace drive |