diff options
author | Kristian Monsen <kristianm@google.com> | 2010-07-22 16:36:44 +0100 |
---|---|---|
committer | Kristian Monsen <kristianm@google.com> | 2010-07-22 16:54:53 +0100 |
commit | 0998b1cdac5733f299c12d88bc31ef9c8035b8fa (patch) | |
tree | 7e69986beebc5d47ae9f52d42771942152eaa170 | |
parent | 2f7cea8af61f1126d50e0749f4ced7844b14f5b4 (diff) | |
download | external_chromium-0998b1cdac5733f299c12d88bc31ef9c8035b8fa.zip external_chromium-0998b1cdac5733f299c12d88bc31ef9c8035b8fa.tar.gz external_chromium-0998b1cdac5733f299c12d88bc31ef9c8035b8fa.tar.bz2 |
Adding chrome sql code to external/chromium
Adding sql_persistent_cookie_store.* and the dependencies in app/sql to enable persistent cookies.
The only modification from the chrome code is the threading in android/app/sqlite_persistent_cookie_store.cc.
Change-Id: Ibe817e610e8f1beecc4f2623fc3138758d2de265
-rw-r--r-- | android/app/sql/connection.cc | 342 | ||||
-rw-r--r-- | android/app/sql/connection.h | 376 | ||||
-rw-r--r-- | android/app/sql/meta_table.cc | 149 | ||||
-rw-r--r-- | android/app/sql/meta_table.h | 79 | ||||
-rw-r--r-- | android/app/sql/statement.cc | 270 | ||||
-rw-r--r-- | android/app/sql/statement.h | 166 | ||||
-rw-r--r-- | android/app/sql/transaction.cc | 51 | ||||
-rw-r--r-- | android/app/sql/transaction.h | 56 | ||||
-rw-r--r-- | android/app/sqlite_persistent_cookie_store.cc | 473 | ||||
-rw-r--r-- | android/app/sqlite_persistent_cookie_store.h | 51 |
10 files changed, 2013 insertions, 0 deletions
diff --git a/android/app/sql/connection.cc b/android/app/sql/connection.cc new file mode 100644 index 0000000..984d8ca --- /dev/null +++ b/android/app/sql/connection.cc @@ -0,0 +1,342 @@ +// 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 "app/sql/connection.h" + +#include <string.h> + +#include "app/sql/statement.h" +#include "base/file_path.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "sqlite3.h" + +namespace sql { + +bool StatementID::operator<(const StatementID& other) const { + if (number_ != other.number_) + return number_ < other.number_; + return strcmp(str_, other.str_) < 0; +} + +Connection::StatementRef::StatementRef() + : connection_(NULL), + stmt_(NULL) { +} + +Connection::StatementRef::StatementRef(Connection* connection, + sqlite3_stmt* stmt) + : connection_(connection), + stmt_(stmt) { + connection_->StatementRefCreated(this); +} + +Connection::StatementRef::~StatementRef() { + if (connection_) + connection_->StatementRefDeleted(this); + Close(); +} + +void Connection::StatementRef::Close() { + if (stmt_) { + sqlite3_finalize(stmt_); + stmt_ = NULL; + } + connection_ = NULL; // The connection may be getting deleted. +} + +Connection::Connection() + : db_(NULL), + page_size_(0), + cache_size_(0), + exclusive_locking_(false), + transaction_nesting_(0), + needs_rollback_(false) { +} + +Connection::~Connection() { + Close(); +} + +bool Connection::Open(const FilePath& path) { +#if defined(OS_WIN) + return OpenInternal(WideToUTF8(path.value())); +#elif defined(OS_POSIX) + return OpenInternal(path.value()); +#endif +} + +bool Connection::OpenInMemory() { + return OpenInternal(":memory:"); +} + +void Connection::Close() { + statement_cache_.clear(); + DCHECK(open_statements_.empty()); + if (db_) { + sqlite3_close(db_); + db_ = NULL; + } +} + +// This function is a local change to sqlite3 which doesn't exist when one is +// using the system sqlite library. Thus, we stub it out here. +int sqlite3Preload(sqlite3* db) { + return 0; +} + +void Connection::Preload() { + if (!db_) { + NOTREACHED(); + return; + } + + // A statement must be open for the preload command to work. If the meta + // table doesn't exist, it probably means this is a new database and there + // is nothing to preload (so it's OK we do nothing). + if (!DoesTableExist("meta")) + return; + Statement dummy(GetUniqueStatement("SELECT * FROM meta")); + if (!dummy || !dummy.Step()) + return; + + sqlite3Preload(db_); +} + +bool Connection::BeginTransaction() { + if (needs_rollback_) { + DCHECK(transaction_nesting_ > 0); + + // When we're going to rollback, fail on this begin and don't actually + // mark us as entering the nested transaction. + return false; + } + + bool success = true; + if (!transaction_nesting_) { + needs_rollback_ = false; + + Statement begin(GetCachedStatement(SQL_FROM_HERE, "BEGIN TRANSACTION")); + if (!begin || !begin.Run()) + return false; + } + transaction_nesting_++; + return success; +} + +void Connection::RollbackTransaction() { + if (!transaction_nesting_) { + NOTREACHED() << "Rolling back a nonexistant transaction"; + return; + } + + transaction_nesting_--; + + if (transaction_nesting_ > 0) { + // Mark the outermost transaction as needing rollback. + needs_rollback_ = true; + return; + } + + DoRollback(); +} + +bool Connection::CommitTransaction() { + if (!transaction_nesting_) { + NOTREACHED() << "Rolling back a nonexistant transaction"; + return false; + } + transaction_nesting_--; + + if (transaction_nesting_ > 0) { + // Mark any nested transactions as failing after we've already got one. + return !needs_rollback_; + } + + if (needs_rollback_) { + DoRollback(); + return false; + } + + Statement commit(GetCachedStatement(SQL_FROM_HERE, "COMMIT")); + if (!commit) + return false; + return commit.Run(); +} + +bool Connection::Execute(const char* sql) { + if (!db_) + return false; + return sqlite3_exec(db_, sql, NULL, NULL, NULL) == SQLITE_OK; +} + +bool Connection::HasCachedStatement(const StatementID& id) const { + return statement_cache_.find(id) != statement_cache_.end(); +} + +scoped_refptr<Connection::StatementRef> Connection::GetCachedStatement( + const StatementID& id, + const char* sql) { + CachedStatementMap::iterator i = statement_cache_.find(id); + if (i != statement_cache_.end()) { + // Statement is in the cache. It should still be active (we're the only + // one invalidating cached statements, and we'll remove it from the cache + // if we do that. Make sure we reset it before giving out the cached one in + // case it still has some stuff bound. + DCHECK(i->second->is_valid()); + sqlite3_reset(i->second->stmt()); + return i->second; + } + + scoped_refptr<StatementRef> statement = GetUniqueStatement(sql); + if (statement->is_valid()) + statement_cache_[id] = statement; // Only cache valid statements. + return statement; +} + +scoped_refptr<Connection::StatementRef> Connection::GetUniqueStatement( + const char* sql) { + if (!db_) + return new StatementRef(this, NULL); // Return inactive statement. + + sqlite3_stmt* stmt = NULL; + if (sqlite3_prepare_v2(db_, sql, -1, &stmt, NULL) != SQLITE_OK) { + // Treat this as non-fatal, it can occur in a number of valid cases, and + // callers should be doing their own error handling. + DLOG(WARNING) << "SQL compile error " << GetErrorMessage(); + return new StatementRef(this, NULL); + } + return new StatementRef(this, stmt); +} + +bool Connection::DoesTableExist(const char* table_name) const { + // GetUniqueStatement can't be const since statements may modify the + // database, but we know ours doesn't modify it, so the cast is safe. + Statement statement(const_cast<Connection*>(this)->GetUniqueStatement( + "SELECT name FROM sqlite_master " + "WHERE type='table' AND name=?")); + if (!statement) + return false; + statement.BindString(0, table_name); + return statement.Step(); // Table exists if any row was returned. +} + +bool Connection::DoesColumnExist(const char* table_name, + const char* column_name) const { + std::string sql("PRAGMA TABLE_INFO("); + sql.append(table_name); + sql.append(")"); + + // Our SQL is non-mutating, so this cast is OK. + Statement statement(const_cast<Connection*>(this)->GetUniqueStatement( + sql.c_str())); + if (!statement) + return false; + + while (statement.Step()) { + if (!statement.ColumnString(1).compare(column_name)) + return true; + } + return false; +} + +int64 Connection::GetLastInsertRowId() const { + if (!db_) { + NOTREACHED(); + return 0; + } + return sqlite3_last_insert_rowid(db_); +} + +int Connection::GetLastChangeCount() const { + if (!db_) { + NOTREACHED(); + return 0; + } + return sqlite3_changes(db_); +} + +int Connection::GetErrorCode() const { + if (!db_) + return SQLITE_ERROR; + return sqlite3_errcode(db_); +} + +const char* Connection::GetErrorMessage() const { + if (!db_) + return "sql::Connection has no connection."; + return sqlite3_errmsg(db_); +} + +bool Connection::OpenInternal(const std::string& file_name) { + if (db_) { + NOTREACHED() << "sql::Connection is already open."; + return false; + } + + int err = sqlite3_open(file_name.c_str(), &db_); + if (err != SQLITE_OK) { + OnSqliteError(err, NULL); + db_ = NULL; + return false; + } + + if (page_size_ != 0) { + if (!Execute(StringPrintf("PRAGMA page_size=%d", page_size_).c_str())) + NOTREACHED() << "Could not set page size"; + } + + if (cache_size_ != 0) { + if (!Execute(StringPrintf("PRAGMA cache_size=%d", cache_size_).c_str())) + NOTREACHED() << "Could not set page size"; + } + + if (exclusive_locking_) { + if (!Execute("PRAGMA locking_mode=EXCLUSIVE")) + NOTREACHED() << "Could not set locking mode."; + } + + return true; +} + +void Connection::DoRollback() { + Statement rollback(GetCachedStatement(SQL_FROM_HERE, "ROLLBACK")); + if (rollback) + rollback.Run(); +} + +void Connection::StatementRefCreated(StatementRef* ref) { + DCHECK(open_statements_.find(ref) == open_statements_.end()); + open_statements_.insert(ref); +} + +void Connection::StatementRefDeleted(StatementRef* ref) { + StatementRefSet::iterator i = open_statements_.find(ref); + if (i == open_statements_.end()) + NOTREACHED(); + else + open_statements_.erase(i); +} + +void Connection::ClearCache() { + statement_cache_.clear(); + + // The cache clear will get most statements. There may be still be references + // to some statements that are held by others (including one-shot statements). + // This will deactivate them so they can't be used again. + for (StatementRefSet::iterator i = open_statements_.begin(); + i != open_statements_.end(); ++i) + (*i)->Close(); +} + +int Connection::OnSqliteError(int err, sql::Statement *stmt) { + if (error_delegate_.get()) + return error_delegate_->OnError(err, this, stmt); + // The default handling is to assert on debug and to ignore on release. + NOTREACHED() << GetErrorMessage(); + return err; +} + +} // namespace sql diff --git a/android/app/sql/connection.h b/android/app/sql/connection.h new file mode 100644 index 0000000..6927c89 --- /dev/null +++ b/android/app/sql/connection.h @@ -0,0 +1,376 @@ +// 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 APP_SQL_CONNECTION_H_ +#define APP_SQL_CONNECTION_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/ref_counted.h" + +class FilePath; +struct sqlite3; +struct sqlite3_stmt; + +namespace sql { + +class Statement; + +// Uniquely identifies a statement. There are two modes of operation: +// +// - In the most common mode, you will use the source file and line number to +// identify your statement. This is a convienient way to get uniqueness for +// a statement that is only used in one place. Use the SQL_FROM_HERE macro +// to generate a StatementID. +// +// - In the "custom" mode you may use the statement from different places or +// need to manage it yourself for whatever reason. In this case, you should +// make up your own unique name and pass it to the StatementID. This name +// must be a static string, since this object only deals with pointers and +// assumes the underlying string doesn't change or get deleted. +// +// This object is copyable and assignable using the compiler-generated +// operator= and copy constructor. +class StatementID { + public: + // Creates a uniquely named statement with the given file ane line number. + // Normally you will use SQL_FROM_HERE instead of calling yourself. + StatementID(const char* file, int line) + : number_(line), + str_(file) { + } + + // Creates a uniquely named statement with the given user-defined name. + explicit StatementID(const char* unique_name) + : number_(-1), + str_(unique_name) { + } + + // This constructor is unimplemented and will generate a linker error if + // called. It is intended to try to catch people dynamically generating + // a statement name that will be deallocated and will cause a crash later. + // All strings must be static and unchanging! + explicit StatementID(const std::string& dont_ever_do_this); + + // We need this to insert into our map. + bool operator<(const StatementID& other) const; + + private: + int number_; + const char* str_; +}; + +#define SQL_FROM_HERE sql::StatementID(__FILE__, __LINE__) + +class Connection; + +// ErrorDelegate defines the interface to implement error handling and recovery +// for sqlite operations. This allows the rest of the classes to return true or +// false while the actual error code and causing statement are delivered using +// the OnError() callback. +// The tipical usage is to centralize the code designed to handle database +// corruption, low-level IO errors or locking violations. +class ErrorDelegate : public base::RefCounted<ErrorDelegate> { + public: + // |error| is an sqlite result code as seen in sqlite\preprocessed\sqlite3.h + // |connection| is db connection where the error happened and |stmt| is + // our best guess at the statement that triggered the error. Do not store + // these pointers. + // + // |stmt| MAY BE NULL if there is no statement causing the problem (i.e. on + // initialization). + // + // If the error condition has been fixed an the original statement succesfuly + // re-tried then returning SQLITE_OK is appropiate; otherwise is recomended + // that you return the original |error| or the appropiae error code. + virtual int OnError(int error, Connection* connection, Statement* stmt) = 0; + + protected: + friend class base::RefCounted<ErrorDelegate>; + + virtual ~ErrorDelegate() {} +}; + +class Connection { + private: + class StatementRef; // Forward declaration, see real one below. + + public: + // The database is opened by calling Open[InMemory](). Any uncommitted + // transactions will be rolled back when this object is deleted. + Connection(); + ~Connection(); + + // Pre-init configuration ---------------------------------------------------- + + // Sets the page size that will be used when creating a new database. This + // must be called before Init(), and will only have an effect on new + // databases. + // + // From sqlite.org: "The page size must be a power of two greater than or + // equal to 512 and less than or equal to SQLITE_MAX_PAGE_SIZE. The maximum + // value for SQLITE_MAX_PAGE_SIZE is 32768." + void set_page_size(int page_size) { page_size_ = page_size; } + + // Sets the number of pages that will be cached in memory by sqlite. The + // total cache size in bytes will be page_size * cache_size. This must be + // called before Open() to have an effect. + void set_cache_size(int cache_size) { cache_size_ = cache_size; } + + // Call to put the database in exclusive locking mode. There is no "back to + // normal" flag because of some additional requirements sqlite puts on this + // transaition (requires another access to the DB) and because we don't + // actually need it. + // + // Exclusive mode means that the database is not unlocked at the end of each + // transaction, which means there may be less time spent initializing the + // next transaction because it doesn't have to re-aquire locks. + // + // This must be called before Open() to have an effect. + void set_exclusive_locking() { exclusive_locking_ = true; } + + // Sets the object that will handle errors. Recomended that it should be set + // before calling Open(). If not set, the default is to ignore errors on + // release and assert on debug builds. + void set_error_delegate(ErrorDelegate* delegate) { + error_delegate_ = delegate; + } + + // Initialization ------------------------------------------------------------ + + // Initializes the SQL connection for the given file, returning true if the + // file could be opened. You can call this or OpenInMemory. + bool Open(const FilePath& path); + + // Initializes the SQL connection for a temporary in-memory database. There + // will be no associated file on disk, and the initial database will be + // empty. You can call this or Open. + bool OpenInMemory(); + + // Returns trie if the database has been successfully opened. + bool is_open() const { return !!db_; } + + // Closes the database. This is automatically performed on destruction for + // you, but this allows you to close the database early. You must not call + // any other functions after closing it. It is permissable to call Close on + // an uninitialized or already-closed database. + void Close(); + + // Pre-loads the first <cache-size> pages into the cache from the file. + // If you expect to soon use a substantial portion of the database, this + // is much more efficient than allowing the pages to be populated organically + // since there is no per-page hard drive seeking. If the file is larger than + // the cache, the last part that doesn't fit in the cache will be brought in + // organically. + // + // This function assumes your class is using a meta table on the current + // database, as it openes a transaction on the meta table to force the + // database to be initialized. You should feel free to initialize the meta + // table after calling preload since the meta table will already be in the + // database if it exists, and if it doesn't exist, the database won't + // generally exist either. + void Preload(); + + // Transactions -------------------------------------------------------------- + + // Transaction management. We maintain a virtual transaction stack to emulate + // nested transactions since sqlite can't do nested transactions. The + // limitation is you can't roll back a sub transaction: if any transaction + // fails, all transactions open will also be rolled back. Any nested + // transactions after one has rolled back will return fail for Begin(). If + // Begin() fails, you must not call Commit or Rollback(). + // + // Normally you should use sql::Transaction to manage a transaction, which + // will scope it to a C++ context. + bool BeginTransaction(); + void RollbackTransaction(); + bool CommitTransaction(); + + // Returns the current transaction nesting, which will be 0 if there are + // no open transactions. + int transaction_nesting() const { return transaction_nesting_; } + + // Statements ---------------------------------------------------------------- + + // Executes the given SQL string, returning true on success. This is + // normally used for simple, 1-off statements that don't take any bound + // parameters and don't return any data (e.g. CREATE TABLE). + bool Execute(const char* sql); + + // Returns true if we have a statement with the given identifier already + // cached. This is normally not necessary to call, but can be useful if the + // caller has to dynamically build up SQL to avoid doing so if it's already + // cached. + bool HasCachedStatement(const StatementID& id) const; + + // Returns a statement for the given SQL using the statement cache. It can + // take a nontrivial amount of work to parse and compile a statement, so + // keeping commonly-used ones around for future use is important for + // performance. + // + // The SQL may have an error, so the caller must check validity of the + // statement before using it. + // + // The StatementID and the SQL must always correspond to one-another. The + // ID is the lookup into the cache, so crazy things will happen if you use + // different SQL with the same ID. + // + // You will normally use the SQL_FROM_HERE macro to generate a statement + // ID associated with the current line of code. This gives uniqueness without + // you having to manage unique names. See StatementID above for more. + // + // Example: + // sql::Statement stmt(connection_.GetCachedStatement( + // SQL_FROM_HERE, "SELECT * FROM foo")); + // if (!stmt) + // return false; // Error creating statement. + scoped_refptr<StatementRef> GetCachedStatement(const StatementID& id, + const char* sql); + + // Returns a non-cached statement for the given SQL. Use this for SQL that + // is only executed once or only rarely (there is overhead associated with + // keeping a statement cached). + // + // See GetCachedStatement above for examples and error information. + scoped_refptr<StatementRef> GetUniqueStatement(const char* sql); + + // Info querying ------------------------------------------------------------- + + // Returns true if the given table exists. + bool DoesTableExist(const char* table_name) const; + + // Returns true if a column with the given name exists in the given table. + bool DoesColumnExist(const char* table_name, const char* column_name) const; + + // Returns sqlite's internal ID for the last inserted row. Valid only + // immediately after an insert. + int64 GetLastInsertRowId() const; + + // Returns sqlite's count of the number of rows modified by the last + // statement executed. Will be 0 if no statement has executed or the database + // is closed. + int GetLastChangeCount() const; + + // Errors -------------------------------------------------------------------- + + // Returns the error code associated with the last sqlite operation. + int GetErrorCode() const; + + // Returns a pointer to a statically allocated string associated with the + // last sqlite operation. + const char* GetErrorMessage() const; + + private: + // Statement access StatementRef which we don't want to expose to erverybody + // (they should go through Statement). + friend class Statement; + + // Internal initialize function used by both Init and InitInMemory. The file + // name is always 8 bits since we want to use the 8-bit version of + // sqlite3_open. The string can also be sqlite's special ":memory:" string. + bool OpenInternal(const std::string& file_name); + + // A StatementRef is a refcounted wrapper around a sqlite statement pointer. + // Refcounting allows us to give these statements out to sql::Statement + // objects while also optionally maintaining a cache of compiled statements + // by just keeping a refptr to these objects. + // + // A statement ref can be valid, in which case it can be used, or invalid to + // indicate that the statement hasn't been created yet, has an error, or has + // been destroyed. + // + // The Connection may revoke a StatementRef in some error cases, so callers + // should always check validity before using. + class StatementRef : public base::RefCounted<StatementRef> { + public: + // Default constructor initializes to an invalid statement. + StatementRef(); + StatementRef(Connection* connection, sqlite3_stmt* stmt); + + // When true, the statement can be used. + bool is_valid() const { return !!stmt_; } + + // If we've not been linked to a connection, this will be NULL. Guaranteed + // non-NULL when is_valid(). + Connection* connection() const { return connection_; } + + // Returns the sqlite statement if any. If the statement is not active, + // this will return NULL. + sqlite3_stmt* stmt() const { return stmt_; } + + // Destroys the compiled statement and marks it NULL. The statement will + // no longer be active. + void Close(); + + private: + friend class base::RefCounted<StatementRef>; + + ~StatementRef(); + + Connection* connection_; + sqlite3_stmt* stmt_; + + DISALLOW_COPY_AND_ASSIGN(StatementRef); + }; + friend class StatementRef; + + // Executes a rollback statement, ignoring all transaction state. Used + // internally in the transaction management code. + void DoRollback(); + + // Called by a StatementRef when it's being created or destroyed. See + // open_statements_ below. + void StatementRefCreated(StatementRef* ref); + void StatementRefDeleted(StatementRef* ref); + + // Frees all cached statements from statement_cache_. + void ClearCache(); + + // Called by Statement objects when an sqlite function returns an error. + // The return value is the error code reflected back to client code. + int OnSqliteError(int err, Statement* stmt); + + // The actual sqlite database. Will be NULL before Init has been called or if + // Init resulted in an error. + sqlite3* db_; + + // Parameters we'll configure in sqlite before doing anything else. Zero means + // use the default value. + int page_size_; + int cache_size_; + bool exclusive_locking_; + + // All cached statements. Keeping a reference to these statements means that + // they'll remain active. + typedef std::map<StatementID, scoped_refptr<StatementRef> > + CachedStatementMap; + CachedStatementMap statement_cache_; + + // A list of all StatementRefs we've given out. Each ref must register with + // us when it's created or destroyed. This allows us to potentially close + // any open statements when we encounter an error. + typedef std::set<StatementRef*> StatementRefSet; + StatementRefSet open_statements_; + + // Number of currently-nested transactions. + int transaction_nesting_; + + // True if any of the currently nested transactions have been rolled back. + // When we get to the outermost transaction, this will determine if we do + // a rollback instead of a commit. + bool needs_rollback_; + + // This object handles errors resulting from all forms of executing sqlite + // commands or statements. It can be null which means default handling. + scoped_refptr<ErrorDelegate> error_delegate_; + + DISALLOW_COPY_AND_ASSIGN(Connection); +}; + +} // namespace sql + +#endif // APP_SQL_CONNECTION_H_ diff --git a/android/app/sql/meta_table.cc b/android/app/sql/meta_table.cc new file mode 100644 index 0000000..4d7c5e1 --- /dev/null +++ b/android/app/sql/meta_table.cc @@ -0,0 +1,149 @@ +// 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 "app/sql/meta_table.h" + +#include "app/sql/connection.h" +#include "app/sql/statement.h" +#include "base/logging.h" +#include "base/string_util.h" + +namespace sql { + +// Key used in our meta table for version numbers. +static const char kVersionKey[] = "version"; +static const char kCompatibleVersionKey[] = "last_compatible_version"; + +// static +bool MetaTable::DoesTableExist(sql::Connection* db) { + DCHECK(db); + return db->DoesTableExist("meta"); +} + +MetaTable::MetaTable() : db_(NULL) { +} + +MetaTable::~MetaTable() { +} + +bool MetaTable::Init(Connection* db, int version, int compatible_version) { + DCHECK(!db_ && db); + db_ = db; + if (!DoesTableExist(db)) { + if (!db_->Execute("CREATE TABLE meta" + "(key LONGVARCHAR NOT NULL UNIQUE PRIMARY KEY," + "value LONGVARCHAR)")) + return false; + + // Note: there is no index over the meta table. We currently only have a + // couple of keys, so it doesn't matter. If we start storing more stuff in + // there, we should create an index. + SetVersionNumber(version); + SetCompatibleVersionNumber(compatible_version); + } + return true; +} + +bool MetaTable::SetValue(const char* key, const std::string& value) { + Statement s; + if (!PrepareSetStatement(&s, key)) + return false; + s.BindString(1, value); + return s.Run(); +} + +bool MetaTable::GetValue(const char* key, std::string* value) { + Statement s; + if (!PrepareGetStatement(&s, key)) + return false; + + *value = s.ColumnString(0); + return true; +} + +bool MetaTable::SetValue(const char* key, int value) { + Statement s; + if (!PrepareSetStatement(&s, key)) + return false; + + s.BindInt(1, value); + return s.Run(); +} + +bool MetaTable::GetValue(const char* key, int* value) { + Statement s; + if (!PrepareGetStatement(&s, key)) + return false; + + *value = s.ColumnInt(0); + return true; +} + +bool MetaTable::SetValue(const char* key, int64 value) { + Statement s; + if (!PrepareSetStatement(&s, key)) + return false; + s.BindInt64(1, value); + return s.Run(); +} + +bool MetaTable::GetValue(const char* key, int64* value) { + Statement s; + if (!PrepareGetStatement(&s, key)) + return false; + + *value = s.ColumnInt64(0); + return true; +} + +void MetaTable::SetVersionNumber(int version) { + SetValue(kVersionKey, version); +} + +int MetaTable::GetVersionNumber() { + int version = 0; + if (!GetValue(kVersionKey, &version)) + return 0; + return version; +} + +void MetaTable::SetCompatibleVersionNumber(int version) { + SetValue(kCompatibleVersionKey, version); +} + +int MetaTable::GetCompatibleVersionNumber() { + int version = 0; + if (!GetValue(kCompatibleVersionKey, &version)) + return 0; + return version; +} + +bool MetaTable::PrepareSetStatement(Statement* statement, const char* key) { + DCHECK(db_ && statement); + statement->Assign(db_->GetCachedStatement(SQL_FROM_HERE, + "INSERT OR REPLACE INTO meta (key,value) VALUES (?,?)")); + if (!statement->is_valid()) { + NOTREACHED() << db_->GetErrorMessage(); + return false; + } + statement->BindCString(0, key); + return true; +} + +bool MetaTable::PrepareGetStatement(Statement* statement, const char* key) { + DCHECK(db_ && statement); + statement->Assign(db_->GetCachedStatement(SQL_FROM_HERE, + "SELECT value FROM meta WHERE key=?")); + if (!statement->is_valid()) { + NOTREACHED() << db_->GetErrorMessage(); + return false; + } + statement->BindCString(0, key); + if (!statement->Step()) + return false; + return true; +} + +} // namespace sql + diff --git a/android/app/sql/meta_table.h b/android/app/sql/meta_table.h new file mode 100644 index 0000000..ae78e11 --- /dev/null +++ b/android/app/sql/meta_table.h @@ -0,0 +1,79 @@ +// 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 APP_SQL_META_TABLE_H_ +#define APP_SQL_META_TABLE_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace sql { + +class Connection; +class Statement; + +class MetaTable { + public: + // Returns true if the 'meta' table exists. + static bool DoesTableExist(Connection* db); + + MetaTable(); + ~MetaTable(); + + // Initializes the MetaTableHelper, creating the meta table if necessary. For + // new tables, it will initialize the version number to |version| and the + // compatible version number to |compatible_version|. + bool Init(Connection* db, int version, int compatible_version); + + // The version number of the database. This should be the version number of + // the creator of the file. The version number will be 0 if there is no + // previously set version number. + // + // See also Get/SetCompatibleVersionNumber(). + void SetVersionNumber(int version); + int GetVersionNumber(); + + // The compatible version number is the lowest version of the code that this + // database can be read by. If there are minor changes or additions, old + // versions of the code can still work with the database without failing. + // + // For example, if an optional column is added to a table in version 3, the + // new code will set the version to 3, and the compatible version to 2, since + // the code expecting version 2 databases can still read and write the table. + // + // Rule of thumb: check the version number when you're upgrading, but check + // the compatible version number to see if you can read the file at all. If + // it's larger than you code is expecting, fail. + // + // The compatible version number will be 0 if there is no previously set + // compatible version number. + void SetCompatibleVersionNumber(int version); + int GetCompatibleVersionNumber(); + + // Set the given arbitrary key with the given data. Returns true on success. + bool SetValue(const char* key, const std::string& value); + bool SetValue(const char* key, int value); + bool SetValue(const char* key, int64 value); + + // Retrieves the value associated with the given key. This will use sqlite's + // type conversion rules. It will return true on success. + bool GetValue(const char* key, std::string* value); + bool GetValue(const char* key, int* value); + bool GetValue(const char* key, int64* value); + + private: + // Conveniences to prepare the two types of statements used by + // MetaTableHelper. + bool PrepareSetStatement(Statement* statement, const char* key); + bool PrepareGetStatement(Statement* statement, const char* key); + + Connection* db_; + + DISALLOW_COPY_AND_ASSIGN(MetaTable); +}; + +} // namespace sql + +#endif // APP_SQL_META_TABLE_H_ diff --git a/android/app/sql/statement.cc b/android/app/sql/statement.cc new file mode 100644 index 0000000..0a63d5c --- /dev/null +++ b/android/app/sql/statement.cc @@ -0,0 +1,270 @@ +// Copyright (c) 2010 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 "app/sql/statement.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "sqlite3.h" + +namespace sql { + +// This empty constructor initializes our reference with an empty one so that +// we don't have to NULL-check the ref_ to see if the statement is valid: we +// only have to check the ref's validity bit. +Statement::Statement() + : ref_(new Connection::StatementRef), + succeeded_(false) { +} + +Statement::Statement(scoped_refptr<Connection::StatementRef> ref) + : ref_(ref), + succeeded_(false) { +} + +Statement::~Statement() { + // Free the resources associated with this statement. We assume there's only + // one statement active for a given sqlite3_stmt at any time, so this won't + // mess with anything. + Reset(); +} + +void Statement::Assign(scoped_refptr<Connection::StatementRef> ref) { + Reset(); + ref_ = ref; +} + +bool Statement::Run() { + if (!is_valid()) + return false; + return CheckError(sqlite3_step(ref_->stmt())) == SQLITE_DONE; +} + +bool Statement::Step() { + if (!is_valid()) + return false; + return CheckError(sqlite3_step(ref_->stmt())) == SQLITE_ROW; +} + +void Statement::Reset() { + if (is_valid()) { + // We don't call CheckError() here because sqlite3_reset() returns + // the last error that Step() caused thereby generating a second + // spurious error callback. + sqlite3_clear_bindings(ref_->stmt()); + sqlite3_reset(ref_->stmt()); + } + succeeded_ = false; +} + +bool Statement::Succeeded() const { + if (!is_valid()) + return false; + return succeeded_; +} + +bool Statement::BindNull(int col) { + if (is_valid()) { + int err = CheckError(sqlite3_bind_null(ref_->stmt(), col + 1)); + return err == SQLITE_OK; + } + return false; +} + +bool Statement::BindBool(int col, bool val) { + return BindInt(col, val ? 1 : 0); +} + +bool Statement::BindInt(int col, int val) { + if (is_valid()) { + int err = CheckError(sqlite3_bind_int(ref_->stmt(), col + 1, val)); + return err == SQLITE_OK; + } + return false; +} + +bool Statement::BindInt64(int col, int64 val) { + if (is_valid()) { + int err = CheckError(sqlite3_bind_int64(ref_->stmt(), col + 1, val)); + return err == SQLITE_OK; + } + return false; +} + +bool Statement::BindDouble(int col, double val) { + if (is_valid()) { + int err = CheckError(sqlite3_bind_double(ref_->stmt(), col + 1, val)); + return err == SQLITE_OK; + } + return false; +} + +bool Statement::BindCString(int col, const char* val) { + if (is_valid()) { + int err = CheckError(sqlite3_bind_text(ref_->stmt(), col + 1, val, -1, + SQLITE_TRANSIENT)); + return err == SQLITE_OK; + } + return false; +} + +bool Statement::BindString(int col, const std::string& val) { + if (is_valid()) { + int err = CheckError(sqlite3_bind_text(ref_->stmt(), col + 1, val.data(), + val.size(), SQLITE_TRANSIENT)); + return err == SQLITE_OK; + } + return false; +} + +bool Statement::BindString16(int col, const string16& value) { + return BindString(col, UTF16ToUTF8(value)); +} + +bool Statement::BindBlob(int col, const void* val, int val_len) { + if (is_valid()) { + int err = CheckError(sqlite3_bind_blob(ref_->stmt(), col + 1, + val, val_len, SQLITE_TRANSIENT)); + return err == SQLITE_OK; + } + return false; +} + +int Statement::ColumnCount() const { + if (!is_valid()) { + NOTREACHED(); + return 0; + } + return sqlite3_column_count(ref_->stmt()); +} + +ColType Statement::ColumnType(int col) const { + // Verify that our enum matches sqlite's values. + COMPILE_ASSERT(COLUMN_TYPE_INTEGER == SQLITE_INTEGER, integer_no_match); + COMPILE_ASSERT(COLUMN_TYPE_FLOAT == SQLITE_FLOAT, float_no_match); + COMPILE_ASSERT(COLUMN_TYPE_TEXT == SQLITE_TEXT, integer_no_match); + COMPILE_ASSERT(COLUMN_TYPE_BLOB == SQLITE_BLOB, blob_no_match); + COMPILE_ASSERT(COLUMN_TYPE_NULL == SQLITE_NULL, null_no_match); + + return static_cast<ColType>(sqlite3_column_type(ref_->stmt(), col)); +} + +bool Statement::ColumnBool(int col) const { + return !!ColumnInt(col); +} + +int Statement::ColumnInt(int col) const { + if (!is_valid()) { + NOTREACHED(); + return 0; + } + return sqlite3_column_int(ref_->stmt(), col); +} + +int64 Statement::ColumnInt64(int col) const { + if (!is_valid()) { + NOTREACHED(); + return 0; + } + return sqlite3_column_int64(ref_->stmt(), col); +} + +double Statement::ColumnDouble(int col) const { + if (!is_valid()) { + NOTREACHED(); + return 0; + } + return sqlite3_column_double(ref_->stmt(), col); +} + +std::string Statement::ColumnString(int col) const { + if (!is_valid()) { + NOTREACHED(); + return ""; + } + const char* str = reinterpret_cast<const char*>( + sqlite3_column_text(ref_->stmt(), col)); + int len = sqlite3_column_bytes(ref_->stmt(), col); + + std::string result; + if (str && len > 0) + result.assign(str, len); + return result; +} + +string16 Statement::ColumnString16(int col) const { + if (!is_valid()) { + NOTREACHED(); + return string16(); + } + std::string s = ColumnString(col); + return !s.empty() ? UTF8ToUTF16(s) : string16(); +} + +int Statement::ColumnByteLength(int col) const { + if (!is_valid()) { + NOTREACHED(); + return 0; + } + return sqlite3_column_bytes(ref_->stmt(), col); +} + +const void* Statement::ColumnBlob(int col) const { + if (!is_valid()) { + NOTREACHED(); + return NULL; + } + + return sqlite3_column_blob(ref_->stmt(), col); +} + +bool Statement::ColumnBlobAsString(int col, std::string* blob) { + if (!is_valid()) { + NOTREACHED(); + return false; + } + const void* p = ColumnBlob(col); + size_t len = ColumnByteLength(col); + blob->resize(len); + if (blob->size() != len) { + return false; + } + blob->assign(reinterpret_cast<const char*>(p), len); + return true; +} + +void Statement::ColumnBlobAsVector(int col, std::vector<char>* val) const { + val->clear(); + if (!is_valid()) { + NOTREACHED(); + return; + } + + const void* data = sqlite3_column_blob(ref_->stmt(), col); + int len = sqlite3_column_bytes(ref_->stmt(), col); + if (data && len > 0) { + val->resize(len); + memcpy(&(*val)[0], data, len); + } +} + +void Statement::ColumnBlobAsVector( + int col, + std::vector<unsigned char>* val) const { + ColumnBlobAsVector(col, reinterpret_cast< std::vector<char>* >(val)); +} + +const char* Statement::GetSQLStatement() { + return sqlite3_sql(ref_->stmt()); +} + +int Statement::CheckError(int err) { + // Please don't add DCHECKs here, OnSqliteError() already has them. + succeeded_ = (err == SQLITE_OK || err == SQLITE_ROW || err == SQLITE_DONE); + if (!succeeded_ && is_valid()) + return ref_->connection()->OnSqliteError(err, this); + return err; +} + +} // namespace sql diff --git a/android/app/sql/statement.h b/android/app/sql/statement.h new file mode 100644 index 0000000..0fbbfba --- /dev/null +++ b/android/app/sql/statement.h @@ -0,0 +1,166 @@ +// Copyright (c) 2010 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 APP_SQL_STATEMENT_H_ +#define APP_SQL_STATEMENT_H_ + +#include <string> +#include <vector> + +#include "app/sql/connection.h" +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/string16.h" + +namespace sql { + +// Possible return values from ColumnType in a statement. These should match +// the values in sqlite3.h. +enum ColType { + COLUMN_TYPE_INTEGER = 1, + COLUMN_TYPE_FLOAT = 2, + COLUMN_TYPE_TEXT = 3, + COLUMN_TYPE_BLOB = 4, + COLUMN_TYPE_NULL = 5, +}; + +// Normal usage: +// sql::Statement s(connection_.GetUniqueStatement(...)); +// if (!s) // You should check for errors before using the statement. +// return false; +// +// s.BindInt(0, a); +// if (s.Step()) +// return s.ColumnString(0); +// +// Step() and Run() just return true to signal success. If you want to handle +// specific errors such as database corruption, install an error handler in +// in the connection object using set_error_delegate(). +class Statement { + public: + // Creates an uninitialized statement. The statement will be invalid until + // you initialize it via Assign. + Statement(); + + explicit Statement(scoped_refptr<Connection::StatementRef> ref); + ~Statement(); + + // Initializes this object with the given statement, which may or may not + // be valid. Use is_valid() to check if it's OK. + void Assign(scoped_refptr<Connection::StatementRef> ref); + + // Returns true if the statement can be executed. All functions can still + // be used if the statement is invalid, but they will return failure or some + // default value. This is because the statement can become invalid in the + // middle of executing a command if there is a serioud error and the database + // has to be reset. + bool is_valid() const { return ref_->is_valid(); } + + // These operators allow conveniently checking if the statement is valid + // or not. See the pattern above for an example. + operator bool() const { return is_valid(); } + bool operator!() const { return !is_valid(); } + + // Running ------------------------------------------------------------------- + + // Executes the statement, returning true on success. This is like Step but + // for when there is no output, like an INSERT statement. + bool Run(); + + // Executes the statement, returning true if there is a row of data returned. + // You can keep calling Step() until it returns false to iterate through all + // the rows in your result set. + // + // When Step returns false, the result is either that there is no more data + // or there is an error. This makes it most convenient for loop usage. If you + // need to disambiguate these cases, use Succeeded(). + // + // Typical example: + // while (s.Step()) { + // ... + // } + // return s.Succeeded(); + bool Step(); + + // Resets the statement to its initial condition. This includes clearing all + // the bound variables and any current result row. + void Reset(); + + // Returns true if the last executed thing in this statement succeeded. If + // there was no last executed thing or the statement is invalid, this will + // return false. + bool Succeeded() const; + + // Binding ------------------------------------------------------------------- + + // These all take a 0-based argument index and return true on failure. You + // may not always care about the return value (they'll DCHECK if they fail). + // The main thing you may want to check is when binding large blobs or + // strings there may be out of memory. + bool BindNull(int col); + bool BindBool(int col, bool val); + bool BindInt(int col, int val); + bool BindInt64(int col, int64 val); + bool BindDouble(int col, double val); + bool BindCString(int col, const char* val); + bool BindString(int col, const std::string& val); + bool BindString16(int col, const string16& value); + bool BindBlob(int col, const void* value, int value_len); + + // Retrieving ---------------------------------------------------------------- + + // Returns the number of output columns in the result. + int ColumnCount() const; + + // Returns the type associated with the given column. + // + // Watch out: the type may be undefined if you've done something to cause a + // "type conversion." This means requesting the value of a column of a type + // where that type is not the native type. For safety, call ColumnType only + // on a column before getting the value out in any way. + ColType ColumnType(int col) const; + + // These all take a 0-based argument index. + bool ColumnBool(int col) const; + int ColumnInt(int col) const; + int64 ColumnInt64(int col) const; + double ColumnDouble(int col) const; + std::string ColumnString(int col) const; + string16 ColumnString16(int col) const; + + // When reading a blob, you can get a raw pointer to the underlying data, + // along with the length, or you can just ask us to copy the blob into a + // vector. Danger! ColumnBlob may return NULL if there is no data! + int ColumnByteLength(int col) const; + const void* ColumnBlob(int col) const; + bool ColumnBlobAsString(int col, std::string* blob); + void ColumnBlobAsVector(int col, std::vector<char>* val) const; + void ColumnBlobAsVector(int col, std::vector<unsigned char>* val) const; + + // Diagnostics -------------------------------------------------------------- + + // Returns the original text of sql statement. Do not keep a pointer to it. + const char* GetSQLStatement(); + + private: + // This is intended to check for serious errors and report them to the + // connection object. It takes a sqlite error code, and returns the same + // code. Currently this function just updates the succeeded flag, but will be + // enhanced in the future to do the notification. + int CheckError(int err); + + // The actual sqlite statement. This may be unique to us, or it may be cached + // by the connection, which is why it's refcounted. This pointer is + // guaranteed non-NULL. + scoped_refptr<Connection::StatementRef> ref_; + + // See Succeeded() for what this holds. + bool succeeded_; + + DISALLOW_COPY_AND_ASSIGN(Statement); +}; + +} // namespace sql + +#endif // APP_SQL_STATEMENT_H_ diff --git a/android/app/sql/transaction.cc b/android/app/sql/transaction.cc new file mode 100644 index 0000000..79a198b --- /dev/null +++ b/android/app/sql/transaction.cc @@ -0,0 +1,51 @@ +// 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 "app/sql/transaction.h" + +#include "app/sql/connection.h" +#include "base/logging.h" + +namespace sql { + +Transaction::Transaction(Connection* connection) + : connection_(connection), + is_open_(false) { +} + +Transaction::~Transaction() { + if (is_open_) + connection_->RollbackTransaction(); +} + +bool Transaction::Begin() { + if (is_open_) { + NOTREACHED() << "Beginning a transaction twice!"; + return false; + } + is_open_ = connection_->BeginTransaction(); + return is_open_; +} + +void Transaction::Rollback() { + if (!is_open_) { + NOTREACHED() << "Attempting to roll back a nonexistant transaction. " + << "Did you remember to call Begin() and check its return?"; + return; + } + is_open_ = false; + connection_->RollbackTransaction(); +} + +bool Transaction::Commit() { + if (!is_open_) { + NOTREACHED() << "Attempting to commit a nonexistant transaction. " + << "Did you remember to call Begin() and check its return?"; + return false; + } + is_open_ = false; + return connection_->CommitTransaction(); +} + +} // namespace sql diff --git a/android/app/sql/transaction.h b/android/app/sql/transaction.h new file mode 100644 index 0000000..70741d1 --- /dev/null +++ b/android/app/sql/transaction.h @@ -0,0 +1,56 @@ +// 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 APP_SQL_TRANSACTION_H_ +#define APP_SQL_TRANSACTION_H_ + +#include "base/basictypes.h" + +namespace sql { + +class Connection; + +class Transaction { + public: + // Creates the scoped transaction object. You MUST call Begin() to begin the + // transaction. If you have begun a transaction and not committed it, the + // constructor will roll back the transaction. If you want to commit, you + // need to manually call Commit before this goes out of scope. + explicit Transaction(Connection* connection); + ~Transaction(); + + // Returns true when there is a transaction that has been successfully begun. + bool is_open() const { return is_open_; } + + // Begins the transaction. This uses the default sqlite "deferred" transaction + // type, which means that the DB lock is lazily acquired the next time the + // database is accessed, not in the begin transaction command. + // + // Returns false on failure. Note that if this fails, you shouldn't do + // anything you expect to be actually transactional, because it won't be! + bool Begin(); + + // Rolls back the transaction. This will happen automatically if you do + // nothing when the transaction goes out of scope. + void Rollback(); + + // Commits the transaction, returning true on success. This will return + // false if sqlite could not commit it, or if another transaction in the + // same outermost transaction has been rolled back (which necessitates a + // rollback of all transactions in that outermost one). + bool Commit(); + + private: + Connection* connection_; + + // True when the transaction is open, false when it's already been committed + // or rolled back. + bool is_open_; + + DISALLOW_COPY_AND_ASSIGN(Transaction); +}; + +} // namespace sql + +#endif // APP_SQL_TRANSACTION_H_ diff --git a/android/app/sqlite_persistent_cookie_store.cc b/android/app/sqlite_persistent_cookie_store.cc new file mode 100644 index 0000000..d8666d1 --- /dev/null +++ b/android/app/sqlite_persistent_cookie_store.cc @@ -0,0 +1,473 @@ +// Copyright (c) 2010 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 "app/sqlite_persistent_cookie_store.h" + +#include <list> + +#include "app/sql/statement.h" +#include "app/sql/transaction.h" +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/thread.h" + +base::Thread* getDbThread() +{ + static base::Thread* dbThread = NULL; + if (dbThread && dbThread->IsRunning()) + return dbThread; + + if (!dbThread) + dbThread = new base::Thread("db"); + + if (!dbThread) + return NULL; + + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_DEFAULT; + if (!dbThread->StartWithOptions(options)) { + delete dbThread; + dbThread = NULL; + } + + return dbThread; +} + +using base::Time; + +// This class is designed to be shared between any calling threads and the +// database thread. It batches operations and commits them on a timer. +class SQLitePersistentCookieStore::Backend + : public base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend> { + public: + // The passed database pointer must be already-initialized. This object will + // take ownership. + explicit Backend(sql::Connection* db) + : db_(db), + num_pending_(0) { + DCHECK(db_) << "Database must exist."; + } + + // Batch a cookie addition. + void AddCookie(const std::string& key, + const net::CookieMonster::CanonicalCookie& cc); + + // Batch a cookie access time update. + void UpdateCookieAccessTime(const net::CookieMonster::CanonicalCookie& cc); + + // Batch a cookie deletion. + void DeleteCookie(const net::CookieMonster::CanonicalCookie& cc); + + // Commit any pending operations and close the database. This must be called + // before the object is destructed. + void Close(); + + private: + friend class base::RefCountedThreadSafe<SQLitePersistentCookieStore::Backend>; + + // You should call Close() before destructing this object. + ~Backend() { + DCHECK(!db_) << "Close should have already been called."; + DCHECK(num_pending_ == 0 && pending_.empty()); + } + + class PendingOperation { + public: + typedef enum { + COOKIE_ADD, + COOKIE_UPDATEACCESS, + COOKIE_DELETE, + } OperationType; + + PendingOperation(OperationType op, + const std::string& key, + const net::CookieMonster::CanonicalCookie& cc) + : op_(op), key_(key), cc_(cc) { } + + OperationType op() const { return op_; } + const std::string& key() const { return key_; } + const net::CookieMonster::CanonicalCookie& cc() const { return cc_; } + + private: + OperationType op_; + std::string key_; // Only used for OP_ADD + net::CookieMonster::CanonicalCookie cc_; + }; + + private: + // Batch a cookie operation (add or delete) + void BatchOperation(PendingOperation::OperationType op, + const std::string& key, + const net::CookieMonster::CanonicalCookie& cc); + // Commit our pending operations to the database. + void Commit(); + // Close() executed on the background thread. + void InternalBackgroundClose(); + + sql::Connection* db_; + + typedef std::list<PendingOperation*> PendingOperationsList; + PendingOperationsList pending_; + PendingOperationsList::size_type num_pending_; + Lock pending_lock_; // Guard pending_ and num_pending_ + + DISALLOW_COPY_AND_ASSIGN(Backend); +}; + +void SQLitePersistentCookieStore::Backend::AddCookie( + const std::string& key, + const net::CookieMonster::CanonicalCookie& cc) { + BatchOperation(PendingOperation::COOKIE_ADD, key, cc); +} + +void SQLitePersistentCookieStore::Backend::UpdateCookieAccessTime( + const net::CookieMonster::CanonicalCookie& cc) { + BatchOperation(PendingOperation::COOKIE_UPDATEACCESS, std::string(), cc); +} + +void SQLitePersistentCookieStore::Backend::DeleteCookie( + const net::CookieMonster::CanonicalCookie& cc) { + BatchOperation(PendingOperation::COOKIE_DELETE, std::string(), cc); +} + +void SQLitePersistentCookieStore::Backend::BatchOperation( + PendingOperation::OperationType op, + const std::string& key, + const net::CookieMonster::CanonicalCookie& cc) { + // Commit every 30 seconds. + static const int kCommitIntervalMs = 30 * 1000; + // Commit right away if we have more than 512 outstanding operations. + static const size_t kCommitAfterBatchSize = 512; + + // We do a full copy of the cookie here, and hopefully just here. + scoped_ptr<PendingOperation> po(new PendingOperation(op, key, cc)); + CHECK(po.get()); + + PendingOperationsList::size_type num_pending; + { + AutoLock locked(pending_lock_); + pending_.push_back(po.release()); + num_pending = ++num_pending_; + } + + if (!getDbThread()) + return; + MessageLoop* loop = getDbThread()->message_loop(); + + if (num_pending == 1) { + // We've gotten our first entry for this batch, fire off the timer. + loop->PostDelayedTask(FROM_HERE, NewRunnableMethod( + this, &Backend::Commit), kCommitIntervalMs); + } else if (num_pending == kCommitAfterBatchSize) { + // We've reached a big enough batch, fire off a commit now. + loop->PostTask(FROM_HERE, NewRunnableMethod(this, &Backend::Commit)); + } +} + +void SQLitePersistentCookieStore::Backend::Commit() { + PendingOperationsList ops; + { + AutoLock locked(pending_lock_); + pending_.swap(ops); + num_pending_ = 0; + } + + // Maybe an old timer fired or we are already Close()'ed. + if (!db_ || ops.empty()) + return; + + sql::Statement add_smt(db_->GetCachedStatement(SQL_FROM_HERE, + "INSERT INTO cookies (creation_utc, host_key, name, value, path, " + "expires_utc, secure, httponly, last_access_utc) " + "VALUES (?,?,?,?,?,?,?,?,?)")); + if (!add_smt) { + NOTREACHED(); + return; + } + + sql::Statement update_access_smt(db_->GetCachedStatement(SQL_FROM_HERE, + "UPDATE cookies SET last_access_utc=? WHERE creation_utc=?")); + if (!update_access_smt) { + NOTREACHED(); + return; + } + + sql::Statement del_smt(db_->GetCachedStatement(SQL_FROM_HERE, + "DELETE FROM cookies WHERE creation_utc=?")); + if (!del_smt) { + NOTREACHED(); + return; + } + + sql::Transaction transaction(db_); + if (!transaction.Begin()) { + NOTREACHED(); + return; + } + for (PendingOperationsList::iterator it = ops.begin(); + it != ops.end(); ++it) { + // Free the cookies as we commit them to the database. + scoped_ptr<PendingOperation> po(*it); + switch (po->op()) { + case PendingOperation::COOKIE_ADD: + add_smt.Reset(); + add_smt.BindInt64(0, po->cc().CreationDate().ToInternalValue()); + add_smt.BindString(1, po->key()); + add_smt.BindString(2, po->cc().Name()); + add_smt.BindString(3, po->cc().Value()); + add_smt.BindString(4, po->cc().Path()); + add_smt.BindInt64(5, po->cc().ExpiryDate().ToInternalValue()); + add_smt.BindInt(6, po->cc().IsSecure()); + add_smt.BindInt(7, po->cc().IsHttpOnly()); + add_smt.BindInt64(8, po->cc().LastAccessDate().ToInternalValue()); + if (!add_smt.Run()) + NOTREACHED() << "Could not add a cookie to the DB."; + break; + + case PendingOperation::COOKIE_UPDATEACCESS: + update_access_smt.Reset(); + update_access_smt.BindInt64(0, + po->cc().LastAccessDate().ToInternalValue()); + update_access_smt.BindInt64(1, + po->cc().CreationDate().ToInternalValue()); + if (!update_access_smt.Run()) + NOTREACHED() << "Could not update cookie last access time in the DB."; + break; + + case PendingOperation::COOKIE_DELETE: + del_smt.Reset(); + del_smt.BindInt64(0, po->cc().CreationDate().ToInternalValue()); + if (!del_smt.Run()) + NOTREACHED() << "Could not delete a cookie from the DB."; + break; + + default: + NOTREACHED(); + break; + } + } + transaction.Commit(); +} + +// Fire off a close message to the background thread. We could still have a +// pending commit timer that will be holding a reference on us, but if/when +// this fires we will already have been cleaned up and it will be ignored. +void SQLitePersistentCookieStore::Backend::Close() { + // Must close the backend on the background thread. + if (!getDbThread()) + return; + + MessageLoop* loop = getDbThread()->message_loop(); + loop->PostTask(FROM_HERE, + NewRunnableMethod(this, &Backend::InternalBackgroundClose)); +} + +void SQLitePersistentCookieStore::Backend::InternalBackgroundClose() { + //DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + // Commit any pending operations + Commit(); + + delete db_; + db_ = NULL; +} + +SQLitePersistentCookieStore::SQLitePersistentCookieStore(const FilePath& path) + : path_(path) { +} + +SQLitePersistentCookieStore::~SQLitePersistentCookieStore() { + if (backend_.get()) { + backend_->Close(); + // Release our reference, it will probably still have a reference if the + // background thread has not run Close() yet. + backend_ = NULL; + } +} + +// Version number of the database. In version 4, we migrated the time epoch. +// If you open the DB with an older version on Mac or Linux, the times will +// look wonky, but the file will likely be usable. On Windows version 3 and 4 +// are the same. +// +// Version 3 updated the database to include the last access time, so we can +// expire them in decreasing order of use when we've reached the maximum +// number of cookies. +static const int kCurrentVersionNumber = 4; +static const int kCompatibleVersionNumber = 3; + +namespace { + +// Initializes the cookies table, returning true on success. +bool InitTable(sql::Connection* db) { + if (!db->DoesTableExist("cookies")) { + if (!db->Execute("CREATE TABLE cookies (" + "creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY," + "host_key TEXT NOT NULL," + "name TEXT NOT NULL," + "value TEXT NOT NULL," + "path TEXT NOT NULL," + // We only store persistent, so we know it expires + "expires_utc INTEGER NOT NULL," + "secure INTEGER NOT NULL," + "httponly INTEGER NOT NULL," + "last_access_utc INTEGER NOT NULL)")) + return false; + } + + // Try to create the index every time. Older versions did not have this index, + // so we want those people to get it. Ignore errors, since it may exist. + db->Execute("CREATE INDEX cookie_times ON cookies (creation_utc)"); + return true; +} + +} // namespace + +bool SQLitePersistentCookieStore::Load( + std::vector<net::CookieMonster::KeyedCanonicalCookie>* cookies) { + scoped_ptr<sql::Connection> db(new sql::Connection); + if (!db->Open(path_)) { + NOTREACHED() << "Unable to open cookie DB."; + return false; + } + + if (!EnsureDatabaseVersion(db.get()) || !InitTable(db.get())) { + NOTREACHED() << "Unable to initialize cookie DB."; + return false; + } + + db->Preload(); + + // Slurp all the cookies into the out-vector. + sql::Statement smt(db->GetUniqueStatement( + "SELECT creation_utc, host_key, name, value, path, expires_utc, secure, " + "httponly, last_access_utc FROM cookies")); + if (!smt) { + NOTREACHED() << "select statement prep failed"; + return false; + } + + while (smt.Step()) { + scoped_ptr<net::CookieMonster::CanonicalCookie> cc( + new net::CookieMonster::CanonicalCookie( + smt.ColumnString(2), // name + smt.ColumnString(3), // value + smt.ColumnString(4), // path + smt.ColumnInt(6) != 0, // secure + smt.ColumnInt(7) != 0, // httponly + Time::FromInternalValue(smt.ColumnInt64(0)), // creation_utc + Time::FromInternalValue(smt.ColumnInt64(8)), // last_access_utc + true, // has_expires + Time::FromInternalValue(smt.ColumnInt64(5)))); // expires_utc + DLOG_IF(WARNING, + cc->CreationDate() > Time::Now()) << L"CreationDate too recent"; + cookies->push_back( + net::CookieMonster::KeyedCanonicalCookie(smt.ColumnString(1), + cc.release())); + } + + // Create the backend, this will take ownership of the db pointer. + backend_ = new Backend(db.release()); + return true; +} + +bool SQLitePersistentCookieStore::EnsureDatabaseVersion(sql::Connection* db) { + // Version check. + if (!meta_table_.Init(db, kCurrentVersionNumber, kCompatibleVersionNumber)) + return false; + + if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { + LOG(WARNING) << "Cookie database is too new."; + return false; + } + + int cur_version = meta_table_.GetVersionNumber(); + if (cur_version == 2) { + sql::Transaction transaction(db); + if (!transaction.Begin()) + return false; + if (!db->Execute("ALTER TABLE cookies ADD COLUMN last_access_utc " + "INTEGER DEFAULT 0") || + !db->Execute("UPDATE cookies SET last_access_utc = creation_utc")) { + LOG(WARNING) << "Unable to update cookie database to version 3."; + return false; + } + ++cur_version; + meta_table_.SetVersionNumber(cur_version); + meta_table_.SetCompatibleVersionNumber( + std::min(cur_version, kCompatibleVersionNumber)); + transaction.Commit(); + } + + if (cur_version == 3) { + // The time epoch changed for Mac & Linux in this version to match Windows. + // This patch came after the main epoch change happened, so some + // developers have "good" times for cookies added by the more recent + // versions. So we have to be careful to only update times that are under + // the old system (which will appear to be from before 1970 in the new + // system). The magic number used below is 1970 in our time units. + sql::Transaction transaction(db); + transaction.Begin(); +#if !defined(OS_WIN) + db->Execute( + "UPDATE cookies " + "SET creation_utc = creation_utc + 11644473600000000 " + "WHERE rowid IN " + "(SELECT rowid FROM cookies WHERE " + "creation_utc > 0 AND creation_utc < 11644473600000000)"); + db->Execute( + "UPDATE cookies " + "SET expires_utc = expires_utc + 11644473600000000 " + "WHERE rowid IN " + "(SELECT rowid FROM cookies WHERE " + "expires_utc > 0 AND expires_utc < 11644473600000000)"); + db->Execute( + "UPDATE cookies " + "SET last_access_utc = last_access_utc + 11644473600000000 " + "WHERE rowid IN " + "(SELECT rowid FROM cookies WHERE " + "last_access_utc > 0 AND last_access_utc < 11644473600000000)"); +#endif + ++cur_version; + meta_table_.SetVersionNumber(cur_version); + transaction.Commit(); + } + + // Put future migration cases here. + + // When the version is too old, we just try to continue anyway, there should + // not be a released product that makes a database too old for us to handle. + LOG_IF(WARNING, cur_version < kCurrentVersionNumber) << + "Cookie database version " << cur_version << " is too old to handle."; + + return true; +} + +void SQLitePersistentCookieStore::AddCookie( + const std::string& key, + const net::CookieMonster::CanonicalCookie& cc) { + if (backend_.get()) + backend_->AddCookie(key, cc); +} + +void SQLitePersistentCookieStore::UpdateCookieAccessTime( + const net::CookieMonster::CanonicalCookie& cc) { + if (backend_.get()) + backend_->UpdateCookieAccessTime(cc); +} + +void SQLitePersistentCookieStore::DeleteCookie( + const net::CookieMonster::CanonicalCookie& cc) { + if (backend_.get()) + backend_->DeleteCookie(cc); +} + +// static +void SQLitePersistentCookieStore::ClearLocalState( + const FilePath& path) { + file_util::Delete(path, false); +} diff --git a/android/app/sqlite_persistent_cookie_store.h b/android/app/sqlite_persistent_cookie_store.h new file mode 100644 index 0000000..9b12ee4 --- /dev/null +++ b/android/app/sqlite_persistent_cookie_store.h @@ -0,0 +1,51 @@ +// Copyright (c) 2010 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. + +// A sqlite implementation of a cookie monster persistent store. + +#ifndef CHROME_BROWSER_NET_SQLITE_PERSISTENT_COOKIE_STORE_H_ +#define CHROME_BROWSER_NET_SQLITE_PERSISTENT_COOKIE_STORE_H_ + +#include <string> +#include <vector> + +#include "app/sql/connection.h" +#include "app/sql/meta_table.h" +#include "base/file_path.h" +#include "base/ref_counted.h" +#include "net/base/cookie_monster.h" + +class FilePath; + +class SQLitePersistentCookieStore + : public net::CookieMonster::PersistentCookieStore { + public: + explicit SQLitePersistentCookieStore(const FilePath& path); + ~SQLitePersistentCookieStore(); + + virtual bool Load(std::vector<net::CookieMonster::KeyedCanonicalCookie>*); + + virtual void AddCookie(const std::string&, + const net::CookieMonster::CanonicalCookie&); + virtual void UpdateCookieAccessTime( + const net::CookieMonster::CanonicalCookie&); + virtual void DeleteCookie(const net::CookieMonster::CanonicalCookie&); + + static void ClearLocalState(const FilePath& path); + + private: + class Backend; + + // Database upgrade statements. + bool EnsureDatabaseVersion(sql::Connection* db); + + FilePath path_; + scoped_refptr<Backend> backend_; + + sql::MetaTable meta_table_; + + DISALLOW_COPY_AND_ASSIGN(SQLitePersistentCookieStore); +}; + +#endif // CHROME_BROWSER_NET_SQLITE_PERSISTENT_COOKIE_STORE_H_ |