blob: 6d8386e54b97eabcc29c46b72357b00fff75753b [file] [log] [blame]
// 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 "content/browser/in_process_webkit/indexed_db_context_impl.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "content/browser/in_process_webkit/indexed_db_quota_client.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebCString.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBDatabase.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebIDBFactory.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebString.h"
#include "webkit/database/database_util.h"
#include "webkit/glue/webkit_glue.h"
#include "webkit/quota/quota_manager.h"
#include "webkit/quota/special_storage_policy.h"
using content::BrowserThread;
using content::IndexedDBContext;
using webkit_database::DatabaseUtil;
using WebKit::WebIDBDatabase;
using WebKit::WebIDBFactory;
using WebKit::WebSecurityOrigin;
const FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] =
FILE_PATH_LITERAL("IndexedDB");
const FilePath::CharType IndexedDBContextImpl::kIndexedDBExtension[] =
FILE_PATH_LITERAL(".leveldb");
namespace {
void GetAllOriginsAndPaths(
const FilePath& indexeddb_path,
std::vector<GURL>* origins,
std::vector<FilePath>* file_paths) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED));
if (indexeddb_path.empty())
return;
file_util::FileEnumerator file_enumerator(indexeddb_path,
false, file_util::FileEnumerator::DIRECTORIES);
for (FilePath file_path = file_enumerator.Next(); !file_path.empty();
file_path = file_enumerator.Next()) {
if (file_path.Extension() == IndexedDBContextImpl::kIndexedDBExtension) {
WebKit::WebString origin_id_webstring =
webkit_glue::FilePathToWebString(file_path.BaseName());
origins->push_back(
DatabaseUtil::GetOriginFromIdentifier(origin_id_webstring));
if (file_paths)
file_paths->push_back(file_path);
}
}
}
// Deletes session-only databases.
void ClearSessionOnlyOrigins(
const FilePath& indexeddb_path,
scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED));
std::vector<GURL> origins;
std::vector<FilePath> file_paths;
GetAllOriginsAndPaths(indexeddb_path, &origins, &file_paths);
DCHECK_EQ(origins.size(), file_paths.size());
std::vector<FilePath>::const_iterator file_path_iter = file_paths.begin();
for (std::vector<GURL>::const_iterator iter = origins.begin();
iter != origins.end(); ++iter, ++file_path_iter) {
if (!special_storage_policy->IsStorageSessionOnly(*iter))
continue;
if (special_storage_policy->IsStorageProtected(*iter))
continue;
file_util::Delete(*file_path_iter, true);
}
}
} // namespace
IndexedDBContextImpl::IndexedDBContextImpl(
const FilePath& data_path,
quota::SpecialStoragePolicy* special_storage_policy,
quota::QuotaManagerProxy* quota_manager_proxy,
base::MessageLoopProxy* webkit_thread_loop)
: force_keep_session_state_(false),
special_storage_policy_(special_storage_policy),
quota_manager_proxy_(quota_manager_proxy) {
if (!data_path.empty())
data_path_ = data_path.Append(kIndexedDBDirectory);
if (quota_manager_proxy &&
!CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) {
quota_manager_proxy->RegisterClient(
new IndexedDBQuotaClient(webkit_thread_loop, this));
}
}
WebIDBFactory* IndexedDBContextImpl::GetIDBFactory() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED));
if (!idb_factory_.get()) {
// Prime our cache of origins with existing databases so we can
// detect when dbs are newly created.
GetOriginSet();
idb_factory_.reset(WebIDBFactory::create());
}
return idb_factory_.get();
}
std::vector<GURL> IndexedDBContextImpl::GetAllOrigins() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED));
std::vector<GURL> origins;
std::set<GURL>* origins_set = GetOriginSet();
for (std::set<GURL>::const_iterator iter = origins_set->begin();
iter != origins_set->end(); ++iter) {
origins.push_back(*iter);
}
return origins;
}
int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) {
if (data_path_.empty() || !IsInOriginSet(origin_url))
return 0;
EnsureDiskUsageCacheInitialized(origin_url);
return origin_size_map_[origin_url];
}
base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) {
if (data_path_.empty() || !IsInOriginSet(origin_url))
return base::Time();
string16 origin_id = DatabaseUtil::GetOriginIdentifier(origin_url);
FilePath idb_directory = GetIndexedDBFilePath(origin_id);
base::PlatformFileInfo file_info;
if (!file_util::GetFileInfo(idb_directory, &file_info))
return base::Time();
return file_info.last_modified;
}
void IndexedDBContextImpl::DeleteForOrigin(const GURL& origin_url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED));
if (data_path_.empty() || !IsInOriginSet(origin_url))
return;
if (connections_.find(origin_url) != connections_.end()) {
ConnectionSet& connections = connections_[origin_url];
ConnectionSet::iterator it = connections.begin();
while (it != connections.end()) {
// Remove before closing so callbacks don't double-erase
WebKit::WebIDBDatabase* db = *it;
connections.erase(it++);
db->forceClose();
}
DCHECK(connections_[origin_url].size() == 0);
connections_.erase(origin_url);
}
string16 origin_id = DatabaseUtil::GetOriginIdentifier(origin_url);
FilePath idb_directory = GetIndexedDBFilePath(origin_id);
EnsureDiskUsageCacheInitialized(origin_url);
const bool recursive = true;
bool deleted = file_util::Delete(idb_directory, recursive);
QueryDiskAndUpdateQuotaUsage(origin_url);
if (deleted) {
RemoveFromOriginSet(origin_url);
origin_size_map_.erase(origin_url);
space_available_map_.erase(origin_url);
}
}
FilePath IndexedDBContextImpl::GetFilePathForTesting(
const string16& origin_id) const {
return GetIndexedDBFilePath(origin_id);
}
void IndexedDBContextImpl::ConnectionOpened(const GURL& origin_url,
WebIDBDatabase* connection) {
DCHECK(connections_[origin_url].count(connection) == 0);
if (quota_manager_proxy()) {
quota_manager_proxy()->NotifyStorageAccessed(
quota::QuotaClient::kIndexedDatabase, origin_url,
quota::kStorageTypeTemporary);
}
connections_[origin_url].insert(connection);
if (AddToOriginSet(origin_url)) {
// A newly created db, notify the quota system.
QueryDiskAndUpdateQuotaUsage(origin_url);
} else {
EnsureDiskUsageCacheInitialized(origin_url);
}
QueryAvailableQuota(origin_url);
}
void IndexedDBContextImpl::ConnectionClosed(const GURL& origin_url,
WebIDBDatabase* connection) {
// May not be in the map if connection was forced to close
if (connections_.find(origin_url) == connections_.end() ||
connections_[origin_url].count(connection) != 1)
return;
if (quota_manager_proxy()) {
quota_manager_proxy()->NotifyStorageAccessed(
quota::QuotaClient::kIndexedDatabase, origin_url,
quota::kStorageTypeTemporary);
}
connections_[origin_url].erase(connection);
if (connections_[origin_url].size() == 0) {
QueryDiskAndUpdateQuotaUsage(origin_url);
connections_.erase(origin_url);
}
}
void IndexedDBContextImpl::TransactionComplete(const GURL& origin_url) {
DCHECK(connections_.find(origin_url) != connections_.end() &&
connections_[origin_url].size() > 0);
QueryDiskAndUpdateQuotaUsage(origin_url);
QueryAvailableQuota(origin_url);
}
bool IndexedDBContextImpl::WouldBeOverQuota(const GURL& origin_url,
int64 additional_bytes) {
if (space_available_map_.find(origin_url) == space_available_map_.end()) {
// We haven't heard back from the QuotaManager yet, just let it through.
return false;
}
bool over_quota = additional_bytes > space_available_map_[origin_url];
return over_quota;
}
bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) {
const int kOneAdditionalByte = 1;
return WouldBeOverQuota(origin_url, kOneAdditionalByte);
}
quota::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() {
return quota_manager_proxy_;
}
IndexedDBContextImpl::~IndexedDBContextImpl() {
WebKit::WebIDBFactory* factory = idb_factory_.release();
if (factory) {
if (!BrowserThread::DeleteSoon(BrowserThread::WEBKIT_DEPRECATED,
FROM_HERE, factory))
delete factory;
}
if (data_path_.empty())
return;
if (force_keep_session_state_)
return;
bool has_session_only_databases =
special_storage_policy_.get() &&
special_storage_policy_->HasSessionOnlyOrigins();
// Clearning only session-only databases, and there are none.
if (!has_session_only_databases)
return;
// No WEBKIT thread here means we are running in a unit test where no clean
// up is needed.
BrowserThread::PostTask(
BrowserThread::WEBKIT_DEPRECATED, FROM_HERE,
base::Bind(&ClearSessionOnlyOrigins,
data_path_,
special_storage_policy_));
}
FilePath IndexedDBContextImpl::GetIndexedDBFilePath(
const string16& origin_id) const {
DCHECK(!data_path_.empty());
FilePath::StringType id =
webkit_glue::WebStringToFilePathString(origin_id).append(
FILE_PATH_LITERAL(".indexeddb"));
return data_path_.Append(id.append(kIndexedDBExtension));
}
int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const {
if (data_path_.empty())
return 0;
string16 origin_id = DatabaseUtil::GetOriginIdentifier(origin_url);
FilePath file_path = GetIndexedDBFilePath(origin_id);
return file_util::ComputeDirectorySize(file_path);
}
void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized(
const GURL& origin_url) {
if (origin_size_map_.find(origin_url) == origin_size_map_.end())
origin_size_map_[origin_url] = ReadUsageFromDisk(origin_url);
}
void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage(
const GURL& origin_url) {
int64 former_disk_usage = origin_size_map_[origin_url];
int64 current_disk_usage = ReadUsageFromDisk(origin_url);
int64 difference = current_disk_usage - former_disk_usage;
if (difference) {
origin_size_map_[origin_url] = current_disk_usage;
// quota_manager_proxy() is NULL in unit tests.
if (quota_manager_proxy())
quota_manager_proxy()->NotifyStorageModified(
quota::QuotaClient::kIndexedDatabase,
origin_url,
quota::kStorageTypeTemporary,
difference);
}
}
void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url,
quota::QuotaStatusCode status,
int64 usage, int64 quota) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(status == quota::kQuotaStatusOk || status == quota::kQuotaErrorAbort)
<< "status was " << status;
if (status == quota::kQuotaErrorAbort) {
// We seem to no longer care to wait around for the answer.
return;
}
BrowserThread::PostTask(
BrowserThread::WEBKIT_DEPRECATED, FROM_HERE,
base::Bind(&IndexedDBContextImpl::GotUpdatedQuota, this, origin_url,
usage, quota));
}
void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url, int64 usage,
int64 quota) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED));
space_available_map_[origin_url] = quota - usage;
}
void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::WEBKIT_DEPRECATED));
if (quota_manager_proxy())
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&IndexedDBContextImpl::QueryAvailableQuota, this,
origin_url));
return;
}
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager())
return;
quota_manager_proxy()->quota_manager()->GetUsageAndQuota(
origin_url,
quota::kStorageTypeTemporary,
base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url));
}
std::set<GURL>* IndexedDBContextImpl::GetOriginSet() {
if (!origin_set_.get()) {
origin_set_.reset(new std::set<GURL>);
std::vector<GURL> origins;
GetAllOriginsAndPaths(data_path_, &origins, NULL);
for (std::vector<GURL>::const_iterator iter = origins.begin();
iter != origins.end(); ++iter) {
origin_set_->insert(*iter);
}
}
return origin_set_.get();
}
void IndexedDBContextImpl::ResetCaches() {
origin_set_.reset();
origin_size_map_.clear();
space_available_map_.clear();
}