// Copyright (c) 2013 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 CHROME_BROWSER_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_DATABASE_H_ #define CHROME_BROWSER_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_DATABASE_H_ #include <string> #include <vector> #include "base/basictypes.h" #include "base/files/file_path.h" #include "base/gtest_prod_util.h" #include "base/memory/ref_counted_memory.h" #include "base/synchronization/lock.h" #include "base/timer/timer.h" #include "chrome/browser/extensions/activity_log/activity_actions.h" #include "chrome/common/extensions/extension.h" #include "content/public/browser/browser_thread.h" #include "sql/connection.h" #include "sql/init_status.h" namespace base { class Clock; class FilePath; } namespace extensions { // Encapsulates the SQL connection for the activity log database. This class // holds the database connection and has methods for writing. All of the // methods except the constructor need to be called on the DB thread. // // Object ownership and lifetime is a bit complicated for ActivityLog, // ActivityLogPolicy, and ActivityDatabase: // // ActivityLog ----> ActivityLogPolicy ----> ActivityDatabase // ^ | // | | // \--(ActivityDatabase::Delegate)-/ // // The ActivityLogPolicy object contains a pointer to the ActivityDatabase, and // the ActivityDatabase contains a pointer back to the ActivityLogPolicy object // (as an instance of ActivityDatabase::Delegate). // // Since some cleanup must happen on the database thread, deletion should occur // as follows: // 1. ActivityLog calls ActivityLogPolicy::Close() // 2. ActivityLogPolicy should call ActivityDatabase::Close() on the database // thread. // 3. ActivityDatabase::Close() shuts down the database, then calls // ActivityDatabase::Delegate::OnDatabaseClose(). // 4. ActivityDatabase::Delegate::OnDatabaseClose() should delete the // ActivityLogPolicy object. // 5. ActivityDatabase::Close() finishes running by deleting the // ActivityDatabase object. // // (This assumes the common case that the ActivityLogPolicy uses an // ActivityDatabase and implements the ActivityDatabase::Delegate interface. // It is also possible for an ActivityLogPolicy to not use a database at all, // in which case ActivityLogPolicy::Close() should directly delete itself.) class ActivityDatabase { public: // Interface defining calls that the ActivityDatabase can make into a // ActivityLogPolicy instance to implement policy-specific behavior. Methods // are always invoked on the database thread. Classes other than // ActivityDatabase should not call these methods. class Delegate { protected: friend class ActivityDatabase; // A Delegate is never directly deleted; it should instead delete itself // after any final cleanup when OnDatabaseClose() is invoked. virtual ~Delegate() {} // Initializes the database schema; this gives a policy a chance to create // or update database tables as needed. Should return true on success. // Will be called from within a database transaction. virtual bool InitDatabase(sql::Connection* db) = 0; // Requests that the policy flush any pending actions to the database. // Should return true on success or false on a database error. Will not be // called from a transaction (the implementation may wish to use a // transaction for the flush). virtual bool FlushDatabase(sql::Connection* db) = 0; // Called if the database encounters a permanent error; the policy should // not expect to make any future writes to the database and may want to // discard any queued data. virtual void OnDatabaseFailure() = 0; // Called by ActivityDatabase just before the ActivityDatabase object is // deleted. The database will make no further callbacks after invoking // this method, so it is an appropriate time for the policy to delete // itself. virtual void OnDatabaseClose() = 0; }; // Value to be passed to AdviseFlush below to force a database flush. static const int kFlushImmediately = -1; // Need to call Init to actually use the ActivityDatabase. The Delegate // provides hooks for an ActivityLogPolicy to control the database schema and // reads/writes. explicit ActivityDatabase(Delegate* delegate); // Opens the DB. This invokes OnDatabaseInit in the delegate to create or // update the database schema if needed. void Init(const base::FilePath& db_name); // An ActivityLogPolicy should call this to kill the ActivityDatabase. void Close(); // Inform the database that there may be additional data which could be // written out. The size parameter should indicate (approximately) how many // records are queued to be written; the database may use this information to // schedule a flush early if too much data is queueing up. A value of // kFlushImmediately will force an immediate call into // Delegate::FlushDatabase(); otherwise, it is up to the database to // determine when to flush. void AdviseFlush(int size); // Turns off batch I/O writing mode. This should only be used in unit tests, // browser tests, or in our special --enable-extension-activity-log-testing // policy state. void SetBatchModeForTesting(bool batch_mode); bool is_db_valid() const { return valid_db_; } // A helper method for initializing or upgrading a database table. The // content_fields array should list the names of all of the columns in the // database. The field_types should specify the types of the corresponding // columns (e.g., INTEGER or LONGVARCHAR). There should be the same number of // field_types as content_fields, since the two arrays should correspond. static bool InitializeTable(sql::Connection* db, const char* table_name, const char* content_fields[], const char* field_types[], const int num_content_fields); // Runs the given callback, passing it a handle to the database connection. // If the database is not valid, the callback is run (to allow it to do any // needed cleanup) but passed a NULL value. void RunOnDatabase(const base::Callback<void(sql::Connection*)>& callback); private: // This should never be invoked by another class. Use Close() to order a // suicide. virtual ~ActivityDatabase(); // Used by the Init() method as a convenience for handling a failed database // initialization attempt. Prints an error and puts us in the soft failure // state. void LogInitFailure(); // When we're in batched mode (which is on by default), we write to the db // every X minutes instead of on every API call. This prevents the annoyance // of writing to disk multiple times a second. void RecordBatchedActions(); // If an error is unrecoverable or occurred while we were trying to close // the database properly, we take "emergency" actions: break any outstanding // transactions, raze the database, and close. When next opened, the // database will be empty. void HardFailureClose(); // Doesn't actually close the DB, but changes bools to prevent further writes // or changes to it. void SoftFailureClose(); // Handle errors in database writes. For a serious & permanent error, it // invokes HardFailureClose(); for a less serious/permanent error, it invokes // SoftFailureClose(). void DatabaseErrorCallback(int error, sql::Statement* stmt); // For unit testing only. void RecordBatchedActionsWhileTesting(); void SetTimerForTesting(int milliseconds); // Retrieve a handle to the raw SQL database. This is only intended to be // used by ActivityLogDatabasePolicy::GetDatabaseConnection(), and should // only be called on the database thread. sql::Connection* GetSqlConnection(); // A reference a Delegate for policy-specific database behavior. See the // top-level comment for ActivityDatabase for comments on cleanup. Delegate* delegate_; sql::Connection db_; bool valid_db_; bool batch_mode_; base::TimeDelta batching_period_; base::RepeatingTimer<ActivityDatabase> timer_; bool already_closed_; bool did_init_; friend class ActivityLogDatabasePolicy; FRIEND_TEST_ALL_PREFIXES(ActivityDatabaseTest, BatchModeOff); FRIEND_TEST_ALL_PREFIXES(ActivityDatabaseTest, BatchModeOn); FRIEND_TEST_ALL_PREFIXES(ActivityDatabaseTest, BatchModeFlush); DISALLOW_COPY_AND_ASSIGN(ActivityDatabase); }; } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_ACTIVITY_LOG_ACTIVITY_DATABASE_H_