blob: 05635353b2355ded9ffe23481af850f2abc8d609 [file] [log] [blame]
// Copyright (c) 2011 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 "webkit/quota/quota_database.h"
#include <string>
#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/file_util.h"
#include "base/time.h"
#include "googleurl/src/gurl.h"
#include "sql/connection.h"
#include "sql/diagnostic_error_delegate.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "webkit/quota/special_storage_policy.h"
namespace quota {
namespace {
// Definitions for database schema.
const int kCurrentVersion = 4;
const int kCompatibleVersion = 2;
const char kHostQuotaTable[] = "HostQuotaTable";
const char kOriginInfoTable[] = "OriginInfoTable";
const char kIsOriginTableBootstrapped[] = "IsOriginTableBootstrapped";
class HistogramUniquifier {
public:
static const char* name() { return "Sqlite.Quota.Error"; }
};
bool PrepareCachedStatement(
sql::Connection* db, const sql::StatementID& id,
const char* sql, sql::Statement* statement) {
DCHECK(db && sql && statement);
statement->Assign(db->GetCachedStatement(id, sql));
if (!statement->is_valid()) {
NOTREACHED() << db->GetErrorMessage();
return false;
}
return true;
}
bool VerifyValidQuotaConfig(const char* key) {
return (key != NULL &&
(!strcmp(key, QuotaDatabase::kDesiredAvailableSpaceKey) ||
!strcmp(key, QuotaDatabase::kTemporaryQuotaOverrideKey)));
}
const int kCommitIntervalMs = 30000;
} // anonymous namespace
// static
const char QuotaDatabase::kDesiredAvailableSpaceKey[] = "DesiredAvailableSpace";
const char QuotaDatabase::kTemporaryQuotaOverrideKey[] =
"TemporaryQuotaOverride";
const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = {
{ kHostQuotaTable,
"(host TEXT NOT NULL,"
" type INTEGER NOT NULL,"
" quota INTEGER DEFAULT 0,"
" UNIQUE(host, type))" },
{ kOriginInfoTable,
"(origin TEXT NOT NULL,"
" type INTEGER NOT NULL,"
" used_count INTEGER DEFAULT 0,"
" last_access_time INTEGER DEFAULT 0,"
" last_modified_time INTEGER DEFAULT 0,"
" UNIQUE(origin, type))" },
};
// static
const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = {
{ "HostIndex",
kHostQuotaTable,
"(host)",
false },
{ "OriginInfoIndex",
kOriginInfoTable,
"(origin)",
false },
{ "OriginLastAccessTimeIndex",
kOriginInfoTable,
"(last_access_time)",
false },
{ "OriginLastModifiedTimeIndex",
kOriginInfoTable,
"(last_modified_time)",
false },
};
struct QuotaDatabase::QuotaTableImporter {
bool Append(const QuotaTableEntry& entry) {
entries.push_back(entry);
return true;
}
std::vector<QuotaTableEntry> entries;
};
// Clang requires explicit out-of-line constructors for them.
QuotaDatabase::QuotaTableEntry::QuotaTableEntry() {}
QuotaDatabase::QuotaTableEntry::QuotaTableEntry(
const std::string& host,
StorageType type,
int64 quota)
: host(host),
type(type),
quota(quota) {
}
QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry() {}
QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry(
const GURL& origin,
StorageType type,
int used_count,
const base::Time& last_access_time,
const base::Time& last_modified_time)
: origin(origin),
type(type),
used_count(used_count),
last_access_time(last_access_time),
last_modified_time(last_modified_time) {
}
// QuotaDatabase ------------------------------------------------------------
QuotaDatabase::QuotaDatabase(const FilePath& path)
: db_file_path_(path),
is_recreating_(false),
is_disabled_(false) {
}
QuotaDatabase::~QuotaDatabase() {
if (db_.get()) {
db_->CommitTransaction();
}
}
void QuotaDatabase::CloseConnection() {
meta_table_.reset();
db_.reset();
}
bool QuotaDatabase::GetHostQuota(
const std::string& host, StorageType type, int64* quota) {
DCHECK(quota);
if (!LazyOpen(false))
return false;
const char* kSql =
"SELECT quota"
" FROM HostQuotaTable"
" WHERE host = ? AND type = ?";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
statement.BindString(0, host);
statement.BindInt(1, static_cast<int>(type));
if (!statement.Step() || !statement.Succeeded())
return false;
*quota = statement.ColumnInt64(0);
return true;
}
bool QuotaDatabase::SetHostQuota(
const std::string& host, StorageType type, int64 quota) {
DCHECK_GE(quota, 0);
if (!LazyOpen(true))
return false;
sql::Statement statement;
const char* kSql =
"INSERT OR REPLACE INTO HostQuotaTable"
" (quota, host, type)"
" VALUES (?, ?, ?)";
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
statement.BindInt64(0, quota);
statement.BindString(1, host);
statement.BindInt(2, static_cast<int>(type));
if (!statement.Run())
return false;
ScheduleCommit();
return true;
}
bool QuotaDatabase::SetOriginLastAccessTime(
const GURL& origin, StorageType type, base::Time last_access_time) {
if (!LazyOpen(true))
return false;
sql::Statement statement;
int used_count = 1;
if (FindOriginUsedCount(origin, type, &used_count)) {
++used_count;
const char* kSql =
"UPDATE OriginInfoTable"
" SET used_count = ?, last_access_time = ?"
" WHERE origin = ? AND type = ?";
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
} else {
const char* kSql =
"INSERT INTO OriginInfoTable"
" (used_count, last_access_time, origin, type)"
" VALUES (?, ?, ?, ?)";
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
}
statement.BindInt(0, used_count);
statement.BindInt64(1, last_access_time.ToInternalValue());
statement.BindString(2, origin.spec());
statement.BindInt(3, static_cast<int>(type));
if (!statement.Run())
return false;
ScheduleCommit();
return true;
}
bool QuotaDatabase::SetOriginLastModifiedTime(
const GURL& origin, StorageType type, base::Time last_modified_time) {
if (!LazyOpen(true))
return false;
sql::Statement statement;
int dummy;
if (FindOriginUsedCount(origin, type, &dummy)) {
const char* kSql =
"UPDATE OriginInfoTable"
" SET last_modified_time = ?"
" WHERE origin = ? AND type = ?";
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
} else {
const char* kSql =
"INSERT INTO OriginInfoTable"
" (last_modified_time, origin, type) VALUES (?, ?, ?)";
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
}
statement.BindInt64(0, last_modified_time.ToInternalValue());
statement.BindString(1, origin.spec());
statement.BindInt(2, static_cast<int>(type));
if (!statement.Run())
return false;
ScheduleCommit();
return true;
}
bool QuotaDatabase::RegisterInitialOriginInfo(
const std::set<GURL>& origins, StorageType type) {
if (!LazyOpen(true))
return false;
typedef std::set<GURL>::const_iterator itr_type;
for (itr_type itr = origins.begin(), end = origins.end();
itr != end; ++itr) {
const char* kSql =
"INSERT OR IGNORE INTO OriginInfoTable"
" (origin, type) VALUES (?, ?)";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
statement.BindString(0, itr->spec());
statement.BindInt(1, static_cast<int>(type));
if (!statement.Run())
return false;
}
ScheduleCommit();
return true;
}
bool QuotaDatabase::DeleteHostQuota(
const std::string& host, StorageType type) {
if (!LazyOpen(false))
return false;
const char* kSql =
"DELETE FROM HostQuotaTable"
" WHERE host = ? AND type = ?";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
statement.BindString(0, host);
statement.BindInt(1, static_cast<int>(type));
if (!statement.Run())
return false;
ScheduleCommit();
return true;
}
bool QuotaDatabase::DeleteOriginInfo(
const GURL& origin, StorageType type) {
if (!LazyOpen(false))
return false;
const char* kSql =
"DELETE FROM OriginInfoTable"
" WHERE origin = ? AND type = ?";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
statement.BindString(0, origin.spec());
statement.BindInt(1, static_cast<int>(type));
if (!statement.Run())
return false;
ScheduleCommit();
return true;
}
bool QuotaDatabase::GetQuotaConfigValue(const char* key, int64* value) {
if (!LazyOpen(false))
return false;
DCHECK(VerifyValidQuotaConfig(key));
return meta_table_->GetValue(key, value);
}
bool QuotaDatabase::SetQuotaConfigValue(const char* key, int64 value) {
if (!LazyOpen(true))
return false;
DCHECK(VerifyValidQuotaConfig(key));
return meta_table_->SetValue(key, value);
}
bool QuotaDatabase::GetLRUOrigin(
StorageType type,
const std::set<GURL>& exceptions,
SpecialStoragePolicy* special_storage_policy,
GURL* origin) {
DCHECK(origin);
if (!LazyOpen(false))
return false;
const char* kSql = "SELECT origin FROM OriginInfoTable"
" WHERE type = ?"
" ORDER BY last_access_time ASC";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
statement.BindInt(0, static_cast<int>(type));
while (statement.Step()) {
GURL url(statement.ColumnString(0));
if (exceptions.find(url) != exceptions.end())
continue;
if (special_storage_policy &&
special_storage_policy->IsStorageUnlimited(url))
continue;
*origin = url;
return true;
}
*origin = GURL();
return statement.Succeeded();
}
bool QuotaDatabase::GetOriginsModifiedSince(
StorageType type, std::set<GURL>* origins, base::Time modified_since) {
DCHECK(origins);
if (!LazyOpen(false))
return false;
const char* kSql = "SELECT origin FROM OriginInfoTable"
" WHERE type = ? AND last_modified_time > ?";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
statement.BindInt(0, static_cast<int>(type));
statement.BindInt64(1, modified_since.ToInternalValue());
origins->clear();
while (statement.Step())
origins->insert(GURL(statement.ColumnString(0)));
return statement.Succeeded();
}
bool QuotaDatabase::IsOriginDatabaseBootstrapped() {
if (!LazyOpen(true))
return false;
int flag = 0;
return meta_table_->GetValue(kIsOriginTableBootstrapped, &flag) && flag;
}
bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) {
if (!LazyOpen(true))
return false;
return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag);
}
void QuotaDatabase::Commit() {
if (!db_.get())
return;
if (timer_.IsRunning())
timer_.Stop();
db_->CommitTransaction();
db_->BeginTransaction();
}
void QuotaDatabase::ScheduleCommit() {
if (timer_.IsRunning())
return;
timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCommitIntervalMs),
this, &QuotaDatabase::Commit);
}
bool QuotaDatabase::FindOriginUsedCount(
const GURL& origin, StorageType type, int* used_count) {
DCHECK(used_count);
if (!LazyOpen(false))
return false;
const char* kSql =
"SELECT used_count FROM OriginInfoTable"
" WHERE origin = ? AND type = ?";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
statement.BindString(0, origin.spec());
statement.BindInt(1, static_cast<int>(type));
if (!statement.Step() || !statement.Succeeded())
return false;
*used_count = statement.ColumnInt(0);
return true;
}
bool QuotaDatabase::LazyOpen(bool create_if_needed) {
if (db_.get())
return true;
// If we tried and failed once, don't try again in the same session
// to avoid creating an incoherent mess on disk.
if (is_disabled_)
return false;
bool in_memory_only = db_file_path_.empty();
if (!create_if_needed &&
(in_memory_only || !file_util::PathExists(db_file_path_))) {
return false;
}
db_.reset(new sql::Connection);
meta_table_.reset(new sql::MetaTable);
bool opened = false;
if (in_memory_only) {
opened = db_->OpenInMemory();
} else if (!file_util::CreateDirectory(db_file_path_.DirName())) {
LOG(ERROR) << "Failed to create quota database directory.";
} else {
opened = db_->Open(db_file_path_);
if (opened)
db_->Preload();
}
if (!opened || !EnsureDatabaseVersion()) {
LOG(ERROR) << "Failed to open the quota database.";
is_disabled_ = true;
db_.reset();
meta_table_.reset();
return false;
}
// Start a long-running transaction.
db_->BeginTransaction();
return true;
}
bool QuotaDatabase::EnsureDatabaseVersion() {
static const size_t kTableCount = ARRAYSIZE_UNSAFE(kTables);
static const size_t kIndexCount = ARRAYSIZE_UNSAFE(kIndexes);
if (!sql::MetaTable::DoesTableExist(db_.get()))
return CreateSchema(db_.get(), meta_table_.get(),
kCurrentVersion, kCompatibleVersion,
kTables, kTableCount,
kIndexes, kIndexCount);
if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
return false;
if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
LOG(WARNING) << "Quota database is too new.";
return false;
}
if (meta_table_->GetVersionNumber() < kCurrentVersion) {
if (!UpgradeSchema(meta_table_->GetVersionNumber()))
return ResetSchema();
}
#ifndef NDEBUG
DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
for (size_t i = 0; i < kTableCount; ++i) {
DCHECK(db_->DoesTableExist(kTables[i].table_name));
}
#endif
return true;
}
// static
bool QuotaDatabase::CreateSchema(
sql::Connection* database,
sql::MetaTable* meta_table,
int schema_version, int compatible_version,
const TableSchema* tables, size_t tables_size,
const IndexSchema* indexes, size_t indexes_size) {
// TODO(kinuko): Factor out the common code to create databases.
sql::Transaction transaction(database);
if (!transaction.Begin())
return false;
if (!meta_table->Init(database, schema_version, compatible_version))
return false;
for (size_t i = 0; i < tables_size; ++i) {
std::string sql("CREATE TABLE ");
sql += tables[i].table_name;
sql += tables[i].columns;
if (!database->Execute(sql.c_str())) {
VLOG(1) << "Failed to execute " << sql;
return false;
}
}
for (size_t i = 0; i < indexes_size; ++i) {
std::string sql;
if (indexes[i].unique)
sql += "CREATE UNIQUE INDEX ";
else
sql += "CREATE INDEX ";
sql += indexes[i].index_name;
sql += " ON ";
sql += indexes[i].table_name;
sql += indexes[i].columns;
if (!database->Execute(sql.c_str())) {
VLOG(1) << "Failed to execute " << sql;
return false;
}
}
return transaction.Commit();
}
bool QuotaDatabase::ResetSchema() {
DCHECK(!db_file_path_.empty());
DCHECK(file_util::PathExists(db_file_path_));
VLOG(1) << "Deleting existing quota data and starting over.";
db_.reset();
meta_table_.reset();
if (!file_util::Delete(db_file_path_, true))
return false;
// Make sure the steps above actually deleted things.
if (file_util::PathExists(db_file_path_))
return false;
// So we can't go recursive.
if (is_recreating_)
return false;
AutoReset<bool> auto_reset(&is_recreating_, true);
return LazyOpen(true);
}
bool QuotaDatabase::UpgradeSchema(int current_version) {
if (current_version == 2) {
QuotaTableImporter importer;
typedef std::vector<QuotaTableEntry> QuotaTableEntries;
if (!DumpQuotaTable(new QuotaTableCallback(base::Bind(
&QuotaTableImporter::Append, base::Unretained(&importer)))))
return false;
ResetSchema();
for (QuotaTableEntries::const_iterator iter = importer.entries.begin();
iter != importer.entries.end(); ++iter) {
if (!SetHostQuota(iter->host, iter->type, iter->quota))
return false;
}
Commit();
return true;
}
return false;
}
bool QuotaDatabase::DumpQuotaTable(QuotaTableCallback* callback) {
scoped_ptr<QuotaTableCallback> callback_deleter(callback);
if (!LazyOpen(true))
return false;
const char* kSql = "SELECT * FROM HostQuotaTable";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
while (statement.Step()) {
QuotaTableEntry entry = QuotaTableEntry(
statement.ColumnString(0),
static_cast<StorageType>(statement.ColumnInt(1)),
statement.ColumnInt64(2));
if (!callback->Run(entry))
return true;
}
return statement.Succeeded();
}
bool QuotaDatabase::DumpOriginInfoTable(
OriginInfoTableCallback* callback) {
scoped_ptr<OriginInfoTableCallback> callback_deleter(callback);
if (!LazyOpen(true))
return false;
const char* kSql = "SELECT * FROM OriginInfoTable";
sql::Statement statement;
if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
return false;
while (statement.Step()) {
OriginInfoTableEntry entry(
GURL(statement.ColumnString(0)),
static_cast<StorageType>(statement.ColumnInt(1)),
statement.ColumnInt(2),
base::Time::FromInternalValue(statement.ColumnInt64(3)),
base::Time::FromInternalValue(statement.ColumnInt64(4)));
if (!callback->Run(entry))
return true;
}
return statement.Succeeded();
}
bool operator<(const QuotaDatabase::QuotaTableEntry& lhs,
const QuotaDatabase::QuotaTableEntry& rhs) {
if (lhs.host < rhs.host) return true;
if (rhs.host < lhs.host) return false;
if (lhs.type < rhs.type) return true;
if (rhs.type < lhs.type) return false;
return lhs.quota < rhs.quota;
}
bool operator<(const QuotaDatabase::OriginInfoTableEntry& lhs,
const QuotaDatabase::OriginInfoTableEntry& rhs) {
if (lhs.origin < rhs.origin) return true;
if (rhs.origin < lhs.origin) return false;
if (lhs.type < rhs.type) return true;
if (rhs.type < lhs.type) return false;
if (lhs.used_count < rhs.used_count) return true;
if (rhs.used_count < lhs.used_count) return false;
return lhs.last_access_time < rhs.last_access_time;
}
} // quota namespace