blob: 30b32f7a7fc705ab645fac907a3b8912227d0708 [file] [log] [blame]
// 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::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_