summaryrefslogtreecommitdiffstats
path: root/webkit/quota/quota_database.cc
diff options
context:
space:
mode:
Diffstat (limited to 'webkit/quota/quota_database.cc')
-rw-r--r--webkit/quota/quota_database.cc540
1 files changed, 540 insertions, 0 deletions
diff --git a/webkit/quota/quota_database.cc b/webkit/quota/quota_database.cc
new file mode 100644
index 0000000..a6dd0e6
--- /dev/null
+++ b/webkit/quota/quota_database.cc
@@ -0,0 +1,540 @@
+// 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 "app/sql/connection.h"
+#include "app/sql/diagnostic_error_delegate.h"
+#include "app/sql/meta_table.h"
+#include "app/sql/statement.h"
+#include "app/sql/transaction.h"
+#include "base/auto_reset.h"
+#include "base/file_util.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+
+const int64 kUninitializedId = -1;
+const int64 kUninitializedQuota = -1;
+
+// Definitions for database schema.
+
+const int kCurrentVersion = 1;
+const int kCompatibleVersion = 1;
+
+const char kOriginsTable[] = "Origins";
+const char kStorageInfoTable[] = "StorageInfo";
+const char kGlobalQuotaKeyPrefix[] = "GlobalQuota-";
+
+const struct {
+ const char* table_name;
+ const char* columns;
+} kTables[] = {
+ { kOriginsTable,
+ "(origin_url TEXT NOT NULL)" },
+ { kStorageInfoTable,
+ "(origin_rowid INTEGER,"
+ " type INTEGER NOT NULL,"
+ " quota INTEGER,"
+ " used_count INTEGER,"
+ " last_access_time INTEGER)" },
+};
+
+const struct {
+ const char* index_name;
+ const char* table_name;
+ const char* columns;
+ bool unique;
+} kIndexes[] = {
+ { "OriginsIndex",
+ kOriginsTable,
+ "(origin_url)",
+ true },
+ { "StorageInfoIndex",
+ kStorageInfoTable,
+ "(origin_rowid)",
+ false },
+};
+
+const int kTableCount = ARRAYSIZE_UNSAFE(kTables);
+const int kIndexCount = ARRAYSIZE_UNSAFE(kIndexes);
+
+class HistogramUniquifier {
+ public:
+ static const char* name() { return "Sqlite.Quota.Error"; }
+};
+
+sql::ErrorDelegate* GetErrorHandlerForQuotaManagerDb() {
+ return new sql::DiagnosticErrorDelegate<HistogramUniquifier>();
+}
+
+bool PrepareUniqueStatement(
+ sql::Connection* db, const char* sql, sql::Statement* statement) {
+ DCHECK(db && sql && statement);
+ statement->Assign(db->GetUniqueStatement(sql));
+ if (!statement->is_valid()) {
+ NOTREACHED() << db->GetErrorMessage();
+ return false;
+ }
+ return true;
+}
+
+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;
+}
+
+std::string GetGlobalQuotaKey(quota::StorageType type) {
+ if (type == quota::kStorageTypeTemporary)
+ return std::string(kGlobalQuotaKeyPrefix) + "temporary";
+ else if (type == quota::kStorageTypePersistent)
+ return std::string(kGlobalQuotaKeyPrefix) + "persistent";
+ NOTREACHED() << "Unknown storage type " << type;
+ return std::string();
+}
+
+} // anonymous namespace
+
+namespace quota {
+
+struct QuotaDatabase::StorageInfoRecord {
+ StorageInfoRecord()
+ : rowid(kUninitializedId),
+ origin_rowid(kUninitializedId),
+ type(kStorageTypeUnknown),
+ quota(kUninitializedQuota),
+ used_count(0) {}
+ int64 rowid;
+ int64 origin_rowid;
+ StorageType type;
+ int64 quota;
+ int used_count;
+ base::Time last_access_time;
+};
+
+QuotaDatabase::QuotaDatabase(const FilePath& path)
+ : db_file_path_(path),
+ is_recreating_(false),
+ is_disabled_(false) {
+}
+
+QuotaDatabase::~QuotaDatabase() {
+}
+
+void QuotaDatabase::CloseConnection() {
+ meta_table_.reset();
+ db_.reset();
+}
+
+bool QuotaDatabase::GetOriginQuota(
+ const GURL& origin, StorageType type, int64* quota) {
+ DCHECK(quota);
+ StorageInfoRecord record;
+ if (!FindStorageInfo(origin, type, &record))
+ return false;
+ if (record.quota == kUninitializedQuota)
+ return false;
+ *quota = record.quota;
+ return true;
+}
+
+bool QuotaDatabase::SetOriginQuota(
+ const GURL& origin, StorageType type, int64 quota) {
+ DCHECK(quota >= 0);
+ if (!LazyOpen(true))
+ return false;
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+
+ int64 origin_rowid;
+ if (!FindOrigin(origin, &origin_rowid)) {
+ if (!InsertOrigin(origin, &origin_rowid))
+ return false;
+ }
+
+ StorageInfoRecord record;
+ if (!FindStorageInfo(origin_rowid, type, &record)) {
+ record.origin_rowid = origin_rowid;
+ record.type = type;
+ record.quota = quota;
+ if (!InsertStorageInfo(record))
+ return false;
+ return transaction.Commit();
+ }
+
+ const char* kSql =
+ "UPDATE StorageInfo"
+ " SET quota = ?"
+ " WHERE rowid = ?";
+ sql::Statement statement;
+ if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, quota);
+ statement.BindInt64(1, record.rowid);
+ if (!statement.Run())
+ return false;
+ return transaction.Commit();
+}
+
+bool QuotaDatabase::SetOriginLastAccessTime(
+ const GURL& origin, StorageType type, base::Time last_access_time) {
+ if (!LazyOpen(true))
+ return false;
+
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+
+ int64 origin_rowid;
+ if (!FindOrigin(origin, &origin_rowid)) {
+ if (!InsertOrigin(origin, &origin_rowid))
+ return false;
+ }
+ StorageInfoRecord record;
+ if (!FindStorageInfo(origin_rowid, type, &record)) {
+ record.origin_rowid = origin_rowid;
+ record.type = type;
+ record.used_count = 0;
+ record.last_access_time = last_access_time;
+ if (!InsertStorageInfo(record))
+ return false;
+ return transaction.Commit();
+ }
+
+ const char* kSql =
+ "UPDATE StorageInfo"
+ " SET used_count = ?, last_access_time = ?"
+ " WHERE rowid = ?";
+ sql::Statement statement;
+ if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt(0, record.used_count + 1);
+ statement.BindInt64(1, last_access_time.ToInternalValue());
+ statement.BindInt64(2, record.rowid);
+ if (!statement.Run())
+ return false;
+
+ return transaction.Commit();
+}
+
+bool QuotaDatabase::DeleteStorageInfo(const GURL& origin, StorageType type) {
+ if (!LazyOpen(false))
+ return false;
+
+ int64 origin_rowid;
+ if (!FindOrigin(origin, &origin_rowid))
+ return false;
+
+ StorageInfoRecord record;
+ if (!FindStorageInfo(origin_rowid, type, &record))
+ return false;
+
+ const char* kSql =
+ "DELETE FROM StorageInfo"
+ " WHERE rowid = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, record.rowid);
+ return statement.Run();
+}
+
+bool QuotaDatabase::GetGlobalQuota(StorageType type, int64* quota) {
+ if (!LazyOpen(false))
+ return false;
+ return meta_table_->GetValue(GetGlobalQuotaKey(type).c_str(), quota);
+}
+
+bool QuotaDatabase::SetGlobalQuota(StorageType type, int64 quota) {
+ if (!LazyOpen(true))
+ return false;
+ return meta_table_->SetValue(GetGlobalQuotaKey(type).c_str(), quota);
+}
+
+bool QuotaDatabase::GetLRUOrigins(
+ StorageType type, std::vector<GURL>* origins,
+ int max_used_count, int num_origins_limit) {
+ DCHECK(origins);
+ DCHECK(num_origins_limit > 0);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSqlBase =
+ "SELECT o.origin_url FROM Origins o, StorageInfo s"
+ " WHERE o.rowid = s.origin_rowid AND"
+ " s.type = ?";
+
+ // TODO(kinuko): depending on how we call this method, consider creating
+ // an index to avoid frequent full table scan.
+ const char* kSqlSuffix =
+ " ORDER BY s.last_access_time ASC "
+ " LIMIT ?";
+
+ std::string sql(kSqlBase);
+ sql::StatementID id = SQL_FROM_HERE;
+ if (max_used_count >= 0) {
+ sql += " AND s.used_count <= ?";
+ id = SQL_FROM_HERE;
+ }
+ sql += kSqlSuffix;
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(db_.get(), id, sql.c_str(), &statement))
+ return false;
+
+ int column = 0;
+ statement.BindInt(column++, static_cast<int>(type));
+ if (max_used_count >= 0)
+ statement.BindInt(column++, max_used_count);
+ statement.BindInt(column++, num_origins_limit);
+
+ origins->clear();
+ while (statement.Step())
+ origins->push_back(GURL(statement.ColumnString(0)));
+
+ DCHECK(origins->size() <= static_cast<size_t>(num_origins_limit));
+
+ return statement.Succeeded();
+}
+
+bool QuotaDatabase::FindOrigin(const GURL& origin_url, int64* origin_rowid) {
+ DCHECK(origin_rowid);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql = "SELECT rowid FROM Origins WHERE origin_url = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindString(0, origin_url.spec());
+ if (!statement.Step() || !statement.Succeeded())
+ return false;
+
+ *origin_rowid = statement.ColumnInt64(0);
+ return true;
+}
+
+bool QuotaDatabase::InsertOrigin(const GURL& origin_url, int64 *origin_rowid) {
+ DCHECK(origin_rowid);
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql =
+ "INSERT INTO Origins (origin_url) VALUES(?)";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+
+ statement.BindString(0, origin_url.spec());
+ if (!statement.Run())
+ return false;
+
+ *origin_rowid = db_->GetLastInsertRowId();
+ return true;
+}
+
+bool QuotaDatabase::FindStorageInfo(int64 origin_rowid, StorageType type,
+ StorageInfoRecord* record) {
+ DCHECK(record);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT rowid, origin_rowid, quota, used_count, last_access_time"
+ " FROM StorageInfo"
+ " WHERE origin_rowid = ? AND type = ?";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, origin_rowid);
+ statement.BindInt(1, static_cast<int>(type));
+ if (!statement.Step() || !statement.Succeeded())
+ return false;
+
+ record->rowid = statement.ColumnInt64(0);
+ record->origin_rowid = statement.ColumnInt64(1);
+ record->quota = statement.ColumnInt64(2);
+ record->used_count = statement.ColumnInt(3);
+ record->last_access_time = base::Time::FromInternalValue(
+ statement.ColumnInt64(4));
+
+ return true;
+}
+
+bool QuotaDatabase::FindStorageInfo(const GURL& origin, StorageType type,
+ StorageInfoRecord* record) {
+ DCHECK(record);
+ if (!LazyOpen(false))
+ return false;
+
+ int64 origin_rowid;
+ if (!FindOrigin(origin, &origin_rowid))
+ return false;
+
+ return FindStorageInfo(origin_rowid, type, record);
+}
+
+bool QuotaDatabase::InsertStorageInfo(const StorageInfoRecord& record) {
+ if (!LazyOpen(true))
+ return false;
+
+ DCHECK(record.type == kStorageTypeTemporary ||
+ record.type == kStorageTypePersistent);
+
+ const char* kSql =
+ "INSERT INTO StorageInfo"
+ " (origin_rowid, type, quota, used_count, last_access_time)"
+ " VALUES(?, ?, ?, ?, ?)";
+
+ sql::Statement statement;
+ if (!PrepareCachedStatement(db_.get(), SQL_FROM_HERE, kSql, &statement))
+ return false;
+
+ statement.BindInt64(0, record.origin_rowid);
+ statement.BindInt(1, static_cast<int>(record.type));
+ statement.BindInt64(2, record.quota);
+ statement.BindInt(3, record.used_count);
+ statement.BindInt64(4, record.last_access_time.ToInternalValue());
+ return statement.Run();
+}
+
+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;
+
+ if (!create_if_needed && !file_util::PathExists(db_file_path_))
+ return false;
+
+ db_.reset(new sql::Connection);
+ meta_table_.reset(new sql::MetaTable);
+
+ bool opened = false;
+ 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;
+ }
+
+ return true;
+}
+
+bool QuotaDatabase::EnsureDatabaseVersion() {
+ if (!sql::MetaTable::DoesTableExist(db_.get()))
+ return CreateSchema();
+
+ 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) {
+ // TODO(kinuko): Support schema upgrade.
+ return ResetSchema();
+ }
+
+#ifndef NDEBUG
+ DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
+ for (int i = 0; i < kTableCount; ++i) {
+ DCHECK(db_->DoesTableExist(kTables[i].table_name));
+ }
+#endif
+
+ return true;
+}
+
+bool QuotaDatabase::CreateSchema() {
+ // TODO(kinuko): Factor out the common code to create databases.
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin())
+ return false;
+
+ if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
+ return false;
+
+ for (int i = 0; i < kTableCount; ++i) {
+ std::string sql("CREATE TABLE ");
+ sql += kTables[i].table_name;
+ sql += kTables[i].columns;
+ if (!db_->Execute(sql.c_str()))
+ return false;
+ }
+
+ for (int i = 0; i < kIndexCount; ++i) {
+ std::string sql;
+ if (kIndexes[i].unique)
+ sql += "CREATE UNIQUE INDEX ";
+ else
+ sql += "CREATE INDEX ";
+ sql += kIndexes[i].index_name;
+ sql += " ON ";
+ sql += kIndexes[i].table_name;
+ sql += kIndexes[i].columns;
+ if (!db_->Execute(sql.c_str()))
+ 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);
+}
+
+} // quota namespace