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/init_status.h"
21 #include "sql/transaction.h"
22 #include "third_party/sqlite/sqlite3.h"
24 #if defined(OS_MACOSX)
25 #include "base/mac/mac_util.h"
28 using content::BrowserThread
;
30 namespace extensions
{
32 // A size threshold at which data should be flushed to the database. The
33 // ActivityDatabase will signal the Delegate to write out data based on a
34 // periodic timer, but will also initiate a flush if AdviseFlush indicates that
35 // more than kSizeThresholdForFlush action records are queued in memory. This
36 // should be set large enough that write costs can be amortized across many
37 // records, but not so large that too much space can be tied up holding records
39 static const int kSizeThresholdForFlush
= 200;
41 ActivityDatabase::ActivityDatabase(ActivityDatabase::Delegate
* delegate
)
42 : delegate_(delegate
),
45 already_closed_(false),
47 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
48 switches::kEnableExtensionActivityLogTesting
)) {
49 batching_period_
= base::TimeDelta::FromSeconds(10);
51 batching_period_
= base::TimeDelta::FromMinutes(2);
55 ActivityDatabase::~ActivityDatabase() {}
57 void ActivityDatabase::Init(const base::FilePath
& db_name
) {
58 if (did_init_
) return;
60 if (BrowserThread::IsMessageLoopValid(BrowserThread::DB
))
61 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
62 db_
.set_histogram_tag("Activity");
63 db_
.set_error_callback(
64 base::Bind(&ActivityDatabase::DatabaseErrorCallback
,
65 base::Unretained(this)));
66 db_
.set_page_size(4096);
67 db_
.set_cache_size(32);
69 if (!db_
.Open(db_name
)) {
70 LOG(ERROR
) << db_
.GetErrorMessage();
71 return LogInitFailure();
74 // Wrap the initialization in a transaction so that the db doesn't
75 // get corrupted if init fails/crashes.
76 sql::Transaction
committer(&db_
);
77 if (!committer
.Begin())
78 return LogInitFailure();
80 #if defined(OS_MACOSX)
81 // Exclude the database from backups.
82 base::mac::SetFileBackupExclusion(db_name
);
85 if (!delegate_
->InitDatabase(&db_
))
86 return LogInitFailure();
88 sql::InitStatus stat
= committer
.Commit() ? sql::INIT_OK
: sql::INIT_FAILURE
;
89 if (stat
!= sql::INIT_OK
)
90 return LogInitFailure();
92 // Pre-loads the first <cache-size> pages into the cache.
93 // Doesn't do anything if the database is new.
97 timer_
.Start(FROM_HERE
,
100 &ActivityDatabase::RecordBatchedActions
);
103 void ActivityDatabase::LogInitFailure() {
104 LOG(ERROR
) << "Couldn't initialize the activity log database.";
108 void ActivityDatabase::AdviseFlush(int size
) {
111 if (!batch_mode_
|| size
== kFlushImmediately
||
112 size
>= kSizeThresholdForFlush
) {
113 if (!delegate_
->FlushDatabase(&db_
))
118 void ActivityDatabase::RecordBatchedActions() {
120 if (!delegate_
->FlushDatabase(&db_
))
125 void ActivityDatabase::SetBatchModeForTesting(bool batch_mode
) {
126 if (batch_mode
&& !batch_mode_
) {
127 timer_
.Start(FROM_HERE
,
130 &ActivityDatabase::RecordBatchedActions
);
131 } else if (!batch_mode
&& batch_mode_
) {
133 RecordBatchedActions();
135 batch_mode_
= batch_mode
;
138 sql::Connection
* ActivityDatabase::GetSqlConnection() {
139 if (BrowserThread::IsMessageLoopValid(BrowserThread::DB
))
140 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
144 LOG(WARNING
) << "Activity log database is not valid";
149 void ActivityDatabase::Close() {
151 if (!already_closed_
) {
152 RecordBatchedActions();
153 db_
.reset_error_callback();
156 already_closed_
= true;
157 // Call DatabaseCloseCallback() just before deleting the ActivityDatabase
158 // itself--these two objects should have the same lifetime.
159 delegate_
->OnDatabaseClose();
163 void ActivityDatabase::HardFailureClose() {
164 if (already_closed_
) return;
167 db_
.reset_error_callback();
169 delegate_
->OnDatabaseFailure();
170 already_closed_
= true;
173 void ActivityDatabase::SoftFailureClose() {
176 delegate_
->OnDatabaseFailure();
179 void ActivityDatabase::DatabaseErrorCallback(int error
, sql::Statement
* stmt
) {
180 if (sql::IsErrorCatastrophic(error
)) {
181 LOG(ERROR
) << "Killing the ActivityDatabase due to catastrophic error.";
183 } else if (error
!= SQLITE_BUSY
) {
184 // We ignore SQLITE_BUSY errors because they are presumably transient.
185 LOG(ERROR
) << "Closing the ActivityDatabase due to error.";
190 void ActivityDatabase::RecordBatchedActionsWhileTesting() {
191 RecordBatchedActions();
195 void ActivityDatabase::SetTimerForTesting(int ms
) {
197 timer_
.Start(FROM_HERE
,
198 base::TimeDelta::FromMilliseconds(ms
),
200 &ActivityDatabase::RecordBatchedActionsWhileTesting
);
204 bool ActivityDatabase::InitializeTable(sql::Connection
* db
,
205 const char* table_name
,
206 const char* const content_fields
[],
207 const char* const field_types
[],
208 const int num_content_fields
) {
209 if (!db
->DoesTableExist(table_name
)) {
210 std::string table_creator
=
211 base::StringPrintf("CREATE TABLE %s (", table_name
);
212 for (int i
= 0; i
< num_content_fields
; i
++) {
213 table_creator
+= base::StringPrintf("%s%s %s",
218 table_creator
+= ")";
219 if (!db
->Execute(table_creator
.c_str()))
222 // In case we ever want to add new fields, this initializes them to be
224 for (int i
= 0; i
< num_content_fields
; i
++) {
225 if (!db
->DoesColumnExist(table_name
, content_fields
[i
])) {
226 std::string table_updater
= base::StringPrintf(
227 "ALTER TABLE %s ADD COLUMN %s %s; ",
231 if (!db
->Execute(table_updater
.c_str()))
239 } // namespace extensions