This source file includes following definitions.
- GetIdFromPath
- weak_ptr_factory_
- GetCacheFilePath
- AssertOnSequencedWorkerPool
- IsUnderFileCacheDirectory
- GetCacheEntry
- GetIterator
- FreeDiskSpaceIfNeededFor
- GetFile
- Store
- Pin
- Unpin
- MarkAsMounted
- OpenForWrite
- IsOpenedForWrite
- UpdateMd5
- ClearDirty
- Remove
- ClearAll
- Initialize
- Destroy
- DestroyOnBlockingPool
- RecoverFilesFromCacheDirectory
- MarkAsUnmounted
- HasEnoughSpaceFor
- RenameCacheFilesToNewFormat
- CloseForWrite
#include "chrome/browser/chromeos/drive/file_cache.h"
#include <vector>
#include "base/callback_helpers.h"
#include "base/file_util.h"
#include "base/files/file_enumerator.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/drive.pb.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
#include "chrome/browser/drive/drive_api_util.h"
#include "chromeos/chromeos_constants.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/drive/task_util.h"
#include "net/base/mime_sniffer.h"
#include "net/base/mime_util.h"
#include "net/base/net_util.h"
#include "third_party/cros_system_api/constants/cryptohome.h"
using content::BrowserThread;
namespace drive {
namespace internal {
namespace {
std::string GetIdFromPath(const base::FilePath& path) {
return util::UnescapeCacheFileName(path.BaseName().AsUTF8Unsafe());
}
}
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());
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
FileCache::~FileCache() {
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::GetCacheEntry(const std::string& id, FileCacheEntry* entry) {
DCHECK(entry);
AssertOnSequencedWorkerPool();
return storage_->GetCacheEntry(id, entry);
}
scoped_ptr<FileCache::Iterator> FileCache::GetIterator() {
AssertOnSequencedWorkerPool();
return storage_->GetCacheEntryIterator();
}
bool FileCache::FreeDiskSpaceIfNeededFor(int64 num_bytes) {
AssertOnSequencedWorkerPool();
if (HasEnoughSpaceFor(num_bytes, cache_file_directory_))
return true;
DVLOG(1) << "Freeing up disk space for " << num_bytes;
scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
storage_->GetCacheEntryIterator();
for (; !it->IsAtEnd(); it->Advance()) {
const FileCacheEntry& entry = it->GetValue();
if (!entry.is_pinned() &&
!entry.is_dirty() &&
!mounted_files_.count(it->GetID()))
storage_->RemoveCacheEntry(it->GetID());
}
DCHECK(!it->HasError());
base::FileEnumerator enumerator(cache_file_directory_,
false,
base::FileEnumerator::FILES);
FileCacheEntry entry;
for (base::FilePath current = enumerator.Next(); !current.empty();
current = enumerator.Next()) {
std::string id = GetIdFromPath(current);
if (!storage_->GetCacheEntry(id, &entry))
base::DeleteFile(current, false );
}
return HasEnoughSpaceFor(num_bytes, cache_file_directory_);
}
FileError FileCache::GetFile(const std::string& id,
base::FilePath* cache_file_path) {
AssertOnSequencedWorkerPool();
DCHECK(cache_file_path);
FileCacheEntry cache_entry;
if (!storage_->GetCacheEntry(id, &cache_entry) ||
!cache_entry.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();
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 (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;
}
FileCacheEntry cache_entry;
storage_->GetCacheEntry(id, &cache_entry);
cache_entry.set_md5(md5);
cache_entry.set_is_present(true);
if (md5.empty())
cache_entry.set_is_dirty(true);
return storage_->PutCacheEntry(id, cache_entry) ?
FILE_ERROR_OK : FILE_ERROR_FAILED;
}
FileError FileCache::Pin(const std::string& id) {
AssertOnSequencedWorkerPool();
FileCacheEntry cache_entry;
storage_->GetCacheEntry(id, &cache_entry);
cache_entry.set_is_pinned(true);
return storage_->PutCacheEntry(id, cache_entry) ?
FILE_ERROR_OK : FILE_ERROR_FAILED;
}
FileError FileCache::Unpin(const std::string& id) {
AssertOnSequencedWorkerPool();
FileCacheEntry cache_entry;
if (!storage_->GetCacheEntry(id, &cache_entry))
return FILE_ERROR_NOT_FOUND;
if (cache_entry.is_present()) {
cache_entry.set_is_pinned(false);
if (!storage_->PutCacheEntry(id, cache_entry))
return FILE_ERROR_FAILED;
} else {
if (!storage_->RemoveCacheEntry(id))
return FILE_ERROR_FAILED;
}
FreeDiskSpaceIfNeededFor(0);
return FILE_ERROR_OK;
}
FileError FileCache::MarkAsMounted(const std::string& id,
base::FilePath* cache_file_path) {
AssertOnSequencedWorkerPool();
DCHECK(cache_file_path);
FileCacheEntry cache_entry;
if (!storage_->GetCacheEntry(id, &cache_entry))
return FILE_ERROR_NOT_FOUND;
if (mounted_files_.count(id))
return FILE_ERROR_INVALID_OPERATION;
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();
FileCacheEntry cache_entry;
if (!storage_->GetCacheEntry(id, &cache_entry) ||
!cache_entry.is_present()) {
LOG(WARNING) << "Can't mark dirty a file that wasn't cached: " << id;
return FILE_ERROR_NOT_FOUND;
}
cache_entry.set_is_dirty(true);
cache_entry.clear_md5();
if (!storage_->PutCacheEntry(id, cache_entry))
return FILE_ERROR_FAILED;
write_opened_files_[id]++;
file_closer->reset(new base::ScopedClosureRunner(
base::Bind(&google_apis::RunTaskOnThread,
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);
}
FileError FileCache::UpdateMd5(const std::string& id) {
AssertOnSequencedWorkerPool();
if (IsOpenedForWrite(id))
return FILE_ERROR_IN_USE;
FileCacheEntry cache_entry;
if (!storage_->GetCacheEntry(id, &cache_entry) ||
!cache_entry.is_present())
return FILE_ERROR_NOT_FOUND;
const std::string& md5 = util::GetMd5Digest(GetCacheFilePath(id));
if (md5.empty())
return FILE_ERROR_NOT_FOUND;
cache_entry.set_md5(md5);
return storage_->PutCacheEntry(id, cache_entry) ?
FILE_ERROR_OK : FILE_ERROR_FAILED;
}
FileError FileCache::ClearDirty(const std::string& id) {
AssertOnSequencedWorkerPool();
if (IsOpenedForWrite(id))
return FILE_ERROR_IN_USE;
FileCacheEntry cache_entry;
if (!storage_->GetCacheEntry(id, &cache_entry) ||
!cache_entry.is_present()) {
LOG(WARNING) << "Can't clear dirty state of a file that wasn't cached: "
<< id;
return FILE_ERROR_NOT_FOUND;
}
if (!cache_entry.is_dirty()) {
LOG(WARNING) << "Can't clear dirty state of a non-dirty file: " << id;
return FILE_ERROR_INVALID_OPERATION;
}
cache_entry.set_is_dirty(false);
return storage_->PutCacheEntry(id, cache_entry) ?
FILE_ERROR_OK : FILE_ERROR_FAILED;
}
FileError FileCache::Remove(const std::string& id) {
AssertOnSequencedWorkerPool();
FileCacheEntry cache_entry;
if (!storage_->GetCacheEntry(id, &cache_entry))
return FILE_ERROR_OK;
if (mounted_files_.count(id))
return FILE_ERROR_IN_USE;
base::FilePath path = GetCacheFilePath(id);
if (!base::DeleteFile(path, false ))
return FILE_ERROR_FAILED;
return storage_->RemoveCacheEntry(id) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
}
bool FileCache::ClearAll() {
AssertOnSequencedWorkerPool();
scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
storage_->GetCacheEntryIterator();
for (; !it->IsAtEnd(); it->Advance()) {
if (!storage_->RemoveCacheEntry(it->GetID()))
return false;
}
if (it->HasError())
return false;
base::FileEnumerator enumerator(cache_file_directory_,
false,
base::FileEnumerator::FILES);
for (base::FilePath file = enumerator.Next(); !file.empty();
file = enumerator.Next())
base::DeleteFile(file, false );
return true;
}
bool FileCache::Initialize() {
AssertOnSequencedWorkerPool();
scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
storage_->GetCacheEntryIterator();
for (; !it->IsAtEnd(); it->Advance()) {
if (it->GetValue().is_dirty()) {
FileCacheEntry new_entry(it->GetValue());
new_entry.clear_md5();
if (!storage_->PutCacheEntry(it->GetID(), new_entry))
return false;
}
}
if (!RenameCacheFilesToNewFormat())
return false;
return true;
}
void FileCache::Destroy() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
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,
base::FileEnumerator::FILES);
for (base::FilePath current = enumerator.Next(); !current.empty();
current = enumerator.Next()) {
const std::string& id = GetIdFromPath(current);
FileCacheEntry entry;
if (storage_->GetCacheEntry(id, &entry)) {
continue;
}
ResourceMetadataStorage::RecoveredCacheInfoMap::const_iterator it =
recovered_cache_info.find(id);
if (it != recovered_cache_info.end()) {
if (!it->second.is_dirty &&
it->second.md5 == util::GetMd5Digest(current)) {
base::DeleteFile(current, false );
continue;
}
}
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)
continue;
base::FilePath dest_base_name(FILE_PATH_LITERAL("file"));
std::string mime_type;
if (it != recovered_cache_info.end() && !it->second.title.empty()) {
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)) {
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"));
}
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);
if (!extensions.empty())
dest_base_name = dest_base_name.AddExtension(extensions[0]);
}
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);
FileCacheEntry cache_entry;
if (!storage_->GetCacheEntry(id, &cache_entry))
return FILE_ERROR_NOT_FOUND;
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);
free_space -= cryptohome::kMinFreeSpaceInBytes;
return (free_space >= num_bytes);
}
bool FileCache::RenameCacheFilesToNewFormat() {
base::FileEnumerator enumerator(cache_file_directory_,
false,
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()) {
if (!base::DeleteFile(current, false ))
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);
}
}
}