diff options
author | dumi@chromium.org <dumi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-02 21:41:56 +0000 |
---|---|---|
committer | dumi@chromium.org <dumi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-02 21:41:56 +0000 |
commit | 615dedfcdefe5fd2d5ea3c97aea4f2669e0d6657 (patch) | |
tree | 02038c0c0863cc0ccb766b4c9f120e039b0d8f0e /webkit | |
parent | 9f8b6045dedbb3189aec7910f23d1c8fa59a7641 (diff) | |
download | chromium_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/DEPS | 3 | ||||
-rw-r--r-- | webkit/database/database_tracker.cc | 211 | ||||
-rw-r--r-- | webkit/database/database_tracker.h | 123 | ||||
-rw-r--r-- | webkit/database/database_tracker_unittest.cc | 175 | ||||
-rw-r--r-- | webkit/database/databases_table.cc | 122 | ||||
-rw-r--r-- | webkit/database/databases_table.h | 47 | ||||
-rw-r--r-- | webkit/database/databases_table_unittest.cc | 126 | ||||
-rw-r--r-- | webkit/tools/test_shell/test_shell.gyp | 2 | ||||
-rw-r--r-- | webkit/webkit.gyp | 4 |
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', ], |