1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/activity_log/activity_database.h"
9 #include "base/command_line.h"
10 #include "base/logging.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/threading/thread.h"
14 #include "base/threading/thread_checker.h"
15 #include "base/time/clock.h"
16 #include "base/time/time.h"
17 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "sql/error_delegate_util.h"
20 #include "sql/transaction.h"
21 #include "third_party/sqlite/sqlite3.h"
23 #if defined(OS_MACOSX)
24 #include "base/mac/mac_util.h"
27 using content::BrowserThread;
29 namespace extensions {
31 // A size threshold at which data should be flushed to the database. The
32 // ActivityDatabase will signal the Delegate to write out data based on a
33 // periodic timer, but will also initiate a flush if AdviseFlush indicates that
34 // more than kSizeThresholdForFlush action records are queued in memory. This
35 // should be set large enough that write costs can be amortized across many
36 // records, but not so large that too much space can be tied up holding records
38 static const int kSizeThresholdForFlush = 200;
40 ActivityDatabase::ActivityDatabase(ActivityDatabase::Delegate* delegate)
41 : delegate_(delegate),
44 already_closed_(false),
46 if (CommandLine::ForCurrentProcess()->HasSwitch(
47 switches::kEnableExtensionActivityLogTesting)) {
48 batching_period_ = base::TimeDelta::FromSeconds(10);
50 batching_period_ = base::TimeDelta::FromMinutes(2);
54 ActivityDatabase::~ActivityDatabase() {}
56 void ActivityDatabase::Init(const base::FilePath& db_name) {
57 if (did_init_) return;
59 if (BrowserThread::IsMessageLoopValid(BrowserThread::DB))
60 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
61 db_.set_histogram_tag("Activity");
62 db_.set_error_callback(
63 base::Bind(&ActivityDatabase::DatabaseErrorCallback,
64 base::Unretained(this)));
65 db_.set_page_size(4096);
66 db_.set_cache_size(32);
68 if (!db_.Open(db_name)) {
69 LOG(ERROR) << db_.GetErrorMessage();
70 return LogInitFailure();
73 // Wrap the initialization in a transaction so that the db doesn't
74 // get corrupted if init fails/crashes.
75 sql::Transaction committer(&db_);
76 if (!committer.Begin())
77 return LogInitFailure();
79 #if defined(OS_MACOSX)
80 // Exclude the database from backups.
81 base::mac::SetFileBackupExclusion(db_name);
84 if (!delegate_->InitDatabase(&db_))
85 return LogInitFailure();
87 sql::InitStatus stat = committer.Commit() ? sql::INIT_OK : sql::INIT_FAILURE;
88 if (stat != sql::INIT_OK)
89 return LogInitFailure();
91 // Pre-loads the first <cache-size> pages into the cache.
92 // Doesn't do anything if the database is new.
96 timer_.Start(FROM_HERE,
99 &ActivityDatabase::RecordBatchedActions);
102 void ActivityDatabase::LogInitFailure() {
103 LOG(ERROR) << "Couldn't initialize the activity log database.";
107 void ActivityDatabase::AdviseFlush(int size) {
110 if (!batch_mode_ || size == kFlushImmediately ||
111 size >= kSizeThresholdForFlush) {
112 if (!delegate_->FlushDatabase(&db_))
117 void ActivityDatabase::RecordBatchedActions() {
119 if (!delegate_->FlushDatabase(&db_))
124 void ActivityDatabase::SetBatchModeForTesting(bool batch_mode) {
125 if (batch_mode && !batch_mode_) {
126 timer_.Start(FROM_HERE,
129 &ActivityDatabase::RecordBatchedActions);
130 } else if (!batch_mode && batch_mode_) {
132 RecordBatchedActions();
134 batch_mode_ = batch_mode;
137 sql::Connection* ActivityDatabase::GetSqlConnection() {
138 if (BrowserThread::IsMessageLoopValid(BrowserThread::DB))
139 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
143 LOG(WARNING) << "Activity log database is not valid";
148 void ActivityDatabase::Close() {
150 if (!already_closed_) {
151 RecordBatchedActions();
152 db_.reset_error_callback();
155 already_closed_ = true;
156 // Call DatabaseCloseCallback() just before deleting the ActivityDatabase
157 // itself--these two objects should have the same lifetime.
158 delegate_->OnDatabaseClose();
162 void ActivityDatabase::HardFailureClose() {
163 if (already_closed_) return;
166 db_.reset_error_callback();
168 delegate_->OnDatabaseFailure();
169 already_closed_ = true;
172 void ActivityDatabase::SoftFailureClose() {
175 delegate_->OnDatabaseFailure();
178 void ActivityDatabase::DatabaseErrorCallback(int error, sql::Statement* stmt) {
179 if (sql::IsErrorCatastrophic(error)) {
180 LOG(ERROR) << "Killing the ActivityDatabase due to catastrophic error.";
182 } else if (error != SQLITE_BUSY) {
183 // We ignore SQLITE_BUSY errors because they are presumably transient.
184 LOG(ERROR) << "Closing the ActivityDatabase due to error.";
189 void ActivityDatabase::RecordBatchedActionsWhileTesting() {
190 RecordBatchedActions();
194 void ActivityDatabase::SetTimerForTesting(int ms) {
196 timer_.Start(FROM_HERE,
197 base::TimeDelta::FromMilliseconds(ms),
199 &ActivityDatabase::RecordBatchedActionsWhileTesting);
203 bool ActivityDatabase::InitializeTable(sql::Connection* db,
204 const char* table_name,
205 const char* content_fields[],
206 const char* field_types[],
207 const int num_content_fields) {
208 if (!db->DoesTableExist(table_name)) {
209 std::string table_creator =
210 base::StringPrintf("CREATE TABLE %s (", table_name);
211 for (int i = 0; i < num_content_fields; i++) {
212 table_creator += base::StringPrintf("%s%s %s",
217 table_creator += ")";
218 if (!db->Execute(table_creator.c_str()))
221 // In case we ever want to add new fields, this initializes them to be
223 for (int i = 0; i < num_content_fields; i++) {
224 if (!db->DoesColumnExist(table_name, content_fields[i])) {
225 std::string table_updater = base::StringPrintf(
226 "ALTER TABLE %s ADD COLUMN %s %s; ",
230 if (!db->Execute(table_updater.c_str()))
238 } // namespace extensions