summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authordumi@chromium.org <dumi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-02 21:41:56 +0000
committerdumi@chromium.org <dumi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-02 21:41:56 +0000
commit615dedfcdefe5fd2d5ea3c97aea4f2669e0d6657 (patch)
tree02038c0c0863cc0ccb766b4c9f120e039b0d8f0e /webkit
parent9f8b6045dedbb3189aec7910f23d1c8fa59a7641 (diff)
downloadchromium_src-615dedfcdefe5fd2d5ea3c97aea4f2669e0d6657.zip
chromium_src-615dedfcdefe5fd2d5ea3c97aea4f2669e0d6657.tar.gz
chromium_src-615dedfcdefe5fd2d5ea3c97aea4f2669e0d6657.tar.bz2
Adding Chromium's database tracker.
BUG=none TEST=none Review URL: http://codereview.chromium.org/334039 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@30747 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/DEPS3
-rw-r--r--webkit/database/database_tracker.cc211
-rw-r--r--webkit/database/database_tracker.h123
-rw-r--r--webkit/database/database_tracker_unittest.cc175
-rw-r--r--webkit/database/databases_table.cc122
-rw-r--r--webkit/database/databases_table.h47
-rw-r--r--webkit/database/databases_table_unittest.cc126
-rw-r--r--webkit/tools/test_shell/test_shell.gyp2
-rw-r--r--webkit/webkit.gyp4
9 files changed, 813 insertions, 0 deletions
diff --git a/webkit/DEPS b/webkit/DEPS
index 2dbe2ac..537654c 100644
--- a/webkit/DEPS
+++ b/webkit/DEPS
@@ -13,6 +13,9 @@ include_rules = [
"+grit", # For generated headers
"+third_party/sqlite",
+ # For databases/
+ "+app/sql",
+
# TODO(brettw) - review these; move up if it's ok, or remove the dependency
"+net/base",
"+net/ftp",
diff --git a/webkit/database/database_tracker.cc b/webkit/database/database_tracker.cc
new file mode 100644
index 0000000..1edf0bf
--- /dev/null
+++ b/webkit/database/database_tracker.cc
@@ -0,0 +1,211 @@
+// Copyright (c) 2009 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/database/database_tracker.h"
+
+#include <vector>
+
+#include "app/sql/connection.h"
+#include "app/sql/meta_table.h"
+#include "app/sql/statement.h"
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/string_util.h"
+#include "webkit/database/databases_table.h"
+
+namespace webkit_database {
+
+const FilePath::CharType kDatabaseDirectoryName[] =
+ FILE_PATH_LITERAL("databases");
+const FilePath::CharType kTrackerDatabaseFileName[] =
+ FILE_PATH_LITERAL("Databases.db");
+const int kCurrentVersion = 1;
+const int kCompatibleVersion = 1;
+const int64 kDefaultQuota = 5 * 1024 * 1024;
+
+DatabaseTracker::DatabaseTracker(const FilePath& profile_path)
+ : initialized_(false),
+ db_dir_(profile_path.Append(FilePath(kDatabaseDirectoryName))),
+ db_(new sql::Connection()),
+ databases_table_(NULL),
+ meta_table_(NULL) {
+}
+
+DatabaseTracker::~DatabaseTracker() {
+ DCHECK(observers_.size() == 0);
+}
+
+void DatabaseTracker::DatabaseOpened(const string16& origin_identifier,
+ const string16& database_name,
+ const string16& database_description,
+ int64 estimated_size,
+ int64* database_size,
+ int64* space_available) {
+ if (!LazyInit()) {
+ *database_size = 0;
+ *space_available = 0;
+ return;
+ }
+
+ InsertOrUpdateDatabaseDetails(origin_identifier, database_name,
+ database_description, estimated_size);
+
+ *database_size = GetCachedDatabaseFileSize(origin_identifier, database_name);
+ *space_available = GetOriginSpaceAvailable(origin_identifier);
+}
+
+void DatabaseTracker::DatabaseModified(const string16& origin_identifier,
+ const string16& database_name) {
+ if (!LazyInit())
+ return;
+
+ int64 updated_db_size =
+ UpdateCachedDatabaseFileSize(origin_identifier, database_name);
+ int64 space_available = GetOriginSpaceAvailable(origin_identifier);
+ FOR_EACH_OBSERVER(Observer, observers_, OnDatabaseSizeChanged(
+ origin_identifier, database_name, updated_db_size, space_available));
+}
+
+void DatabaseTracker::DatabaseClosed(const string16& origin_identifier,
+ const string16& database_name) {
+ // TODO(dumi): figure out how to use this information at a later time
+}
+
+void DatabaseTracker::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DatabaseTracker::RemoveObserver(Observer* observer) {
+ // When we remove a listener, we do not know which cached information
+ // is still needed and which information can be discarded. So we just
+ // clear all caches and re-populate them as needed.
+ observers_.RemoveObserver(observer);
+ ClearAllCachedOriginInfo();
+}
+
+void DatabaseTracker::CloseTrackerDatabaseAndClearCaches() {
+ ClearAllCachedOriginInfo();
+ meta_table_.reset(NULL);
+ databases_table_.reset(NULL);
+ db_->Close();
+ initialized_ = false;
+}
+
+FilePath DatabaseTracker::GetFullDBFilePath(
+ const string16& origin_identifier,
+ const string16& database_name) const {
+ return db_dir_.Append(FilePath::FromWStringHack(UTF16ToWide(
+ origin_identifier + ASCIIToUTF16("_") + database_name)));
+}
+
+bool DatabaseTracker::LazyInit() {
+ if (!initialized_) {
+ databases_table_.reset(new DatabasesTable(db_.get()));
+ meta_table_.reset(new sql::MetaTable());
+ initialized_ =
+ file_util::CreateDirectory(db_dir_) &&
+ db_->Open(db_dir_.Append(FilePath(kTrackerDatabaseFileName))) &&
+ meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion) &&
+ (meta_table_->GetCompatibleVersionNumber() <= kCurrentVersion) &&
+ databases_table_->Init();
+ }
+ return initialized_;
+}
+
+void DatabaseTracker::InsertOrUpdateDatabaseDetails(
+ const string16& origin_identifier,
+ const string16& database_name,
+ const string16& database_description,
+ int64 estimated_size) {
+ DatabaseDetails details;
+ if (!databases_table_->GetDatabaseDetails(
+ origin_identifier, database_name, &details)) {
+ details.origin_identifier = origin_identifier;
+ details.database_name = database_name;
+ details.description = database_description;
+ details.estimated_size = estimated_size;
+ databases_table_->InsertDatabaseDetails(details);
+ } else if ((details.description != database_description) ||
+ (details.estimated_size != estimated_size)) {
+ details.description = database_description;
+ details.estimated_size = estimated_size;
+ databases_table_->UpdateDatabaseDetails(details);
+ }
+}
+
+int64 DatabaseTracker::GetDBFileSize(const string16& origin_identifier,
+ const string16& database_name) const {
+ FilePath db_file_name = GetFullDBFilePath(origin_identifier, database_name);
+ int64 db_file_size = 0;
+ if (!file_util::GetFileSize(db_file_name, &db_file_size))
+ db_file_size = 0;
+ return db_file_size;
+}
+
+void DatabaseTracker::ClearAllCachedOriginInfo() {
+ origins_info_map_.clear();
+}
+
+DatabaseTracker::CachedOriginInfo* DatabaseTracker::GetCachedOriginInfo(
+ const string16& origin_identifier) {
+ // Populate the cache with data for this origin if needed.
+ if (origins_info_map_.find(origin_identifier) == origins_info_map_.end()) {
+ std::vector<DatabaseDetails> details;
+ if (!databases_table_->GetAllDatabaseDetailsForOrigin(
+ origin_identifier, &details)) {
+ return NULL;
+ }
+
+ CachedOriginInfo& origin_info = origins_info_map_[origin_identifier];
+ for (std::vector<DatabaseDetails>::const_iterator it = details.begin();
+ it != details.end(); it++) {
+ int64 db_file_size =
+ GetDBFileSize(it->origin_identifier, it->database_name);
+ origin_info.SetCachedDatabaseSize(it->database_name, db_file_size);
+ }
+ }
+
+ return &origins_info_map_[origin_identifier];
+}
+
+int64 DatabaseTracker::GetCachedDatabaseFileSize(
+ const string16& origin_identifier,
+ const string16& database_name) {
+ CachedOriginInfo* origin_info = GetCachedOriginInfo(origin_identifier);
+ if (!origin_info)
+ return 0;
+ return origin_info->GetCachedDatabaseSize(database_name);
+}
+
+int64 DatabaseTracker::UpdateCachedDatabaseFileSize(
+ const string16& origin_identifier,
+ const string16& database_name) {
+ int64 new_size = GetDBFileSize(origin_identifier, database_name);
+ CachedOriginInfo* origin_info = GetCachedOriginInfo(origin_identifier);
+ if (origin_info)
+ origin_info->SetCachedDatabaseSize(database_name, new_size);
+ return new_size;
+}
+
+int64 DatabaseTracker::GetOriginUsage(const string16& origin_identifier) {
+ CachedOriginInfo* origin_info = GetCachedOriginInfo(origin_identifier);
+ if (!origin_info)
+ return kint64max;
+ return origin_info->TotalSize();
+}
+
+int64 DatabaseTracker::GetOriginQuota(
+ const string16& /*origin_identifier*/) const {
+ return kDefaultQuota;
+}
+
+int64 DatabaseTracker::GetOriginSpaceAvailable(
+ const string16& origin_identifier) {
+ int64 space_available = GetOriginQuota(origin_identifier) -
+ GetOriginUsage(origin_identifier);
+ return (space_available < 0 ? 0 : space_available);
+}
+
+} // namespace webkit_database
diff --git a/webkit/database/database_tracker.h b/webkit/database/database_tracker.h
new file mode 100644
index 0000000..1e017aa
--- /dev/null
+++ b/webkit/database/database_tracker.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2009 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_DATABASE_DATABASE_TRACKER_H_
+#define WEBKIT_DATABASE_DATABASE_TRACKER_H_
+
+#include <map>
+
+#include "base/file_path.h"
+#include "base/observer_list.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "base/string16.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace sql {
+class Connection;
+class MetaTable;
+}
+
+namespace webkit_database {
+
+class DatabasesTable;
+
+// This class manages the main database, and keeps track of per origin quotas.
+//
+// The data in this class is not thread-safe, so all methods of this class
+// should be called on the same thread. The only exception is
+// database_directory() which returns a constant that is initialized when
+// the DatabaseTracker instance is created.
+//
+// Furthermore, some methods of this class have to read/write data from/to
+// the disk. Therefore, in a multi-threaded application, all methods of this
+// class should be called on the thread dedicated to file operations (file
+// thread in the browser process, for example), if such a thread exists.
+class DatabaseTracker
+ : public base::RefCountedThreadSafe<DatabaseTracker> {
+ public:
+ class Observer {
+ public:
+ virtual void OnDatabaseSizeChanged(const string16& origin_identifier,
+ const string16& database_name,
+ int64 database_size,
+ int64 space_available) = 0;
+ virtual ~Observer() {}
+ };
+
+ explicit DatabaseTracker(const FilePath& profile_path);
+ ~DatabaseTracker();
+
+ void DatabaseOpened(const string16& origin_identifier,
+ const string16& database_name,
+ const string16& database_details,
+ int64 estimated_size,
+ int64* database_size,
+ int64* space_available);
+ void DatabaseModified(const string16& origin_identifier,
+ const string16& database_name);
+ void DatabaseClosed(const string16& origin_identifier,
+ const string16& database_name);
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ void CloseTrackerDatabaseAndClearCaches();
+
+ const FilePath& DatabaseDirectory() const { return db_dir_; }
+ FilePath GetFullDBFilePath(const string16& origin_identifier,
+ const string16& database_name) const;
+
+ private:
+ class CachedOriginInfo {
+ public:
+ CachedOriginInfo() : total_size_(0) { }
+ int64 TotalSize() const { return total_size_; }
+ int64 GetCachedDatabaseSize(const string16& database_name) {
+ return cached_database_sizes_[database_name];
+ }
+ void SetCachedDatabaseSize(const string16& database_name, int64 new_size) {
+ int64 old_size = cached_database_sizes_[database_name];
+ cached_database_sizes_[database_name] = new_size;
+ if (new_size != old_size)
+ total_size_ += new_size - old_size;
+ }
+
+ private:
+ int64 total_size_;
+ std::map<string16, int64> cached_database_sizes_;
+ };
+
+ bool LazyInit();
+ void InsertOrUpdateDatabaseDetails(const string16& origin_identifier,
+ const string16& database_name,
+ const string16& database_details,
+ int64 estimated_size);
+
+ void ClearAllCachedOriginInfo();
+ CachedOriginInfo* GetCachedOriginInfo(const string16& origin_identifier);
+ int64 GetCachedDatabaseFileSize(const string16& origin_identifier,
+ const string16& database_name);
+ int64 UpdateCachedDatabaseFileSize(const string16& origin_identifier,
+ const string16& database_name);
+ int64 GetDBFileSize(const string16& origin_identifier,
+ const string16& database_name) const;
+ int64 GetOriginUsage(const string16& origin_identifer);
+ int64 GetOriginQuota(const string16& origin_identifier) const;
+ int64 GetOriginSpaceAvailable(const string16& origin_identifier);
+
+ bool initialized_;
+ const FilePath db_dir_;
+ scoped_ptr<sql::Connection> db_;
+ scoped_ptr<DatabasesTable> databases_table_;
+ scoped_ptr<sql::MetaTable> meta_table_;
+ ObserverList<Observer> observers_;
+ std::map<string16, CachedOriginInfo> origins_info_map_;
+
+ FRIEND_TEST(DatabaseTrackerTest, TestIt);
+};
+
+} // namespace webkit_database
+
+#endif // WEBKIT_DATABASE_DATABASE_TRACKER_H_
diff --git a/webkit/database/database_tracker_unittest.cc b/webkit/database/database_tracker_unittest.cc
new file mode 100644
index 0000000..7fadeff
--- /dev/null
+++ b/webkit/database/database_tracker_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright (c) 2009 The Chromium Authos. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/scoped_ptr.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/database/database_tracker.h"
+
+namespace {
+
+class TestObserver : public webkit_database::DatabaseTracker::Observer {
+ public:
+ TestObserver() : new_notification_received_(false) {}
+ virtual ~TestObserver() {}
+ virtual void OnDatabaseSizeChanged(const string16& origin_identifier,
+ const string16& database_name,
+ int64 database_size,
+ int64 space_available) {
+ new_notification_received_ = true;
+ origin_identifier_ = origin_identifier;
+ database_name_ = database_name;
+ database_size_ = database_size;
+ space_available_ = space_available;
+ }
+ bool DidReceiveNewNotification() {
+ bool temp_new_notification_received = new_notification_received_;
+ new_notification_received_ = false;
+ return temp_new_notification_received;
+ }
+ string16 GetNotificationOriginIdentifier() { return origin_identifier_; }
+ string16 GetNotificationDatabaseName() { return database_name_; }
+ int64 GetNotificationDatabaseSize() { return database_size_; }
+ int64 GetNotificationSpaceAvailable() { return space_available_; }
+
+ private:
+ bool new_notification_received_;
+ string16 origin_identifier_;
+ string16 database_name_;
+ int64 database_size_;
+ int64 space_available_;
+};
+
+void CheckNotificationReceived(TestObserver* observer,
+ const string16& expected_origin_identifier,
+ const string16& expected_database_name,
+ int64 expected_database_size,
+ int64 expected_space_available) {
+ EXPECT_TRUE(observer->DidReceiveNewNotification());
+ EXPECT_EQ(expected_origin_identifier,
+ observer->GetNotificationOriginIdentifier());
+ EXPECT_EQ(expected_database_name,
+ observer->GetNotificationDatabaseName());
+ EXPECT_EQ(expected_database_size,
+ observer->GetNotificationDatabaseSize());
+ EXPECT_EQ(expected_space_available,
+ observer->GetNotificationSpaceAvailable());
+}
+
+} // namespace
+
+namespace webkit_database {
+
+TEST(DatabaseTrackerTest, TestIt) {
+ // Initialize the tracker database.
+ ScopedTempDir temp_dir;
+ EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
+ scoped_refptr<DatabaseTracker> tracker(new DatabaseTracker(temp_dir.path()));
+
+ // Get the default quota for all origins.
+ const int64 kDefaultQuota = tracker->GetOriginQuota(EmptyString16());
+
+ // Add two observers.
+ TestObserver observer1;
+ TestObserver observer2;
+ tracker->AddObserver(&observer1);
+ tracker->AddObserver(&observer2);
+
+ // Open three new databases.
+ int64 database_size = 0;
+ int64 space_available = 0;
+ const string16 kOrigin1 = ASCIIToUTF16("kOrigin1");
+ const string16 kOrigin2 = ASCIIToUTF16("kOrigin2");
+ const string16 kDB1 = ASCIIToUTF16("kDB1");
+ const string16 kDB2 = ASCIIToUTF16("kDB2");
+ const string16 kDB3 = ASCIIToUTF16("kDB3");
+ const string16 kDescription = ASCIIToUTF16("database_kDescription");
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
+ &database_size, &space_available);
+ EXPECT_EQ(0, database_size);
+ EXPECT_EQ(kDefaultQuota, space_available);
+ tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0,
+ &database_size, &space_available);
+ EXPECT_EQ(0, database_size);
+ EXPECT_EQ(kDefaultQuota, space_available);
+ tracker->DatabaseOpened(kOrigin1, kDB3, kDescription, 0,
+ &database_size, &space_available);
+ EXPECT_EQ(0, database_size);
+ EXPECT_EQ(kDefaultQuota, space_available);
+
+ // Tell the tracker that a database has changed.
+ // Even though nothing has changed, the observers should be notified.
+ tracker->DatabaseModified(kOrigin1, kDB1);
+ CheckNotificationReceived(&observer1, kOrigin1, kDB1, 0, kDefaultQuota);
+ CheckNotificationReceived(&observer2, kOrigin1, kDB1, 0, kDefaultQuota);
+
+ // Write some data to each file and check that the listeners are
+ // called with the appropriate values.
+ EXPECT_EQ(1, file_util::WriteFile(
+ tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1));
+ EXPECT_EQ(2, file_util::WriteFile(
+ tracker->GetFullDBFilePath(kOrigin2, kDB2), "aa", 2));
+ EXPECT_EQ(4, file_util::WriteFile(
+ tracker->GetFullDBFilePath(kOrigin1, kDB3), "aaaa", 4));
+ tracker->DatabaseModified(kOrigin1, kDB1);
+ CheckNotificationReceived(&observer1, kOrigin1, kDB1, 1, kDefaultQuota - 1);
+ CheckNotificationReceived(&observer2, kOrigin1, kDB1, 1, kDefaultQuota - 1);
+ tracker->DatabaseModified(kOrigin2, kDB2);
+ CheckNotificationReceived(&observer1, kOrigin2, kDB2, 2, kDefaultQuota - 2);
+ CheckNotificationReceived(&observer2, kOrigin2, kDB2, 2, kDefaultQuota - 2);
+ tracker->DatabaseModified(kOrigin1, kDB3);
+ CheckNotificationReceived(&observer1, kOrigin1, kDB3, 4, kDefaultQuota - 5);
+ CheckNotificationReceived(&observer2, kOrigin1, kDB3, 4, kDefaultQuota - 5);
+
+ // Open an existing database and check the reported size
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
+ &database_size, &space_available);
+ EXPECT_EQ(1, database_size);
+ EXPECT_EQ(kDefaultQuota - 5, space_available);
+
+ // Make sure that the observers are notified even if
+ // the size of the database hasn't changed.
+ EXPECT_EQ(1, file_util::WriteFile(
+ tracker->GetFullDBFilePath(kOrigin1, kDB1), "b", 1));
+ tracker->DatabaseModified(kOrigin1, kDB1);
+ CheckNotificationReceived(&observer1, kOrigin1, kDB1, 1, kDefaultQuota - 5);
+ CheckNotificationReceived(&observer2, kOrigin1, kDB1, 1, kDefaultQuota - 5);
+
+ // Remove an observer; this should clear all caches.
+ tracker->RemoveObserver(&observer2);
+
+ // Change kDB1's and kDB3's size and call tracker->DatabaseModified()
+ // for kDB1 only. If the caches were indeed cleared, then calling
+ // tracker->DatabaseModified() should re-populate the cache for
+ // kOrigin1 == kOrigin1, and thus, should pick up kDB3's size change too.
+ EXPECT_EQ(5, file_util::WriteFile(
+ tracker->GetFullDBFilePath(kOrigin1, kDB1), "ccccc", 5));
+ EXPECT_EQ(6, file_util::WriteFile(
+ tracker->GetFullDBFilePath(kOrigin1, kDB3), "dddddd", 6));
+ tracker->DatabaseModified(kOrigin1, kDB1);
+ CheckNotificationReceived(&observer1, kOrigin1, kDB1, 5, kDefaultQuota - 11);
+ EXPECT_FALSE(observer2.DidReceiveNewNotification());
+
+ // Close the tracker database and clear all caches.
+ // Then make sure that DatabaseOpened() still returns the correct result.
+ tracker->CloseTrackerDatabaseAndClearCaches();
+ tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
+ &database_size, &space_available);
+ EXPECT_EQ(5, database_size);
+ EXPECT_EQ(kDefaultQuota - 11, space_available);
+
+ // Close the tracker database and clear all caches. Then make sure that
+ // DatabaseModified() still calls the observers with correct values.
+ tracker->CloseTrackerDatabaseAndClearCaches();
+ tracker->DatabaseModified(kOrigin1, kDB3);
+ CheckNotificationReceived(&observer1, kOrigin1, kDB3, 6, kDefaultQuota - 11);
+
+ // Clean up.
+ tracker->RemoveObserver(&observer1);
+}
+
+} // namespace webkit_database
diff --git a/webkit/database/databases_table.cc b/webkit/database/databases_table.cc
new file mode 100644
index 0000000..af351d8
--- /dev/null
+++ b/webkit/database/databases_table.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 2009 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/database/databases_table.h"
+
+#include "app/sql/connection.h"
+#include "app/sql/statement.h"
+#include "base/string_util.h"
+
+namespace webkit_database {
+
+bool DatabasesTable::Init() {
+ // 'Databases' schema:
+ // id A unique ID assigned to each database
+ // origin The originto which the database belongs. This is a
+ // string that can be used as part of a file name
+ // (http_webkit.org_0, for example).
+ // name The database name.
+ // description A short description of the database.
+ // estimated_size The estimated size of the database.
+ return db_->DoesTableExist("Databases") ||
+ (db_->Execute(
+ "CREATE TABLE Databases ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "origin TEXT NOT NULL, "
+ "name TEXT NOT NULL, "
+ "description TEXT NOT NULL, "
+ "estimated_size INTEGER NOT NULL)") &&
+ db_->Execute(
+ "CREATE INDEX origin_index ON Databases (origin)") &&
+ db_->Execute(
+ "CREATE UNIQUE INDEX unique_index ON Databases (origin, name)"));
+}
+
+bool DatabasesTable::GetDatabaseDetails(const string16& origin_identifier,
+ const string16& database_name,
+ DatabaseDetails* details) {
+ DCHECK(details);
+ sql::Statement select_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "SELECT description, estimated_size FROM Databases "
+ "WHERE origin = ? AND name = ?"));
+ if (select_statement.is_valid() &&
+ select_statement.BindString(0, UTF16ToUTF8(origin_identifier)) &&
+ select_statement.BindString(1, UTF16ToUTF8(database_name)) &&
+ select_statement.Step()) {
+ details->origin_identifier = origin_identifier;
+ details->database_name = database_name;
+ details->description = UTF8ToUTF16(select_statement.ColumnString(0));
+ details->estimated_size = select_statement.ColumnInt64(1);
+ return true;
+ }
+
+ return false;
+}
+
+bool DatabasesTable::InsertDatabaseDetails(const DatabaseDetails& details) {
+ sql::Statement insert_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "INSERT INTO Databases (origin, name, description, "
+ "estimated_size) VALUES (?, ?, ?, ?)"));
+ if (insert_statement.is_valid() &&
+ insert_statement.BindString(0, UTF16ToUTF8(details.origin_identifier)) &&
+ insert_statement.BindString(1, UTF16ToUTF8(details.database_name)) &&
+ insert_statement.BindString(2, UTF16ToUTF8(details.description)) &&
+ insert_statement.BindInt64(3, details.estimated_size)) {
+ return insert_statement.Run();
+ }
+
+ return false;
+}
+
+bool DatabasesTable::UpdateDatabaseDetails(const DatabaseDetails& details) {
+ sql::Statement update_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "UPDATE Databases SET description = ?, "
+ "estimated_size = ? WHERE origin = ? AND name = ?"));
+ if (update_statement.is_valid() &&
+ update_statement.BindString(0, UTF16ToUTF8(details.description)) &&
+ update_statement.BindInt64(1, details.estimated_size) &&
+ update_statement.BindString(2, UTF16ToUTF8(details.origin_identifier)) &&
+ update_statement.BindString(3, UTF16ToUTF8(details.database_name))) {
+ return (update_statement.Run() && db_->GetLastChangeCount());
+ }
+
+ return false;
+}
+
+bool DatabasesTable::DeleteDatabaseDetails(const string16& origin_identifier,
+ const string16& database_name) {
+ sql::Statement delete_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "DELETE FROM Databases WHERE origin = ? AND name = ?"));
+ if (delete_statement.is_valid() &&
+ delete_statement.BindString(0, UTF16ToUTF8(origin_identifier)) &&
+ delete_statement.BindString(1, UTF16ToUTF8(database_name))) {
+ return (delete_statement.Run() && db_->GetLastChangeCount());
+ }
+
+ return false;
+}
+
+bool DatabasesTable::GetAllDatabaseDetailsForOrigin(
+ const string16& origin_identifier,
+ std::vector<DatabaseDetails>* details_vector) {
+ sql::Statement statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "SELECT name, description, estimated_size "
+ "FROM Databases WHERE origin = ? ORDER BY origin, name"));
+ if (statement.is_valid() &&
+ statement.BindString(0, UTF16ToUTF8(origin_identifier))) {
+ while (statement.Step()) {
+ DatabaseDetails details;
+ details.origin_identifier = origin_identifier;
+ details.database_name = UTF8ToUTF16(statement.ColumnString(0));
+ details.description = UTF8ToUTF16(statement.ColumnString(1));
+ details.estimated_size = statement.ColumnInt64(2);
+ details_vector->push_back(details);
+ }
+ return statement.Succeeded();
+ }
+
+ return false;
+}
+
+} // namespace webkit_database
diff --git a/webkit/database/databases_table.h b/webkit/database/databases_table.h
new file mode 100644
index 0000000..cafff9f
--- /dev/null
+++ b/webkit/database/databases_table.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2009 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_DATABASE_DATABASES_TABLE_H_
+#define WEBKIT_DATABASE_DATABASES_TABLE_H_
+
+#include <vector>
+
+#include "base/string16.h"
+
+namespace sql {
+class Connection;
+}
+
+namespace webkit_database {
+
+struct DatabaseDetails {
+ DatabaseDetails() : estimated_size(0) { }
+
+ string16 origin_identifier;
+ string16 database_name;
+ string16 description;
+ int64 estimated_size;
+};
+
+class DatabasesTable {
+ public:
+ explicit DatabasesTable(sql::Connection* db) : db_(db) { }
+
+ bool Init();
+ bool GetDatabaseDetails(const string16& origin_identifier,
+ const string16& database_name,
+ DatabaseDetails* details);
+ bool InsertDatabaseDetails(const DatabaseDetails& details);
+ bool UpdateDatabaseDetails(const DatabaseDetails& details);
+ bool DeleteDatabaseDetails(const string16& origin_identifier,
+ const string16& database_name);
+ bool GetAllDatabaseDetailsForOrigin(const string16& origin_identifier,
+ std::vector<DatabaseDetails>* details);
+ private:
+ sql::Connection* db_;
+};
+
+} // namespace webkit_database
+
+#endif // WEBKIT_DATABASE_DATABASES_TABLE_H_
diff --git a/webkit/database/databases_table_unittest.cc b/webkit/database/databases_table_unittest.cc
new file mode 100644
index 0000000..5012cc5
--- /dev/null
+++ b/webkit/database/databases_table_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2009 The Chromium Authos. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "app/sql/connection.h"
+#include "app/sql/statement.h"
+#include "base/scoped_temp_dir.h"
+#include "base/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/database/databases_table.h"
+
+namespace webkit_database {
+
+class TestErrorDelegate : public sql::ErrorDelegate {
+ public:
+ virtual ~TestErrorDelegate() { }
+ virtual int OnError(
+ int error, sql::Connection* connection, sql::Statement* stmt) {
+ return error;
+ }
+};
+
+static void CheckDetailsAreEqual(const DatabaseDetails& d1,
+ const DatabaseDetails& d2) {
+ EXPECT_EQ(d1.origin_identifier, d2.origin_identifier);
+ EXPECT_EQ(d1.database_name, d2.database_name);
+ EXPECT_EQ(d1.description, d2.description);
+ EXPECT_EQ(d1.estimated_size, d2.estimated_size);
+}
+
+static bool DatabasesTableIsEmpty(sql::Connection* db) {
+ sql::Statement statement(db->GetCachedStatement(
+ SQL_FROM_HERE, "SELECT COUNT(*) FROM Databases"));
+ return (statement.is_valid() && statement.Step() && !statement.ColumnInt(0));
+}
+
+TEST(DatabasesTableTest, TestIt) {
+ // Initialize the 'Databases' table.
+ ScopedTempDir temp_dir;
+ sql::Connection db;
+
+ // Set an error delegate that will make all operations return false on error.
+ scoped_refptr<TestErrorDelegate> error_delegate(new TestErrorDelegate());
+ db.set_error_delegate(error_delegate);
+
+ // Initialize the temp dir and the 'Databases' table.
+ EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
+ EXPECT_TRUE(db.Open(temp_dir.path().Append(FILE_PATH_LITERAL("tracker.db"))));
+ DatabasesTable databases_table(&db);
+ EXPECT_TRUE(databases_table.Init());
+
+ // The 'Databases' table should be empty.
+ EXPECT_TRUE(DatabasesTableIsEmpty(&db));
+
+ // Create the details for a databases.
+ DatabaseDetails details_in1;
+ DatabaseDetails details_out1;
+ details_in1.origin_identifier = ASCIIToUTF16("origin1");
+ details_in1.database_name = ASCIIToUTF16("db1");
+ details_in1.description = ASCIIToUTF16("description_db1");
+ details_in1.estimated_size = 100;
+
+ // Updating details for this database should fail.
+ EXPECT_FALSE(databases_table.UpdateDatabaseDetails(details_in1));
+ EXPECT_FALSE(databases_table.GetDatabaseDetails(
+ details_in1.origin_identifier,
+ details_in1.database_name,
+ &details_out1));
+
+ // Inserting details for this database should pass.
+ EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in1));
+ EXPECT_TRUE(databases_table.GetDatabaseDetails(
+ details_in1.origin_identifier,
+ details_in1.database_name,
+ &details_out1));
+
+ // Check that the details were correctly written to the database.
+ CheckDetailsAreEqual(details_in1, details_out1);
+
+ // Check that inserting a duplicate row fails.
+ EXPECT_FALSE(databases_table.InsertDatabaseDetails(details_in1));
+
+ // Insert details for another database with the same origin.
+ DatabaseDetails details_in2;
+ details_in2.origin_identifier = ASCIIToUTF16("origin1");
+ details_in2.database_name = ASCIIToUTF16("db2");
+ details_in2.description = ASCIIToUTF16("description_db2");
+ details_in2.estimated_size = 200;
+ EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in2));
+
+ // Insert details for a third database with a different origin.
+ DatabaseDetails details_in3;
+ details_in3.origin_identifier = ASCIIToUTF16("origin2");
+ details_in3.database_name = ASCIIToUTF16("db3");
+ details_in3.description = ASCIIToUTF16("description_db3");
+ details_in3.estimated_size = 300;
+ EXPECT_TRUE(databases_table.InsertDatabaseDetails(details_in3));
+
+ // There should be no database with origin "origin3".
+ std::vector<DatabaseDetails> details_out_origin3;
+ EXPECT_TRUE(databases_table.GetAllDatabaseDetailsForOrigin(
+ ASCIIToUTF16("origin3"), &details_out_origin3));
+ EXPECT_TRUE(details_out_origin3.empty());
+
+ // There should be only two databases with origin "origin1".
+ std::vector<DatabaseDetails> details_out_origin1;
+ EXPECT_TRUE(databases_table.GetAllDatabaseDetailsForOrigin(
+ details_in1.origin_identifier, &details_out_origin1));
+ EXPECT_EQ(size_t(2), details_out_origin1.size());
+ CheckDetailsAreEqual(details_in1, details_out_origin1[0]);
+ CheckDetailsAreEqual(details_in2, details_out_origin1[1]);
+
+ // Delete the details for 'db1' and check that they're no longer there.
+ EXPECT_TRUE(databases_table.DeleteDatabaseDetails(
+ details_in1.origin_identifier, details_in1.database_name));
+ EXPECT_FALSE(databases_table.GetDatabaseDetails(
+ details_in1.origin_identifier,
+ details_in1.database_name,
+ &details_out1));
+
+ // Check that trying to delete a record that doesn't exist fails.
+ EXPECT_FALSE(databases_table.DeleteDatabaseDetails(
+ ASCIIToUTF16("unknown_origin"), ASCIIToUTF16("unknown_database")));
+}
+
+} // namespace webkit_database
diff --git a/webkit/tools/test_shell/test_shell.gyp b/webkit/tools/test_shell/test_shell.gyp
index d7a18d2..d8d4625 100644
--- a/webkit/tools/test_shell/test_shell.gyp
+++ b/webkit/tools/test_shell/test_shell.gyp
@@ -374,6 +374,8 @@
'../../appcache/appcache_update_job_unittest.cc',
'../../appcache/mock_appcache_service.h',
'../../appcache/mock_appcache_storage_unittest.cc',
+ '../../database/databases_table_unittest.cc',
+ '../../database/database_tracker_unittest.cc',
'../../glue/bookmarklet_unittest.cc',
'../../glue/context_menu_unittest.cc',
'../../glue/cpp_bound_class_unittest.cc',
diff --git a/webkit/webkit.gyp b/webkit/webkit.gyp
index 77b4fc9..d9d6e55 100644
--- a/webkit/webkit.gyp
+++ b/webkit/webkit.gyp
@@ -494,6 +494,10 @@
'../third_party/sqlite/sqlite.gyp:sqlite',
],
'sources': [
+ 'database/databases_table.cc',
+ 'database/databases_table.h',
+ 'database/database_tracker.cc',
+ 'database/database_tracker.h',
'database/vfs_backend.cc',
'database/vfs_backend.h',
],