summaryrefslogtreecommitdiffstats
path: root/webkit/quota
diff options
context:
space:
mode:
authorkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-17 07:46:00 +0000
committerkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-17 07:46:00 +0000
commit42b1020f82ad53a0d0c8591e977360e8fee3415c (patch)
tree0ba1fa306810790507fb065cb4c43c215de5812e /webkit/quota
parenta604854af9ff6f729ada6d8e5bc6f53147b0f3e4 (diff)
downloadchromium_src-42b1020f82ad53a0d0c8591e977360e8fee3415c.zip
chromium_src-42b1020f82ad53a0d0c8591e977360e8fee3415c.tar.gz
chromium_src-42b1020f82ad53a0d0c8591e977360e8fee3415c.tar.bz2
Implement quota manager database.
BUG=61676 TEST=QuotaDatabaseTest.* Review URL: http://codereview.chromium.org/6627003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@78521 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/quota')
-rw-r--r--webkit/quota/quota_database.cc540
-rw-r--r--webkit/quota/quota_database.h93
-rw-r--r--webkit/quota/quota_database_unittest.cc161
-rw-r--r--webkit/quota/webkit_quota.gypi4
4 files changed, 798 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
diff --git a/webkit/quota/quota_database.h b/webkit/quota/quota_database.h
new file mode 100644
index 0000000..6aa1603
--- /dev/null
+++ b/webkit/quota/quota_database.h
@@ -0,0 +1,93 @@
+// 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.
+
+#ifndef WEBKIT_QUOTA_QUOTA_DATABASE_H_
+#define WEBKIT_QUOTA_QUOTA_DATABASE_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+
+namespace sql {
+class Connection;
+class MetaTable;
+class Statement;
+}
+
+class GURL;
+
+namespace quota {
+
+// TODO(kinuko): put this in a separated header file when we have more modules.
+enum StorageType {
+ kStorageTypeUnknown,
+ kStorageTypeTemporary,
+ kStorageTypePersistent,
+};
+
+// All the methods of this class must run on the DB thread.
+class QuotaDatabase {
+ public:
+ explicit QuotaDatabase(const FilePath& path);
+ ~QuotaDatabase();
+
+ void CloseConnection();
+
+ bool GetOriginQuota(const GURL& origin, StorageType type, int64* quota);
+ bool SetOriginQuota(const GURL& origin, StorageType type, int64 quota);
+
+ bool SetOriginLastAccessTime(const GURL& origin, StorageType type,
+ base::Time last_access_time);
+
+ bool DeleteStorageInfo(const GURL& origin, StorageType type);
+
+ bool GetGlobalQuota(StorageType type, int64* quota);
+ bool SetGlobalQuota(StorageType type, int64 quota);
+
+ // Return least recently used origins whose used_count is <=
+ // |max_used_count| up to |num_origins_limit|. If |max_used_count| is -1,
+ // it just returns LRU storages regardless of the used_count value.
+ // |num_origins_limit| must be > 0.
+ bool GetLRUOrigins(StorageType type, std::vector<GURL>* origins,
+ int max_used_count, int num_origins_limit);
+
+ private:
+ struct StorageInfoRecord;
+
+ bool FindOrigin(const GURL& origin_url, int64* origin_rowid);
+ bool InsertOrigin(const GURL& origin_url, int64* origin_rowid);
+
+ bool FindStorageInfo(int64 origin_rowid, StorageType type,
+ StorageInfoRecord* record);
+ bool FindStorageInfo(const GURL& origin, StorageType type,
+ StorageInfoRecord* record);
+ bool InsertStorageInfo(const StorageInfoRecord& record);
+
+ bool LazyOpen(bool create_if_needed);
+ bool EnsureDatabaseVersion();
+ bool CreateSchema();
+ bool ResetSchema();
+
+ FilePath db_file_path_;
+
+ scoped_ptr<sql::Connection> db_;
+ scoped_ptr<sql::MetaTable> meta_table_;
+ bool is_recreating_;
+ bool is_disabled_;
+
+ FRIEND_TEST_ALL_PREFIXES(QuotaDatabaseTest, LazyOpen);
+ FRIEND_TEST_ALL_PREFIXES(QuotaDatabaseTest, OriginQuota);
+ FRIEND_TEST_ALL_PREFIXES(QuotaDatabaseTest, GlobalQuota);
+ FRIEND_TEST_ALL_PREFIXES(QuotaDatabaseTest, OriginLastAccessTimeLRU);
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaDatabase);
+};
+
+} // namespace quota
+
+#endif // WEBKIT_QUOTA_QUOTA_DATABASE_H_
diff --git a/webkit/quota/quota_database_unittest.cc b/webkit/quota/quota_database_unittest.cc
new file mode 100644
index 0000000..836ee16
--- /dev/null
+++ b/webkit/quota/quota_database_unittest.cc
@@ -0,0 +1,161 @@
+// 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 "testing/gtest/include/gtest/gtest.h"
+
+#include "app/sql/connection.h"
+#include "base/file_util.h"
+#include "base/scoped_temp_dir.h"
+#include "googleurl/src/gurl.h"
+#include "webkit/quota/quota_database.h"
+
+namespace {
+
+const base::Time kZeroTime;
+
+class TestErrorDelegate : public sql::ErrorDelegate {
+ public:
+ virtual ~TestErrorDelegate() { }
+ virtual int OnError(
+ int error, sql::Connection* connection, sql::Statement* stmt) {
+ return error;
+ }
+};
+
+} // namespace
+
+namespace quota {
+
+TEST(QuotaDatabaseTest, LazyOpen) {
+ ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+
+ const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db");
+ QuotaDatabase db(kDbFile);
+ EXPECT_FALSE(db.LazyOpen(false));
+ ASSERT_TRUE(db.LazyOpen(true));
+ EXPECT_TRUE(file_util::PathExists(kDbFile));
+}
+
+TEST(QuotaDatabaseTest, OriginQuota) {
+ ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+
+ const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db");
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(true));
+
+ const GURL kOrigin("http://foo.com:8080/");
+ const int kQuota1 = 13579;
+ const int kQuota2 = kQuota1 + 1024;
+
+ int64 quota = -1;
+ EXPECT_FALSE(db.GetOriginQuota(kOrigin, kStorageTypeTemporary, &quota));
+ EXPECT_FALSE(db.GetOriginQuota(kOrigin, kStorageTypePersistent, &quota));
+
+ // Insert quota for temporary.
+ EXPECT_TRUE(db.SetOriginQuota(kOrigin, kStorageTypeTemporary, kQuota1));
+ EXPECT_TRUE(db.GetOriginQuota(kOrigin, kStorageTypeTemporary, &quota));
+ EXPECT_EQ(kQuota1, quota);
+
+ // Update quota for temporary.
+ EXPECT_TRUE(db.SetOriginQuota(kOrigin, kStorageTypeTemporary, kQuota2));
+ EXPECT_TRUE(db.GetOriginQuota(kOrigin, kStorageTypeTemporary, &quota));
+ EXPECT_EQ(kQuota2, quota);
+
+ // Quota for persistent must not be updated.
+ EXPECT_FALSE(db.GetOriginQuota(kOrigin, kStorageTypePersistent, &quota));
+
+ // Delete temporary storage info.
+ EXPECT_TRUE(db.DeleteStorageInfo(kOrigin, kStorageTypeTemporary));
+ EXPECT_FALSE(db.GetOriginQuota(kOrigin, kStorageTypeTemporary, &quota));
+}
+
+TEST(QuotaDatabaseTest, GlobalQuota) {
+ ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+
+ const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db");
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(true));
+
+ const int kQuota1 = 9999;
+ const int kQuota2 = 86420;
+
+ int64 quota = -1;
+ EXPECT_FALSE(db.GetGlobalQuota(kStorageTypeTemporary, &quota));
+ EXPECT_FALSE(db.GetGlobalQuota(kStorageTypePersistent, &quota));
+
+ EXPECT_TRUE(db.SetGlobalQuota(kStorageTypeTemporary, kQuota1));
+ EXPECT_TRUE(db.GetGlobalQuota(kStorageTypeTemporary, &quota));
+ EXPECT_EQ(kQuota1, quota);
+
+ EXPECT_TRUE(db.SetGlobalQuota(kStorageTypeTemporary, kQuota1 + 1024));
+ EXPECT_TRUE(db.GetGlobalQuota(kStorageTypeTemporary, &quota));
+ EXPECT_EQ(kQuota1 + 1024, quota);
+
+ EXPECT_FALSE(db.GetGlobalQuota(kStorageTypePersistent, &quota));
+
+ EXPECT_TRUE(db.SetGlobalQuota(kStorageTypePersistent, kQuota2));
+ EXPECT_TRUE(db.GetGlobalQuota(kStorageTypePersistent, &quota));
+ EXPECT_EQ(kQuota2, quota);
+}
+
+TEST(QuotaDatabaseTest, OriginLastAccessTimeLRU) {
+ ScopedTempDir data_dir;
+ ASSERT_TRUE(data_dir.CreateUniqueTempDir());
+
+ const FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db");
+ QuotaDatabase db(kDbFile);
+ ASSERT_TRUE(db.LazyOpen(true));
+
+ std::vector<GURL> origins;
+ EXPECT_TRUE(db.GetLRUOrigins(kStorageTypeTemporary, &origins, -1, 10));
+ EXPECT_EQ(0U, origins.size());
+
+ const GURL kOrigin1("http://a/");
+ const GURL kOrigin2("http://b/");
+ const GURL kOrigin3("http://c/");
+ const GURL kOrigin4("http://p/");
+
+ // Adding three temporary storages, and
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin1, kStorageTypeTemporary, base::Time::FromInternalValue(10)));
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin2, kStorageTypeTemporary, base::Time::FromInternalValue(20)));
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin3, kStorageTypeTemporary, base::Time::FromInternalValue(30)));
+
+ // one persistent.
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin4, kStorageTypePersistent, base::Time::FromInternalValue(40)));
+
+ EXPECT_TRUE(db.GetLRUOrigins(kStorageTypeTemporary, &origins, 0, 10));
+
+ ASSERT_EQ(3U, origins.size());
+ EXPECT_EQ(kOrigin1.spec(), origins[0].spec());
+ EXPECT_EQ(kOrigin2.spec(), origins[1].spec());
+ EXPECT_EQ(kOrigin3.spec(), origins[2].spec());
+
+ EXPECT_TRUE(db.SetOriginLastAccessTime(
+ kOrigin1, kStorageTypeTemporary, base::Time::Now()));
+
+ EXPECT_TRUE(db.GetLRUOrigins(kStorageTypeTemporary, &origins, 0, 10));
+
+ // Now kOrigin1 has used_count=1, so it should not be in the returned list.
+ ASSERT_EQ(2U, origins.size());
+ EXPECT_EQ(kOrigin2.spec(), origins[0].spec());
+ EXPECT_EQ(kOrigin3.spec(), origins[1].spec());
+
+ // Query again without used_count condition.
+ EXPECT_TRUE(db.GetLRUOrigins(kStorageTypeTemporary, &origins, -1, 10));
+
+ // Now kOrigin1 must be returned as the newest one.
+ ASSERT_EQ(3U, origins.size());
+ EXPECT_EQ(kOrigin2.spec(), origins[0].spec());
+ EXPECT_EQ(kOrigin3.spec(), origins[1].spec());
+ EXPECT_EQ(kOrigin1.spec(), origins[2].spec());
+}
+
+} // namespace quota
diff --git a/webkit/quota/webkit_quota.gypi b/webkit/quota/webkit_quota.gypi
index b738d43..491c494 100644
--- a/webkit/quota/webkit_quota.gypi
+++ b/webkit/quota/webkit_quota.gypi
@@ -9,9 +9,13 @@
'type': '<(library)',
'msvs_guid': '36A9AAD2-10DD-42CF-BF51-3CC79D26FCB4',
'dependencies': [
+ '<(DEPTH)/app/app.gyp:app_base',
'<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/net/net.gyp:net',
],
'sources': [
+ 'quota_database.cc',
+ 'quota_database.h',
'special_storage_policy.cc',
'special_storage_policy.h',
],