blob: cd02deb0a0f260913dc2b1993c3006b972121f59 [file] [log] [blame]
// Copyright 2014 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 "content/browser/cache_storage/cache_storage_manager.h"
#include <stdint.h>
#include <map>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/id_map.h"
#include "base/sha1.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "content/browser/cache_storage/cache_storage.h"
#include "content/browser/cache_storage/cache_storage.pb.h"
#include "content/browser/cache_storage/cache_storage_quota_client.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/net_util.h"
#include "storage/browser/quota/quota_manager_proxy.h"
#include "storage/common/database/database_identifier.h"
#include "storage/common/quota/quota_status_code.h"
#include "url/gurl.h"
namespace content {
namespace {
bool DeleteDir(const base::FilePath& path) {
return base::DeleteFile(path, true /* recursive */);
}
void DeleteOriginDidDeleteDir(
const storage::QuotaClient::DeletionCallback& callback,
bool rv) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
callback.Run(rv ? storage::kQuotaStatusOk : storage::kQuotaErrorAbort);
}
std::set<GURL> ListOriginsOnDisk(base::FilePath root_path) {
std::set<GURL> origins;
base::FileEnumerator file_enum(root_path, false /* recursive */,
base::FileEnumerator::DIRECTORIES);
base::FilePath path;
while (!(path = file_enum.Next()).empty()) {
std::string protobuf;
base::ReadFileToString(path.AppendASCII(CacheStorage::kIndexFileName),
&protobuf);
CacheStorageIndex index;
if (index.ParseFromString(protobuf)) {
if (index.has_origin())
origins.insert(GURL(index.origin()));
}
}
return origins;
}
std::vector<CacheStorageUsageInfo> GetAllOriginsUsageOnTaskRunner(
const base::FilePath root_path) {
std::vector<CacheStorageUsageInfo> entries;
const std::set<GURL> origins = ListOriginsOnDisk(root_path);
entries.reserve(origins.size());
for (const GURL& origin : origins) {
base::FilePath path =
CacheStorageManager::ConstructOriginPath(root_path, origin);
int64_t size = base::ComputeDirectorySize(path);
base::File::Info file_info;
base::Time last_modified;
if (base::GetFileInfo(path, &file_info))
last_modified = file_info.last_modified;
entries.push_back(CacheStorageUsageInfo(origin, size, last_modified));
}
return entries;
}
void GetOriginsForHostDidListOrigins(
const std::string& host,
const storage::QuotaClient::GetOriginsCallback& callback,
const std::set<GURL>& origins) {
std::set<GURL> out_origins;
for (const GURL& origin : origins) {
if (host == net::GetHostOrSpecFromURL(origin))
out_origins.insert(origin);
}
callback.Run(out_origins);
}
void EmptyQuotaStatusCallback(storage::QuotaStatusCode code) {}
} // namespace
// static
scoped_ptr<CacheStorageManager> CacheStorageManager::Create(
const base::FilePath& path,
const scoped_refptr<base::SequencedTaskRunner>& cache_task_runner,
const scoped_refptr<storage::QuotaManagerProxy>& quota_manager_proxy) {
base::FilePath root_path = path;
if (!path.empty()) {
root_path = path.Append(ServiceWorkerContextCore::kServiceWorkerDirectory)
.AppendASCII("CacheStorage");
}
return make_scoped_ptr(new CacheStorageManager(root_path, cache_task_runner,
quota_manager_proxy));
}
// static
scoped_ptr<CacheStorageManager> CacheStorageManager::Create(
CacheStorageManager* old_manager) {
scoped_ptr<CacheStorageManager> manager(new CacheStorageManager(
old_manager->root_path(), old_manager->cache_task_runner(),
old_manager->quota_manager_proxy_.get()));
// These values may be NULL, in which case this will be called again later by
// the dispatcher host per usual.
manager->SetBlobParametersForCache(old_manager->url_request_context_getter(),
old_manager->blob_storage_context());
return manager;
}
CacheStorageManager::~CacheStorageManager() = default;
void CacheStorageManager::OpenCache(
const GURL& origin,
const std::string& cache_name,
const CacheStorage::CacheAndErrorCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CacheStorage* cache_storage = FindOrCreateCacheStorage(origin);
cache_storage->OpenCache(cache_name, callback);
}
void CacheStorageManager::HasCache(
const GURL& origin,
const std::string& cache_name,
const CacheStorage::BoolAndErrorCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CacheStorage* cache_storage = FindOrCreateCacheStorage(origin);
cache_storage->HasCache(cache_name, callback);
}
void CacheStorageManager::DeleteCache(
const GURL& origin,
const std::string& cache_name,
const CacheStorage::BoolAndErrorCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CacheStorage* cache_storage = FindOrCreateCacheStorage(origin);
cache_storage->DeleteCache(cache_name, callback);
}
void CacheStorageManager::EnumerateCaches(
const GURL& origin,
const CacheStorage::StringsAndErrorCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CacheStorage* cache_storage = FindOrCreateCacheStorage(origin);
cache_storage->EnumerateCaches(callback);
}
void CacheStorageManager::MatchCache(
const GURL& origin,
const std::string& cache_name,
scoped_ptr<ServiceWorkerFetchRequest> request,
const CacheStorageCache::ResponseCallback& callback) {
CacheStorage* cache_storage = FindOrCreateCacheStorage(origin);
cache_storage->MatchCache(cache_name, std::move(request), callback);
}
void CacheStorageManager::MatchAllCaches(
const GURL& origin,
scoped_ptr<ServiceWorkerFetchRequest> request,
const CacheStorageCache::ResponseCallback& callback) {
CacheStorage* cache_storage = FindOrCreateCacheStorage(origin);
cache_storage->MatchAllCaches(std::move(request), callback);
}
void CacheStorageManager::SetBlobParametersForCache(
const scoped_refptr<net::URLRequestContextGetter>& request_context_getter,
base::WeakPtr<storage::BlobStorageContext> blob_storage_context) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(cache_storage_map_.empty());
DCHECK(!request_context_getter_ ||
request_context_getter_.get() == request_context_getter.get());
DCHECK(!blob_context_ || blob_context_.get() == blob_storage_context.get());
request_context_getter_ = request_context_getter;
blob_context_ = blob_storage_context;
}
void CacheStorageManager::GetAllOriginsUsage(
const CacheStorageContext::GetUsageInfoCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (IsMemoryBacked()) {
std::vector<CacheStorageUsageInfo> entries;
entries.reserve(cache_storage_map_.size());
for (const auto& origin_details : cache_storage_map_) {
entries.push_back(CacheStorageUsageInfo(
origin_details.first, origin_details.second->MemoryBackedSize(),
base::Time()));
}
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(callback, entries));
return;
}
PostTaskAndReplyWithResult(
cache_task_runner_.get(), FROM_HERE,
base::Bind(&GetAllOriginsUsageOnTaskRunner, root_path_),
base::Bind(callback));
}
void CacheStorageManager::GetOriginUsage(
const GURL& origin_url,
const storage::QuotaClient::GetUsageCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (IsMemoryBacked()) {
int64_t usage = 0;
if (ContainsKey(cache_storage_map_, origin_url))
usage = cache_storage_map_[origin_url]->MemoryBackedSize();
callback.Run(usage);
return;
}
MigrateOrigin(origin_url);
PostTaskAndReplyWithResult(
cache_task_runner_.get(), FROM_HERE,
base::Bind(base::ComputeDirectorySize,
ConstructOriginPath(root_path_, origin_url)),
base::Bind(callback));
}
void CacheStorageManager::GetOrigins(
const storage::QuotaClient::GetOriginsCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (IsMemoryBacked()) {
std::set<GURL> origins;
for (const auto& key_value : cache_storage_map_)
origins.insert(key_value.first);
callback.Run(origins);
return;
}
PostTaskAndReplyWithResult(cache_task_runner_.get(), FROM_HERE,
base::Bind(&ListOriginsOnDisk, root_path_),
base::Bind(callback));
}
void CacheStorageManager::GetOriginsForHost(
const std::string& host,
const storage::QuotaClient::GetOriginsCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (IsMemoryBacked()) {
std::set<GURL> origins;
for (const auto& key_value : cache_storage_map_) {
if (host == net::GetHostOrSpecFromURL(key_value.first))
origins.insert(key_value.first);
}
callback.Run(origins);
return;
}
PostTaskAndReplyWithResult(
cache_task_runner_.get(), FROM_HERE,
base::Bind(&ListOriginsOnDisk, root_path_),
base::Bind(&GetOriginsForHostDidListOrigins, host, callback));
}
void CacheStorageManager::DeleteOriginData(
const GURL& origin,
const storage::QuotaClient::DeletionCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CacheStorageMap::iterator it = cache_storage_map_.find(origin);
if (it == cache_storage_map_.end()) {
callback.Run(storage::kQuotaStatusOk);
return;
}
CacheStorage* cache_storage = it->second.release();
cache_storage_map_.erase(origin);
cache_storage->CloseAllCaches(
base::Bind(&CacheStorageManager::DeleteOriginDidClose, origin, callback,
base::Passed(make_scoped_ptr(cache_storage)),
weak_ptr_factory_.GetWeakPtr()));
}
void CacheStorageManager::DeleteOriginData(const GURL& origin) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DeleteOriginData(origin, base::Bind(&EmptyQuotaStatusCallback));
}
// static
void CacheStorageManager::DeleteOriginDidClose(
const GURL& origin,
const storage::QuotaClient::DeletionCallback& callback,
scoped_ptr<CacheStorage> cache_storage,
base::WeakPtr<CacheStorageManager> cache_manager) {
// TODO(jkarlin): Deleting the storage leaves any unfinished operations
// hanging, resulting in unresolved promises. Fix this by guaranteeing that
// callbacks are called in ServiceWorkerStorage.
cache_storage.reset();
if (!cache_manager) {
callback.Run(storage::kQuotaErrorAbort);
return;
}
if (cache_manager->IsMemoryBacked()) {
callback.Run(storage::kQuotaStatusOk);
return;
}
cache_manager->MigrateOrigin(origin);
PostTaskAndReplyWithResult(
cache_manager->cache_task_runner_.get(), FROM_HERE,
base::Bind(&DeleteDir,
ConstructOriginPath(cache_manager->root_path_, origin)),
base::Bind(&DeleteOriginDidDeleteDir, callback));
}
CacheStorageManager::CacheStorageManager(
const base::FilePath& path,
const scoped_refptr<base::SequencedTaskRunner>& cache_task_runner,
const scoped_refptr<storage::QuotaManagerProxy>& quota_manager_proxy)
: root_path_(path),
cache_task_runner_(cache_task_runner),
quota_manager_proxy_(quota_manager_proxy),
weak_ptr_factory_(this) {
if (quota_manager_proxy_.get()) {
quota_manager_proxy_->RegisterClient(
new CacheStorageQuotaClient(weak_ptr_factory_.GetWeakPtr()));
}
}
CacheStorage* CacheStorageManager::FindOrCreateCacheStorage(
const GURL& origin) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(request_context_getter_);
CacheStorageMap::const_iterator it = cache_storage_map_.find(origin);
if (it == cache_storage_map_.end()) {
MigrateOrigin(origin);
CacheStorage* cache_storage = new CacheStorage(
ConstructOriginPath(root_path_, origin), IsMemoryBacked(),
cache_task_runner_.get(), request_context_getter_, quota_manager_proxy_,
blob_context_, origin);
cache_storage_map_.insert(
std::make_pair(origin, make_scoped_ptr(cache_storage)));
return cache_storage;
}
return it->second.get();
}
// static
base::FilePath CacheStorageManager::ConstructLegacyOriginPath(
const base::FilePath& root_path,
const GURL& origin) {
const std::string origin_hash = base::SHA1HashString(origin.spec());
const std::string origin_hash_hex = base::ToLowerASCII(
base::HexEncode(origin_hash.c_str(), origin_hash.length()));
return root_path.AppendASCII(origin_hash_hex);
}
// static
base::FilePath CacheStorageManager::ConstructOriginPath(
const base::FilePath& root_path,
const GURL& origin) {
const std::string identifier = storage::GetIdentifierFromOrigin(origin);
const std::string origin_hash = base::SHA1HashString(identifier);
const std::string origin_hash_hex = base::ToLowerASCII(
base::HexEncode(origin_hash.c_str(), origin_hash.length()));
return root_path.AppendASCII(origin_hash_hex);
}
// Migrate from old origin-based path to storage identifier-based path.
// TODO(jsbell); Remove after a few releases.
void CacheStorageManager::MigrateOrigin(const GURL& origin) {
if (IsMemoryBacked())
return;
base::FilePath old_path = ConstructLegacyOriginPath(root_path_, origin);
base::FilePath new_path = ConstructOriginPath(root_path_, origin);
cache_task_runner_->PostTask(
FROM_HERE, base::Bind(&MigrateOriginOnTaskRunner, old_path, new_path));
}
// static
void CacheStorageManager::MigrateOriginOnTaskRunner(
const base::FilePath& old_path,
const base::FilePath& new_path) {
if (base::PathExists(old_path)) {
if (!base::PathExists(new_path))
base::Move(old_path, new_path);
base::DeleteFile(old_path, /*recursive*/ true);
}
}
} // namespace content